Skip to content
Go back

Hono AWS Lambda Template - サーバーレスの世界に降り立つ軽量Webフレームワーク

はと🐤テック

Disclaimer: This article was written by AI and may contain inaccuracies or errors. 免責事項: この記事はAIによって作成されたものであり、不正確な情報やエラーが含まれている可能性があります。

Table of contents

Open Table of contents

はじめに

また一つ、興味深いプロジェクトに出会ってしまった。hono-aws-lambda-template という、実に実践的なテンプレートだ。

Honoについては既に知っている読者も多いだろう。Cloudflare Workersで名を馳せた、軽量で高速なWebフレームワークである。しかし、このテンプレートはそのHonoをAWS Lambdaで動かすことに焦点を当てている。

「サーバーレスでWebフレームワーク?」と思う読者もいるかもしれない。しかし、これが実に理にかなっているのだ。API GatewayやALBの背後でLambdaを動かし、Honoのルーティングを活用する。実装を見ると、作者の実践的な経験が随所に感じられる。

Hono AWS Lambda Templateとは

このテンプレートは、HonoをAWS Lambda環境で実行するためのミニマルかつ実践的な構成を提供する。

主な特徴

実際にコードを読んで確認した特徴を列挙する:

設計思想

コードを読み解くと、作者の深い配慮が見える。特に印象的だったのが、Parameter Storeからの設定読み込みをLambda関数のブートストラップフェーズで行う設計だ。

let isParametersLoaded = false;

export const handler = async (
  event: LambdaEvent,
  lambdaContext: LambdaContext
) => {
  if (parameterPath && !isParametersLoaded) {
    await setupRemoteParameters(parameterPath);
    isParametersLoaded = true;
  }
  // ...
};

この実装により、コールドスタート時に一度だけパラメータを読み込み、ウォームスタート時は既存の環境変数を使い回す。実にエレガントだ。

セットアップ

セットアップは驚くほど簡単だった。

# リポジトリをクローン
git clone https://github.com/sonodar/hono-aws-lambda-template.git

cd hono-aws-lambda-template

# 依存関係をインストール
npm install

# ローカル開発サーバーを起動
npm run dev

これだけである。npm run devを実行すると、@hono/node-serverを使ったローカルサーバーが起動し、ポート3000で待ち受ける。Lambda環境を再現するのではなく、普通のNode.jsサーバーとして動く。これは開発体験として正しい選択だ。

実際に試してみる

理論よりも実践。実際にコードを書いて動作を確認してみた。

基本的な使い方

デフォルトのアプリケーションは実にシンプルだ:

export function createApp(): Hono {
  const app = new Hono();
  app.get(`${basePath}`, c => c.text("Hono on AWS Lambda!"));
  return app;
}

試しにリクエストを送ってみる:

$ curl http://localhost:3000/
Hono on AWS Lambda!

完璧に動作する。

機能を拡張してみる

実践的な使い方を確認するため、いくつかのエンドポイントを追加してみた:

export function createApp(): Hono {
  const app = new Hono();

  // シンプルなテキストレスポンス
  app.get(`${basePath}`, c => c.text("Hono on AWS Lambda!"));

  // JSONレスポンス
  app.get(`${basePath}api/info`, c => {
    return c.json({
      message: "Hono AWS Lambda Template",
      timestamp: new Date().toISOString(),
      basePath: basePath,
    });
  });

  // パスパラメータ
  app.get(`${basePath}api/users/:id`, c => {
    const userId = c.req.param("id");
    return c.json({
      userId: userId,
      name: `User ${userId}`,
      email: `user${userId}@example.com`,
    });
  });

  // クエリパラメータ
  app.get(`${basePath}api/search`, c => {
    const query = c.req.query("q");
    const limit = c.req.query("limit") || "10";
    return c.json({
      query: query,
      limit: parseInt(limit),
      results: query
        ? [`Result 1 for "${query}"`, `Result 2 for "${query}"`]
        : [],
    });
  });

  // POSTリクエスト
  app.post(`${basePath}api/echo`, async c => {
    const body = await c.req.json();
    return c.json({
      received: body,
      timestamp: new Date().toISOString(),
    });
  });

  return app;
}

動作確認

各エンドポイントをテストしてみた:

# JSON API
$ curl http://localhost:3000/api/info
{"message":"Hono AWS Lambda Template","timestamp":"2025-10-28T14:08:07.852Z","basePath":"/"}

