Honoを使ってAWS Lambda + API Gateway環境でAPI開発に使ってみた
ちょうど、T&Tでシステム連携のためのRESTful APIを実装する機会があったのですが、新規かつ、小さなものだったので、たまには使ったことがないものを使ってみようかなと思い、軽いと評判のHonoを使ってみることにしました。
- Hono
- AWS Lambda + API Gateway REST API(v1)
- v2じゃないのはAPIキーの仕組みを使いたかったため
- @hono/zod-openapiでOpen API DocとSwagger UIを出力
Honoとは、高速、軽量(zero dependencies)、マルチプラットフォーム(Cloudflare, Fastly, Deno, Bun, AWS Lambda, Node.jsなどで動作)、強力なTypeScriptサポートが特徴のモダンなウェブフレームワークです。
どのようなシステムで利用したか
今回、MEGURU CLUBという資源回収を基点としたコミュニティサービスむけに、APIを提供しました。システムの概要としては、資源回収ボックスにPP素材の資源(豆腐パックなど)を投入すると、チェックインをすることができ、チェックインしたことがある人に対して、Generative Artを使ったNFTを配布できるというものです。
Generative Artを使ったNFT。資源投入数が増えると花の数が増えていく
T&TはNFTを利用したシステムを開発するためのプラットフォームです。MEGURU CLUBはこのT&Tを使ってNFTを配布しているのですが、MEGURU CLUBの状態(資源回収=チェックイン回数など)を受け取って、T&Tのシステムに反映するためのAPIをHonoを使って実装しました。
APIの役割は、MEGURU CLUBのシステムから発行数、発行期間、NFTのメタデータ(今回は資源回収数など)などを受け取ると、DynamoDBにその内容を保存し、ブロックチェーンに必要な情報を書き込むジョブを起動するメッセージをSQSに送信するというものです。ジョブの処理が完了すると、訪問するとNFTを受け取ることができる使い捨てURLのウェブページ(mintページ)が取得できるようになります。
ブロックチェーン関連のシステムではありますが、APIがアクセスするのは、Lambda, DynamoDB, SQSなどのAWSのサービスなので、一般的なシステムと言えます。
感想
普段はexpressなどを使っているのですが、Honoになって違和感を感じることはありませんでした。性能を必要とするようなシステムではなかったので、性能面がどうだったのかは判断できませんが、開発体験はとても良かったです。
特に感心したのは、Middlewareと公式サイトのドキュメントで、新規でRESTful APIを作る際に、軽量フレームワークなのにほしいものがMiddlewareとしてまとめられていて、整理されたドキュメントから簡単に把握することができます。
プロジェクトを立ち上げて、zodの定義をもとに、リクエストとレスポンスに対するバリデーションとOpenAPIのドキュメント生成の入ったAPIがスムーズに作れるのはとてもありがたいです。
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
const app = new OpenAPIHono();
const RequestSchema = z.object({
seriesId: z.string().openapi({ description: "Series Id" }),
});
const ResponseSchema = z.object({
seriesId: z.string().openapi({ description: "Series Id" }),
mintUrl: z.string().openapi({ description: "..." }),
isMinted: z.boolean().openapi({ description: "..." }),
});
const route = createRoute({
method: "post",
path: "/v1/mint_pages",
tags: ["MintPage"],
request: {
body: {
content: {
"application/json": {
schema: RequestSchema,
},
},
required: true,
},
},
responses: {
200: {
content: {
"application/json": {
schema: ResponseSchema,
},
},
description: "Create the MintPage",
},
...
},
});
app.openapi(route, async (c) => {
const { seriesId } = await c.req.valid("json");
...
気をつけた点
今回は、AWS Lambda向けということもあってNode.jsアダプターを使って開発してみても良かったと思うのですが、せっかくなので wrangler環境で動かしていました。(今回は見送ったのですが、CloudFlareを導入することを検討していたというのもあります。)
wrangler環境ならではの話として、以下のあたりは気をつける必要があります。
まず、node:crypto
を使っているパッケージを利用していたので、wrangler.toml
でcompatibility_flags
を指定する必要がありました。
compatibility_flags = [ "nodejs_compat" ]
次に、AWS SDKを使っている場合、環境変数でアクセスキーなどを受け渡しているケースもあると思いますが、wranglerは環境変数を一旦、handlerのcontextに入るので、wrangler.toml
で[vars]
を指定して、handler内部で取り出して使うようにしていました。ただ、秘密情報を設定ファイルに書くのは嫌だなと思っていたら、.dev.vars
に書けるとWranglerのドキュメントに書いてあったので、そちらに書くようにしました。
APPNAME_AWS_ACCESS_KEY_ID=""
APPNAME_AWS_SECRET_ACCESS_KEY=""
AWS Lambda環境では、Roleベースのセッション情報(アクセスキーやセッションIDなどをLambdaが環境変数に入れてくれる)を使ってキーをAWSのクライアントに渡さないと思うので、ローカルの場合は明示的にキーを渡して、Lambda環境では渡さないというような場合分けが必要になります。
他にも、Cloudflare Workers environment don't have FileReader defined #4765のように互換性の問題でAWS SDKが動作しないケースに遭遇しました。
素直にNode.jsアダプターを使っておけば、何も考えずに済むのかなとは思いますし、Wrangler使っておけば、マルチプラットフォームで書くいい練習になるとも言えるかもしれないです。
終わりに
新規プロジェクトということもあって、しがらみもなくやりたいことをシュッと実現したかったので、Honoを使ったのは大満足でした。
最近は、HonoとDrizzle ORMを使ってAPIを開発しているので、その辺の感想も共有できたらと思っています。
Discussion