Posted at

正月休みにnode初心者が勉強したメモ

正月休みにnode.jsの勉強をしたメモ

前知識としては nodeは非同期で async/await使うと便利というぐらい


作るもの

スマートフォンゲーム向けのAPIサーバを題材にした

大雑把な設計は下記

WindowsでもMac、Linuxでも動くようにする

開発は Windows10 Proと Mac Mojave で開発する

Node.js、Express、MongoDB、Redis、MySQL

を使う

RedisはMongoのAPIキャッシュに使う

MySQLは 課金関連などのトランザクションがシビアな部分に使うかも


設計

開発を早く始めたかったので適当にした

ユーザテーブルは1つで良いが、マイクロサービス、DBレスポンスなど考え 機能ごとに分割した

マスタテーブルは当然複数分割

APIは user_idごとにシャーディングしやすいよう、パスパラメータの最初の方にuser_idを入れてみる

この辺は パッと思いついた設計なので変わるかもしれない


暗号化等

とりあえずJWTを使う事にする

デバイスIDにより新規登録を行った時 user_idとpasswordが発行される

ランダムパスワードなのでHash化しなくていいが勉強のためHash化して保存する

user_idとpasswordでログインしたら 数時間有効なSessionが発行されRedisにキャッシュされる

以降の通信には user_idとsession_idを送り認証する

サンプルなのでこのぐらいでいいはず


環境構築

Dockerにする。DB等もたくさん使うので DockerComposeにする


Windows

Windows10 Proなので Docker for Windowsにする

WSLでBashを使いつつ、パス問題があるので Dockerは今回は Gitbadhにした。使い分け便利!


Mac

Docker for Mac

特に変な設定は行わない


DockerCompose

小さいイメージにしたいのでAlpineで

node、Redis、Mongo、MySQLの4つを建てる

まずは通常の設定をし起動するようにする


サンプルプロジェクト

私のいつもの勉強法は

Githubで該当する条件 今回はNode.js で検索し

スター上位から 参考に適したプロジェクトを探す

今回は

https://github.com/linnovate/mean

を参考にしてみた

Gitlab.comでソース管理を行なう


HelloWorld

express等をnpmでインストールする

routerを使い / へのアクセスで HelloWorldを返すようにした


DevOps

面倒くさいが最初にやるべきなのは 作業工数が下がる環境構築

ってことでまず nodemonによる自動ビルドを行った

裏でファイルを変更したら 即時にnodeを立ち上げ直してくれる

SlackBotを導入し Slackにコミット、Issue等を送るように

APIのテストのために PostManを使い、テストデータを作る


DB作成

MongoDBのラッパーにMongooseを使う

メインとなる5テーブルほど定義を作る

MongoDBでコマンドラインからDBを操るテスト

そして DB初期値で数人のユーザデータを作る

Require関連があるのかー 設定する


CRUDするAPI作成

APIを何個か作ってテストする

body-parserなどを使ってJSONパースする


リファクタリング

フォルダ構成などを見直し MVC的な構成にする

ModelsにDBスキーマ等を記述

ControllersにデータのValidateや変形を行い

Routesにルーティング

コントローラーが太ってるけど とりあえず後で考えよう

コールバックをやめ、async/awaitにした。

また、Routeを渡したときに Requestの中身が消えて消耗したが

const router = express.Router( { mergeParams: true });

と、mergePramasを設定すれば Requestを引き継げた すばらしい


Webpackで消耗する

Typescript化を考えたので Webpackの導入を考える

が ちょっと消耗したので、Webpackあきらめる


Typescript導入

Webpackを使わずに導入した

今はWebpack使わなくても concurrentlyを使えば tscをwatch出来た

JSを1つにまとめることは出来ないが必須ではない

Dockerの構成を見直したり 少し苦労したが Typescriptで動くようになった


Typescript化

Typescriptにしても JavaScriptの文法が使えるが

せっかくなので Typescriptにかえてみる

TypeScriptは基本的には 型付けやClass等が使えるようになっている

その JavaScriptライブラリの型付けのライブラリ@typesもインストールする