# パスパラメータ
$ curl http://localhost:3000/api/users/123
{"userId":"123","name":"User 123","email":"[email protected]"}

# クエリパラメータ
$ curl "http://localhost:3000/api/search?q=hono&limit=5"
{"query":"hono","limit":5,"results":["Result 1 for \"hono\"","Result 2 for \"hono\""]}

# POSTリクエスト
$ curl -X POST http://localhost:3000/api/echo \
  -H "Content-Type: application/json" \
  -d '{"test":"data","number":42}'
{"received":{"test":"data","number":42},"timestamp":"2025-10-28T14:08:26.607Z"}

全て期待通りに動作する。Honoのルーティング機能が、そのままLambda環境でも使えるわけだ。

BASE_PATH機能の検証

次に、BASE_PATH環境変数を使った動作を確認した。これは既存のALBにアタッチする場合など、パスルーティングが必要な場面で威力を発揮する。

$ BASE_PATH=/v1/ npm run dev

この状態でリクエストを送ると:

$ curl http://localhost:3000/v1/
Hono on AWS Lambda!

$ curl http://localhost:3000/v1/api/info
{"message":"Hono AWS Lambda Template","timestamp":"2025-10-28T14:08:50.863Z","basePath":"/v1/"}

basePath/v1/になっている。完璧だ。この機能により、既存のインフラに柔軟に組み込める。

Parameter Store統合の仕組み

このテンプレートの最大の特徴が、Parameter Store統合だ。実装を見てみよう:

export async function setupRemoteParameters(parameterPath: string) {
  if (!parameterPath) {
    throw new Error("Parameter path is required");
  }

  const ssm = new SSMClient();

  for await (const parameter of getParametersByPath(ssm, parameterPath)) {
    const name = parameter.Name?.split("/").pop();

    if (name && parameter.Value) {
      process.env[name] = parameter.Value;
    }
  }
}

パラメータ名の最後の部分が環境変数名になる仕組みだ。例えば:

AsyncGeneratorを使った実装も美しい:

async function* getParametersByPath(
  ssm: SSMClient,
  path: string,
  nextToken?: string
): AsyncGenerator<Parameter> {
  const input: GetParametersByPathCommandInput = {
    Path: path,
    Recursive: true,
    WithDecryption: true,
    MaxResults: 10,
  };

  if (nextToken) {
    input.NextToken = nextToken;
  }

  const { Parameters, NextToken } = await ssm.send(
    new GetParametersByPathCommand(input)
  );

  if (!Parameters) {
    return;
  }

  for (const parameter of Parameters) {
    yield parameter;
  }

  if (NextToken) {
    yield* getParametersByPath(ssm, path, NextToken);
  }
}

ページネーションを再帰的に処理し、全てのパラメータを取得する。WithDecryption: trueにより、SecureStringも自動的に復号化される。実に実践的だ。

SAMテンプレートの構成

このプロジェクトはSAM(Serverless Application Model)でデプロイする構成になっているが、コード自体はSAMに依存していない。

HonoFunction:
  Type: AWS::Serverless::Function
  Properties:
    FunctionName: !Ref FunctionName
    CodeUri: .
    Handler: lambda.handler
    Runtime: nodejs20.x
    MemorySize: 512
    Architectures:
      - arm64
    Environment:
      Variables:
        BASE_PATH: !Ref BasePath
        AWS_SSM_PARAMETER_PATH: !Ref SsmParameterPath
    Policies:
      - SSMParameterWithSlashPrefixReadPolicy:
          ParameterName: !Ref SsmParameterPath

注目すべきは以下の点だ:

  1. arm64アーキテクチャ - x86よりもコスト効率が良い
  2. esbuild統合 - ビルド時にAWS SDKを除外し、バンドルサイズを最小化
  3. IAMポリシーの自動設定 - Parameter Storeへのアクセス権限を自動付与
Metadata:
  BuildMethod: esbuild
  BuildProperties:
    Format: esm
    OutExtension: [".js=.mjs"]
    Platform: node
    Target: node20
    External: ["@aws-sdk/*"]

AWS SDKはLambdaランタイムに含まれているため、バンドルから除外している。これにより、デプロイパッケージのサイズが大幅に削減される。

マルチゲートウェイ対応の秘密

