AWS Lambda のハンドラは辛い
こんにちは!AWS Lambda初心者です!
AWS Lambda と言えば、サーバレスかつ非常に安い価格で無限にスケールアウトできる処理を書けることで有名ですが1、 2022年04月の Function URL アップデート でAPI Gatewayなど用いなくてもラムダ単体かつ無料でHTTPリクエストを処理できるURLを持てるようになり、かなりの神サービスになりました。
弊社でも Function URL でラムダによる外部連携用のサーバを立てておき、ちょっとしたHTTPのリクエストを受け取って、他所のサーバにデータ連携したり、S3にリクエスト中のデータを保存したりする用途に積極的に使っています。
さて、そんな神サービスである AWS Lambda 君ですが、いざ実際にJavaScriptでHTTPサーバを書こうとすると…… ちょっと辛い!
// https://lambda-function-url/echo にアクセスすると "Hello" を返すサーバの例
exports.handler = async (event) => {
if (event.httpMethod === "GET" && event.path === "/echo") {
return {
statusCode: 200,
body: "Hello"
};
}
return {
statusCode: 404
};
};
-
event
に何が入ってくるのかイマイチピンとこない- しかもAPIゲートウェイ経由か関数URL経由かによって渡されるものが違う
- 渡されたオブジェクト内のいろんなフィールドに同じ値が入っているが、そのうちのどれを使っていいのか分からない
-
return
も何を返せばいいのか分からない - この仕様でAWS Lambdaにべったりベンダーロックインされたコードを書くのが嫌だ
- いざとなったら自前でサーバを立てたり、類似の別サービスに乗り換えたい
- 大規模なものを書くと詰みそう
- Express.js のようなルーターを使いたい
- TypeScript で書きたい
というあまり良くない開発体験になりがちです。
そこで Hono !
そこで、最近は直接ラムダのハンドラを書くのではなく、 Hono を使って HTTP 用のラムダを書いています。
import { Hono } from 'hono'
// 純粋なHTTPサーバの定義
const app = new Hono()
// Express.js のようなルーターを書ける
app.get('/echo', async (c) => {
return c.text("Hello")
})
export default app
import { handle } from 'hono/aws-lambda'
import app from './index.ts'
// index.ts で定義された純粋なHTTPサーバをAWS Lambda用のアダプタでラップしてハンドラとしてエクスポート
export const handler = handle(app)
src/index.ts
は純粋なHTTPサーバの定義で、 src/lambda.ts
はそのHTTPサーバを AWS Lambda 用のアダプタでラップしたものになっています。ラムダのエントリポイントとして登録するのは src/lambda.ts
になります。
ラムダを立てずに、ローカルでHTTPサーバを実行するには src/index.ts
を起動すればよく、以下のコマンドで起動できます。
(以降Bunを実行していますが、Node.jsでも設定を行えば基本的に実行可能です)
$ bun run --hot src/index.ts
Started server http://localhost:3000
$ curl http://localhost:3000/echo
Hello
逆にAWS Lambda上で上記のサーバを起動するには ESBuild などでバンドル&zip化した src/lambda.ts
をAWS LambdaにアップロードすればOKです。
# ESBuild でバンドル (外部のライブラリを参照していてもうまい具合に1ファイルにバンドルされる)
$ bun run esbuild --bundle \
--outfile=./dist/index.js \
--platform=node --target=node20 \
./src/lambda.ts
# zip化
$ zip -j lambda.zip dist/index.js
# zipをAWS Lambdaにアップロード
$ aws lambda update-function-code \
--zip-file fileb://lambda.zip \
--function-name my-lambda-function-on-aws
上記のコマンド・依存性の初期準備が難しい? 心配は無用です。各種JS処理系の create
コマンドでプロジェクトの雛形を作れるようになっています。
$ bun create hono .
create-hono version 0.5.0
✔ Using target directory … .
✔ Which template do you want to use? › aws-lambda
cloned honojs/starter#main to /Users/ksaitou/hono-aws-lambda
✔ Do you want to install project dependencies? … yes
✔ Which package manager do you want to use? › bun
✔ Installed project dependencies
その他、Honoには便利機能が満載です。
- Zod による型安全なリクエストのバリデーション機能、OpenAPI仕様の生成
- 卓越したパフォーマンス
-
Web標準に準拠したAPI
- ブラウザ上で動かすことも可能
- multipart/form-data への標準対応
- 環境間でポータブルな環境変数へのアクセスAPI
- などなど
AWS Lambda から Cloudflare Workers に夜逃げしてみる
では、実際にAWS Lambda用に作った前述のプロジェクトを Cloudflare Workers にアップロードして動かしてみたいと思います。
Cloudflare Workers のハンドラは以下のようなものなのですが、果たしてHonoはそのまま動くのでしょうか?
export default {
async fetch(request, env, ctx) {
return new Response('Hello World!');
},
}
その前にいくつか準備が必要です。
# Cloudflare の設定ファイル
name = "WorkerのID"
compatibility_date = "2023-12-01"
# Cloudflare 関連の依存性の追加
$ bun add -D wrangler @cloudflare/workers-types
準備が出来たのでデプロイしてみます。
$ bun run wrangler deploy --minify src/index.ts
⛅️ wrangler 3.36.0
-------------------
Total Upload: 18.93 KiB / gzip: 7.17 KiB
Uploaded WorkerのID (1.40 sec)
Published WorkerのID (0.30 sec)
https://WorkerのID.ksaitou.workers.dev
Current Deployment ID: WorkerのID
動かしてみましょう。
$ curl https://WorkerのID.ksaitou.workers.dev/echo
Hello
AWS Lambda 用に書いたサーバが Cloudflare Workers でもちゃんと動きました!
まとめ
Hono を使えばプラットフォーム可搬性のあるTypeScriptによるHTTPサーバを簡単に書くことができます。それぞれのプラットフォームの理解が難しいAPIに触れる必要もありません。
ちょっとした外部連携用のHTTPサーバを書く際など非常に便利なので使ってみましょう。
続編も書きました!
-
正直AWS内での広範な扱いを見ていると、AWSの便利なマクロ拡張関数といったほうが適切な気はします ↩