export default等 色々とややこしい


Validation

APIのデータのValidationに Joiを使ってみる

これで APIの入力JSONに 必須項目や型が設定出来、不要なデータがあるとエラーで落とせるようになる

設定ファイル等にもいちおうValidationを導入した

おそらく最終的には 設定ファイルは環境変数なりファイルなりから読む事になるだろうから


node gyp

次のパスワード暗号化に bcryptを使う事にしたが

npmのinstallでエラーがおきる

原因を調べたところ、alpineには Pythonやg++等がないため node gypがビルド出来てない

ので 色々と消耗した

結論からいえば dockerfileにて node gyp環境をつくればいいようだ

# node-gyp使うためPythonとc++必要

RUN apk --no-cache add --virtual native-deps \
g++ gcc libgcc libstdc++ linux-headers make python && \
npm install --quiet node-gyp -g &&\
npm install --quiet
# apk del native-deps

こんなかんじで 入れると大丈夫のようだ

いちおう 怖いので delはコメントにしてるが、後に無駄は全部排除したい

これで bcryptがインストールできるようになった


パスワード暗号化

パスワードを生成し bcryptで暗号化し、DBに保存する

何度もいうが 今回はパスワードは自動生成なので 暗号化しなくてもいいんだけどね・・

passportでちゃんとBasic認証出来るところまで。

APIサーバなのでセッションはfalseに(同じサーバになるとはかぎらない)


ちゃんと暗号化考える

JWTで行なう方針で問題ないことを確認

JWTに対する知見をゲット

passportと組み合わせ、JWTのトークンを生成し

クライアントのAuthorizedヘッダにトークンを渡せば 承認ができるはず

で 一通りの設計やテストコードを成功させる


APIやテーブルのリファクタリング

今まではAPIエントリポイントは 適当な設計だったが

マイクロサービス化を考え、最初にサブシステム名≒テーブル名 にし、振り分けやすくし

その後に {user_id}をつけ、シャーディング時に振り分けやすく設計の見直し

以上が三ヶ日にできたこと


インストールしたnpm


types

"@types/bcrypt": "^3.0.0",

"@types/express": "^4.16.0",

"@types/joi": "^14.0.1",

"@types/mongoose": "^5.3.5",

"@types/node": "^10.12.18",


ビルド、自動起動

"typescript": "^3.2.2",

"node-gyp": "^3.8.0",
"nodemon": "^1.18.9",
"concurrently": "^4.1.0",


webAPI関連

"express": "^4.16.4",

"express-async-handler": "^1.1.4",
"body-parser": "^1.18.3",
"compression": "^1.7.3",
"cookie-parser": "^1.4.3",
"helmet": "^3.15.0",
"method-override": "^3.0.0",


認証、暗号、バリデーション

"bcrypt": "^3.0.3",

"joi": "^14.3.1",
"mongoose": "^5.4.1",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",

##その他


"winston": "^3.1.0"

"moment": "^2.23.0",

"moment-timezone": "^0.5.23",


今後やること

認証(実際は許可)でJWTを実際に使う

URL、関数名のリファクタリング

Request内容のValidate(現在はDB更新時のValidateのみ)

Responceをちゃんと返す(エラーコード作る)

作ってないAPI、DB

課金関連

Push通知等のサービス

エラー処理

テストフレームワーク

CI、ChatOpsでの自動ビルドパイプライン


参考にしたサイト

https://github.com/linnovate/mean

https://qiita.com/nkjm/items/723990c518acfee6e473

http://hyata-dev.hateblo.jp/entry/2016/03/11/133632

https://www.ikemo3.com/inverted/typescript/javascript-to-typescript/

https://qiita.com/zuzuwen/items/2577680a5432f5b15247

https://qiita.com/papi_tokei/items/9b852774114ebc7a6255

https://knimon-software.github.io/www.passportjs.org/guide/

https://www.djamware.com/post/58eba06380aca72673af8500/node-express-mongoose-and-passportjs-rest-api-authentication

など 数々のサイトありがとうございました