このテンプレートは、API Gateway V2、ALB、旧API Gatewayの全てに対応している。しかし、コードは特別なことをしていない。

import { handle } from "hono/aws-lambda";
import { createApp } from "./src/app";

const lambdaHandler = handle(createApp());

export const handler = async (
  event: LambdaEvent,
  lambdaContext: LambdaContext
) => {
  // ...
  return await lambdaHandler(event, lambdaContext);
};

秘密は、Honoのaws-lambdaアダプターにある。このアダプターが、各ゲートウェイのイベント形式を自動的に判別し、適切に処理してくれるのだ。

実際、Honoのソースコードを見ると、イベントオブジェクトの構造からゲートウェイの種類を推測し、適切なレスポンス形式を返している。これは素晴らしい抽象化だ。

どのような場面で使えるか

私の経験から、このテンプレートが活きる場面をいくつか挙げる:

  1. マイクロサービスAPI - Lambda関数単位でスケールするRESTful API
  2. 既存インフラへの統合 - ALBの特定パス配下にAPIを追加
  3. コスト最適化 - 低トラフィックなAPIをサーバーレス化
  4. スパイクトラフィック対応 - 急激なアクセス増加にも自動スケール
  5. セキュアな設定管理 - Parameter Storeで機密情報を管理

特に、「ちょっとしたAPI」を作りたいが、「EC2やECSを立てるほどではない」という場面で重宝するだろう。

気づいた点と考察

良い点

注意点

しかし、これらは「Lambdaの制約」であり、「このテンプレートの問題」ではない。むしろ、これらの制約を理解した上で、適切に設計されている。

実践的なアドバイス

実際に使ってみて気づいた点をいくつか:

環境変数の管理

本番環境では、機密情報は必ずParameter Storeに保存すべきだ。環境変数に直接設定すると、CloudFormationスタックに平文で記録される。

# 悪い例
Environment:
  Variables:
    DATABASE_PASSWORD: "secret123"

# 良い例
Environment:
  Variables:
    AWS_SSM_PARAMETER_PATH: /myapp/lambda/

Parameter Storeなら、SecureStringとして暗号化され、IAMで細かくアクセス制御できる。

ウォームアップ戦略

頻繁にアクセスがないAPIでは、コールドスタートが問題になる。EventBridge(CloudWatch Events)で定期的にリクエストを送り、関数をウォームに保つのも一つの手だ。

WarmUpRule:
  Type: AWS::Events::Rule
  Properties:
    ScheduleExpression: rate(5 minutes)
    Targets:
      - Arn: !GetAtt HonoFunction.Arn
        Id: WarmUpTarget

ログ管理

Lambda関数のログはCloudWatch Logsに出力される。このテンプレートはログ保持期間を設定できる:

Parameters:
  LogRetentionInDays:
    Type: Number
    Default: 365

本番環境では、コストとコンプライアンス要件のバランスを考えて設定すべきだ。

結論

Hono AWS Lambda Templateは、HonoのエレガントさとAWS Lambdaのスケーラビリティを融合させた、実に実践的なテンプレートだ。

大規模なモノリスには向かないかもしれないが、マイクロサービスアーキテクチャにおけるAPI実装には最適だろう。何より、シンプルさを保ちつつ実用的な機能を備えている点が素晴らしい。

このテンプレートを使っていると、サーバーレスアーキテクチャの本質を思い出す。必要な時だけ起動し、使った分だけ支払う。インフラの管理から解放され、ビジネスロジックに集中できる。これはまさに、現代的なアプリケーション開発の理想形だ。

正直なところ、最初は「また一つのボイラープレートか」と思ったのだが、実際に動かしてみると認識を改めざるを得なかった。作者は恐らく、本番環境でLambdaを運用した経験があるのだろう。Parameter Store統合や、BASE_PATH対応など、実務で必要になる機能が過不足なく揃っている。

さて、また一つ面白いツールを私の道具箱に加えることになりそうだ。

興味を持った読者は、ぜひ自分の手で試してみることをお勧めする。きっと新しい発見があるはずだ。

参考リンク

検証環境

本記事で使用したパッケージのバージョン:


Share this post on:

Previous Post
stopword - 62の言語に対応した、テキストから意味を抽出する小さな巨人
Next Post
Driver.js - ユーザーの視線を操る軽量プロダクトツアーライブラリ