NestJS 素振り 感想
#TypeScript #Node.js
https://github.com/fsubal/nestjs-docker-prisma-mysql を作ってみて思ったこと、意識してたこと
前に一度触ったことがあったが、改めて大きめのアプリケーションテンプレートを作ってみることにした
Controller と DI のレールを敷いてくれる点は良い
DB についてはさほど out-of-the-box ではない
自分で選ぶ必要がある
が、組み合わせ方のドキュメントがちゃんとしてる
もっと #Rails みたいな感じを想定してたがあんまり MVC 感はない
個人的には #PHP の Slim(v3 以降のやつ)とかを思い出す使い心地だった
公式ドキュメントを見ると大体載ってて便利
DB は Prisma に任せると快適
過去 NestJS を避けてた理由の1つに ORM が TypeORM や Knex になってしまうことがあった
いまは Prisma + NestJS のドキュメントも充実している
コンテナ内の DB に入って操作するのが Prisma Studio で全部済むので便利
モダンな phpMyAdmin があると嬉しい
が、migration をしてスキーマを変えたときに再起動が必要 (?) なのはダルい
https://github.com/prisma/studio/issues/625
express だと全部「ミドルウェア」として雑にくくられる概念に名前がついている点は良い
Guards, Middlewares, Pipe ……
express 脳だと全部 app.use() に入れるやつ、としか認識できない
デコレータが好きでないので、Controller 以外ではできるだけ使わない縛りをすることにした
DTO はできるだけ素の TypeScript クラスのままにする
内部的には nestjs/swagger に @ApiProperty を生成させてはいるのだが
Service では @Injectable 以外書かない
class-validator を避ける
逆に Controller は便利に書こうと思うとどんどん Decorator まみれになる
ParseIntPipe、ApiResponse、I18n などなど…
デコレータがところどころ型安全でないのが腹立つ
たとえば @Param('id', ParseIntPipe) id: string は型エラーになってほしい( id: number が正しいので )
TS の decorators の仕組みを理解してないが、原理的に厳しいかもしれない
デフォルトの tsconfig.json が strict: true じゃない
気づくのに時間がかかった、さっさと true にしたほうが良い
宣言的なバリデーションはあまり追求しない
ある程度 Service 内でトランザクションスクリプトをすると割り切る
大体の場合、トランザクションスクリプトが悪く見えるのはただ関数が長いせいであり、それはトランザクションスクリプトあまり関係ない(関数を分ければ良い)
JS やってる人なら純粋関数使いながら責務を分けることにも慣れてるはず
class-validator は使わなかった
必要に応じて zod を使うようにした。zod は最高
テーブルに近い層で validation をすることにあまり良い思い出がない
特定の場面では走ってほしくない validation が生じる苦痛のほうが結局大きい
「このフィールドは管理者なら好きに変更できるけど一般ユーザーは作成時にしか入力できない」みたいな
#Rails 書いてるときも結局、validate ... on: になるのを避けるべく、ApplicationRecord よりも Form Object に validation を寄せてることが多い
#なんでも_Form_Object_になってしまう
保存処理の責務を分けたければ、保存処理を実行するクラスを別に切り出せば良い(いざとなれば CQRS 専用モジュールも提供されてる)
#REST でやるにあたって JSON の形式を統一したかったが、めんどくさくて諦めてしまった
最初いわゆる JSON:API フォーマットをやろうとしてた
data と links があって、error object が決まってて、Mime Type も application/vnd.api+json にするアレね
https://jsonapi.org/
が、どれだけ Controller でそういうのを書いても、エラー時に NestJS が決めた形の json ( { statusCode, message } )が飛び出してくる
全統一はキリがないと思い始めて結局諦めた
おとなしくこれに従ったほうが良い
あと DTO をもとに OpenAPI の型を生成しようと思うと、すべての DTO クラスに data と links を書くような形になる
これを何とかすることはできるけど、そこまでして統一するメリットが薄いと感じてやめた
REST でルールを統一することの徒労感を味わうとやっぱり GraphQL に流れたくなりますね
i18n については公式では何もしてくれないので何か考える必要がある
JSON のエラーメッセージを日本語英語どっちで返すか悩んだときに気づいた
#Rails のようなビューまで面倒見るフレームワークならともかく、API サーバーメインのフレームワークだとそこにリソースを割かないのは理解できる
公式のでないライブラリはいろいろ存在する
そもそも API のエラーメッセージって i18n するべきか?みたいな問題はある
エラーコードだけ返して、ユーザー向けのメッセージはフロントエンドでやればいいじゃんみたいな
最初 Accept-Language を判定して JSON:API 形式の errors 配列にいい感じにメッセージを入れる処理を書こうとしたがだるくてやめた
JSON:API 形式諦めたもう1つの理由が実はこれ
仮にリクエスト時 i18n をやるにしても、たぶんエラーメッセージぐらいしか出番がない
ので、Exception Filter を使ってなんとかするアプローチが最も筋が良さそうと思って採用した
https://qiita.com/kyusyukeigo/items/abad98429030b8adb5d0 が良かった
メールの i18n とかも世の中にはあるけど…
そういうのは Accept-Language じゃなくてユーザーの言語設定とか使うはず
ので、ユーザー登録を実装するまでは考えなくていい話
Docker でいい感じに開発する方法についてはドキュメントにあったら嬉しかったなーと少し思った
ここだけ調べるときのソースが Qiita とかその辺の個人ブログになってだるかった
まぁ普通の Node.js + MySQL のコンテナ作るのと変わらんので良いんですが #SQL
公式としてはホストマシンでやったほうが Live Reload とか簡単ですよという気持ちなのかもしれない
私もだいたいの場合 docker なしで node.js やるけど、MySQL とかがいるとまぁ欲しくなるじゃないですか
コンテナに載せた結果 nest watch でサーバーをリロードしている間 Empty response が返る体験になってしまった
まぁ良いっちゃ良いんだけど…
まだわかってないこと
デプロイ
Prisma に env ファイルがあるので、本番だけクラウドの MySQL につなぐとかをいい感じにする必要がある
NestJS と Prisma がそれぞれで env ファイルを要求してくるのでいい感じに管理するとか
もしデプロイするなら api.〇〇.net みたいなドメインで NestJS を、サブドメインなしの 〇〇.net で Next.js をサーブする( DB は Planetscale )とかを考えそう
もちろん CORS つきで
ファイルアップロード
multer を入れて受け取ったファイルを任意の S3 互換の何かに投げるみたいな
認証、認可、ログイン
Guards を使ってどうこうみたいなの
https://speakerdeck.com/ci7lus/nestjs-meetup-tokyo-number-1 みたいな話
Q. ここまで作り込むなら Blitz.js ではダメだったのか?
API サーバーを作るとしたら、の新しい選択肢が欲しかった
ビューまで一貫して面倒を見てくれるということにそもそも興味がなかった