🔥

Honoを使ってAWS Lambda + API Gateway環境でAPI開発に使ってみた

2024/07/26に公開

ちょうど、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
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.tomlcompatibility_flagsを指定する必要がありました。

wrangler.toml
compatibility_flags = [ "nodejs_compat" ]

次に、AWS SDKを使っている場合、環境変数でアクセスキーなどを受け渡しているケースもあると思いますが、wranglerは環境変数を一旦、handlerのcontextに入るので、wrangler.toml[vars]を指定して、handler内部で取り出して使うようにしていました。ただ、秘密情報を設定ファイルに書くのは嫌だなと思っていたら、.dev.varsに書けるとWranglerのドキュメントに書いてあったので、そちらに書くようにしました。

.dev.vars
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