Turboreoで管理しているTypeScript製のExpressのAPIサーバーをVercel上で稼働させるという、需要がないであろうTips。
なおVercelではフリープランの場合、APIの数が12個を超えるとPro以上のプラン加入が必要となるので注意。
成果物
環境:
パッケージ | バージョン |
---|---|
Node.js | 20.10.0 |
Yarn | 1.22.21 |
ベースとしてTurborepoの公式ドキュメントにある Kitchen Sink を利用。
気をつけるポイント
ポイントは3つだけ。
tsupを使う
monorepoをコンパイルするために tsc ではなく tsup を使う必要がある。
ディレクトリの名前をapiにする
ディレクトリ名を src → api に変える。これは公式ドキュメントでもそう記述されている。
Vercelのデプロイ設定の変更
アプリの Settings > Build & Development Settings から以下を上書きする。
Build Command: cd ../.. && turbo run build --filter=api...
Output Directory: .
同様に Root Directory も apps/api
に変更。
以上の手順でAPIがデプロイできるはず。APIが正しくデプロイできているかどうかはデプロイのDeployment Details画面からDeployment Summaryを参照し、「Functions」というのが追加されているかどうかで判断するとよい。
そもそもの問題
以下のStackoverflowを参照していただくと分かるように、多くの人がこの簡単そうにみえる作業を完遂できない。
そしてなぜか上記QA内でマイナス評価をつけられている回答が、唯一公式でも挙げられている方法なのがまた混乱を生んでいる。
TypeScriptのコンパイル後のソースは大抵はdistに配置するところが、そうするとVercelでExpressのAPIは動作しない。
じゃあdistじゃなくてapiというディレクトリにコンパイル済みのファイルを配置すれば解決するのでは? と思う方もいるだろう。
実際はこれも無理で、Vercelのビルド時にapiディレクトリ配下にコンパイル前のtsファイルが入ってないとだめらしい。つまり、以下のようなディレクトリ構成はNG。
.
├── api
│ └── index.js
├── node_modules
├── package.json
├── src
│ ├── __tests__
│ ├── index.ts
│ └── server.ts
├── tsconfig.json
├── tsup.config.ts
├── turbo.json
└── vercel.json
api配下にサーバーレスファンクションとして動作するような構成、例えば以下のようにする必要がある。
.
├── api
│ ├── __tests__
│ ├── index.js
│ ├── index.ts
│ └── server.ts
├── node_modules
├── package.json
├── tsconfig.json
├── tsup.config.ts
├── turbo.json
└── vercel.json
この構成の注意点としては、tsupでビルドするとapi配下にindex.jsファイルが生成されてしまう。間違ってこれをコミットしてしまわないように注意が必要。
また、実装の責務をディレクトリで分けてしまうとそのディレクトリのファイルの1つ1つが、1つのサーバーレスファンクションとして扱われてしまう。Utilディレクトリを作って共通処理をもたせるというのも同様。この仕様のせいで、先に述べた 「APIの数が12個」という無料版の制約 を簡単に超過してしまう。
その他の解決方法
1. apiディレクトリを作成せずvercel.jsonで対応
以下のようにvercel.jsonの記法を変えることで、apiディレクトリを作成しなくてもデプロイすることは可能。
ただし、上記2つのリポジトリのvercel.jsonに使われているbuildsやroutesは、公式ドキュメントでは推奨される記法ではないとされている。
https://vercel.com/docs/projects/project-configuration#builds
https://vercel.com/docs/projects/project-configuration#routes
このパターンでも結局TypeScriptの場合はコンパイルした成果物をdistなどのディレクトリではなく同一ディレクトリに配置する必要があるので、実はapiディレクトリがあるかないかの違いしかない。(そもそもbuildsをvercel.jsonで書き換えることでturborepoのビルドが上書きされてしまうのでちょっと工夫が必要かもしれない)
2. ExpressをAPIではなくWebアプリとしてデプロイする
これも有用。だが個人的にAPIサーバーなのに?という疑問が湧く。
3. コンパイルファイルごとGit管理する
distをあとから読み込ませるのが無理なら、最初からdist(とコンパイルされるファイル)をGit管理しておけばよくないか?という解決策。
詳しい手順は以下に記載されている。(distの扱いについてもコメントに記述がある)
https://dev.to/tirthpatel/deploy-node-ts-express-typescript-on-vercel-284h
4. Next.jsをAPIサーバーとして使う
実はこの方法はVercelが公式で推奨している。
Next.jsを使うのならわざわざAPIとフロントエンドに分けないのでは?と個人的には思う。
5. NestJSを使う
最後にこれを挙げるのはどうかと思うが、多分一番安定して動かせる。ちゃんとしたTypeScriptのAPIサーバーをVercelで動かしたいのならこれ一択と考えてよい。
まとめ
今回はTurborepoでシンプルなAPI構成をExpressサーバーとしてVercelで動作させる事例を紹介した。
実際の開発では、SentryやPrisma、TypeORMなどのライブラリを使うことが多いと思う。そして多分そういう運用にはVercel+Expressは耐えられない気がするので、大人しくAWS、GCP、Azureなどのクラウドサービスやサーバーレスサービスを使うか、Next.jsやNestJSを使おう。