vercel-denoで自作DenoモジュールをServerless化する
先日、Denoを使ってZennのAPIを作りました。
これはDenoのプログラムから呼び出して使うことはできるのですが、せっかくなのでDeno以外からもアクセスできるようにしました。
ローカルサーバーを作る
まず、httpでサーバーからアクセスできるようにします。
Deno公式でローカルサーバーを立てる方法が紹介されています。
最もシンプルなサーバーは次のようになります [1] 。同ディレクトリに前回作成したzenn_api.tsがある想定です。
import { serve } from "https://deno.land/std@0.99.0/http/server.ts";
import { zennApi } from "./zenn_api.ts";
const port = 8080;
const server = serve({ port });
console.log(`HTTP webserver running. Access it at: http://localhost:${port}/`);
for await (const request of server) {
const body = JSON.stringify(await zennApi(request.url));
const headers = new Headers({ "Content-Type": "application/json" });
request.respond({ status: 200, headers, body });
}
request.urlには相対パスが入ってくるので、それをzennApi()に渡します。
この結果がJSONで帰ってくるので、headersに"Content-Type": "application/json"を設定して返却しています。
立ち上げます。
❯ deno run --allow-net server.ts
HTTP webserver running. Access it at: http://localhost:8080/
別端末からcurlしてみます。
❯ curl localhost:8080
{"dailyTechArticles":[{"id":41129,"title":"正式仕様リリース! JavaScriptの最新仕様ES2021で追加された新機能まとめ","slug":"es2021-whats-new","published":true,"commentsCount":3,"likedCount":292,"bodyLettersCount":9013,"readingTime":10,"articleType":"tech","emoji":"💐"
(略)
"page":"/","query":{}}
❯ curl localhost:8080/kawarimidoll
{"user":{"id":39895,"username":"kawarimidoll","name":"kawarimidoll","avatarSmallUrl":"https://storage.googleapis.com/zenn-user-upload/avatar/icon_2379ac8d86.jpeg","avatarUrl":"https://storage.googleapis.com/zenn-user-upload/avatar/2379ac8d86.jpeg",
(略)
"page":"/[username]","query":{"username":"kawarimidoll"}}
取得できました。このサーバーを公開すれば、外部からのアクセスが可能になります。
Vercelデプロイ用に設定する
今回はvercel-denoを使ってVercelへデプロイします。
現在の状態のままではデプロイできないので、いくつか設定を行っていきます。
serverの設定
vercel-denoでServerless関数を実行する際、ローカルサーバーを立てた際のようにrequestを待ったりする処理は必要ありません。
ハンドラだけあれば良いので、READMEの例に従い、先程のserver.tsを整理します。
- import { serve } from "https://deno.land/std@0.99.0/http/server.ts";
+ import { ServerRequest } from "https://deno.land/std@0.99.0/http/server.ts";
import { zennApi } from "./zenn_api.ts";
- const port = 8080;
- const server = serve({ port });
- console.log(`HTTP webserver running. Access it at: http://localhost:${port}/`);
- for await (const request of server) {
+ export default async (request: ServerRequest) => {
const body = JSON.stringify(await zennApi(request.url));
const headers = new Headers({ "Content-Type": "application/json" });
request.respond({ status: 200, headers, body });
- }
+ };
なお、ローカルで実行する場合は、requestをハンドラにわたす処理をdebug.tsに退避しておけば可能です。
import { serve } from "https://deno.land/std@0.99.0/http/server.ts";
import handler from "./server.ts";
const port = 8080;
const server = serve({ port });
console.log(`HTTP webserver running. Access it at: http://localhost:${port}/`);
for await (const request of server) {
handler(request);
}
❯ deno run --allow-net debug.ts
HTTP webserver running. Access it at: http://localhost:8080/
ディレクトリ構成の修正
VercelのServerless Functionsの制約上、使用するコードをapiディレクトリ配下においておく必要があります [2] 。
後ほどデプロイしますが、例えばserver.tsをデプロイ項目に指定しても、apiディレクトリ内でないと怒られます。

ということで、実行に必要なファイルをapiディレクトリ配下に移動します。
今回のデプロイとは関係ないけど必要な修正
zenn_api.tsもserver.tsもapiディレクトリに入るので、これらの相対パスは変化しませんが、前回モジュール公開用に作成したmod.tsはルートに残しておきたいので、このインポートパスを修正します。
- export { ZENN_ROOT, zennApi } from "./zenn_api.ts";
+ export { ZENN_ROOT, zennApi } from "./api/zenn_api.ts";
ローカル実行用にdebug.tsを作った場合も、server.tsのインポートパスの修正が必要になると思います。
- import handler from "./server.ts";
+ import handler from "./api/server.ts";
(略)
Vercelの設定ファイルの追加
vercel.jsonを作成します。
{
"functions": {
"api/server.ts": {
"runtime": "vercel-deno@0.8.0",
"includeFiles": "api/*.ts"
}
},
"rewrites": [{ "source": "/(.*)", "destination": "/api/server" }]
}
functionsで、api/server.tsのランタイムをvercel-denoに設定しています。また、apiディレクトリ配下の他のファイルを読み込むよう指定しています。
また、デフォルトでは、xxx.vercel.app/apiへのアクセスが自動的にapiディレクトリ以下のファイルに割り振られるようになっています。
どういうことかというと、xxx.vercel.app/api/helloにアクセスするとapi/hello.tsが実行されるような形式です。
しかし今回は、すべてのアクセスをapi/server.tsでさばきたいので、rewritesでその旨を設定します。
ここの記法は正規表現っぽいですが違うので注意してください。
また、このvercel.jsonおよびapi配下以外のファイルはデプロイ時には不要なので、.vercelignoreも追加しておきましょう。
/*
!api
!vercel.json
デプロイに必要な構成の確認
ここまでで、以下のようなファイル構成になっていると思います[3]。以前の記事で作成したmod.tsや、今回確認用に作成したdebug.tsもありますが、.vercelignoreのルールによりデプロイはされません。
❯ tree
.
├── .vercelignore
├── api
│ ├── deps.ts
│ ├── server.ts
│ └── zenn_api.ts
├── debug.ts
├── mod.ts
└── vercel.json
これで準備が完了しました。Vercelからデプロイしてみます。
デプロイする
Vercelのダッシュボードに入りましょう。
GitHubとの連携が必要になります。
New Projectから…

目的のものをImportします。

Personalを使用します。

特に設定変更は不要です。

デプロイします。

Deploy logの途中で赤文字が出るかもしれません[4]が、最終的に成功の表示が出れば問題ないはずです。
以下のパスでアクセスできるようになります。
https://deno-zenn-api.vercel.app/
https://deno-zenn-api.vercel.app/kawarimidoll
TOPページやユーザーページの内容を取得できました。
おわりに
今回は自作したZennのデータを取得するモジュールをVercelに上げてみました。
何らかのデータをJSON形式で出力するモジュールであればほぼそのまま使えるはずですし、その他の形式にも応用できると思います。
関連プロジェクト
実は、vercel-denoはしばらく前に作ったTypograssyというツールで使っていました。こちらはデータをSVG形式で出力しています。
Typograssyの動作説明はQiitaに書いているのでよろしければご覧ください。
動作説明は残しておいたのですが、実装まわりの手順を完全に忘れていて難儀しました。
ちゃんと作業記録や知見は残しておかないと駄目ですね。
過去の自分のためにも未来の自分のためにも記録をつけておくようにしたいものです。
Discussion