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統合 - AWS Systems Manager Parameter Storeから設定を自動取得
- マルチゲートウェイ対応 - API Gateway V2、ALB、旧API Gatewayに対応
- BASE_PATH対応 - 既存のALBへのパスルーティングに便利
- SAM非依存 - SAMでデプロイするが、コードはCDKやTerraformでも使える
- TypeScript対応 - 型安全な開発環境
- esbuild統合 - 高速なビルドプロセス
- ゼロコールドスタート最適化 - 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;
}
}
}
パラメータ名の最後の部分が環境変数名になる仕組みだ。例えば:
- パラメータ:
/myapp/lambda/DATABASE_URL - 環境変数パス:
/myapp/lambda/ - 結果:
process.env.DATABASE_URLに値が設定される
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
注目すべきは以下の点だ:
- arm64アーキテクチャ - x86よりもコスト効率が良い
- esbuild統合 - ビルド時にAWS SDKを除外し、バンドルサイズを最小化
- 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のソースコードを見ると、イベントオブジェクトの構造からゲートウェイの種類を推測し、適切なレスポンス形式を返している。これは素晴らしい抽象化だ。
どのような場面で使えるか
私の経験から、このテンプレートが活きる場面をいくつか挙げる:
- マイクロサービスAPI - Lambda関数単位でスケールするRESTful API
- 既存インフラへの統合 - ALBの特定パス配下にAPIを追加
- コスト最適化 - 低トラフィックなAPIをサーバーレス化
- スパイクトラフィック対応 - 急激なアクセス増加にも自動スケール
- セキュアな設定管理 - Parameter Storeで機密情報を管理
特に、「ちょっとしたAPI」を作りたいが、「EC2やECSを立てるほどではない」という場面で重宝するだろう。
気づいた点と考察
良い点
- シンプルさ - 最小限の構成で実用的な機能を提供
- 実践的 - Parameter Store統合など、本番運用を見据えた設計
- 柔軟性 - SAM、CDK、Terraformどれでもデプロイ可能
- パフォーマンス - コールドスタート最適化、esbuildによる高速ビルド
- 開発体験 - ローカルで普通のNode.jsサーバーとして動く
注意点
- sam local非対応 - ローカルでLambda環境を完全に再現はできない(2024/06時点)
- ステートレス - Lambda関数は本質的にステートレス。永続化にはDBが必要
- コールドスタート - 頻繁にアクセスがない場合、初回リクエストに遅延が生じる
- 実行時間制限 - Lambda関数は最大15分で終了。長時間処理には向かない
しかし、これらは「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対応など、実務で必要になる機能が過不足なく揃っている。
さて、また一つ面白いツールを私の道具箱に加えることになりそうだ。
興味を持った読者は、ぜひ自分の手で試してみることをお勧めする。きっと新しい発見があるはずだ。
参考リンク
検証環境
本記事で使用したパッケージのバージョン:
- Node.js: 22.20.0
- hono: 4.10.3
- @hono/node-server: 1.19.5
- @aws-sdk/client-ssm: 3.918.0
- esbuild: 0.21.5
- tsx: 4.20.6