
多観点で比較する Lambdalith vs 単一目的 Lambda
こんにちは、リテールアプリ共創部の戸田駿太です。
AWS 関連のコミュニティイベント「品川会」の LT で「多観点で比較する Lambdalith vs 単一目的 Lambda」というタイトルで発表しました。
AWS で API を作るとき、Lambda を エンドポイントごとに分ける か 1 つにまとめる かという分かれ道があります。前者は API Gateway がルーティングを担う AWS 公式推奨の形、後者は Lambda の中で Hono などのフレームワークがルーティングする Lambdalith と呼ばれる形です。
実際に両方の構成の案件を担当したので、7 つの観点で比べてみました。この記事ではその LT の内容をスライドと一緒にまとめます。
この LT で比べる 2 つの構成
比較するのは、どちらも CDK + TypeScript で作った実際の案件の構成です。

A: 単一目的 Lambda(single-purpose function)
- パスごとに Lambda を置いて、API Gateway がルーティングする構成
- AWS の Well-Architected Serverless Lens が推奨している形
- いわば「教科書どおり」のやり方です
B: Lambdalith
- 1 つの Lambda の中で Hono が全パスをルーティングする構成
- AWS 公式のガイドではアンチパターンとして扱われている
- ただしコミュニティでは近年評価が変わってきています
実際 Yan Cui 氏 は 2018 年には分割派でしたが、2025 年には「既存 Web アプリの移行には最適」と立場を変えています。
以降は、この 2 つを「A」「B」と呼んで観点ごとに見ていきます。
構成A: 単一目的 Lambda

API Gateway の Resource / Method がルーティングを担当し、パスごとに Lambda が並びます。
ハンドラは APIGatewayProxyEvent を受け取る形です。
ルーティングの責務が AWS 側(インフラ側)にあるのがこの構成の特徴です。
構成B: Lambdalith

入口は API Gateway でも CloudFront + Lambda Function URL でも構いません。
重要なのは、行き先が 1 つの Lambda で、その中で Hono が全パスをルーティングすることです。
つまり ルーティングの責務がアプリケーション側にある構成ですね。
この違いが、これから見る 7 つの観点すべてに効いてきます。
観点1: コールドスタート・スケール

最初はいちばん気になるであろうコールドスタートです。
A: 小さい関数がたくさん
- バンドルが小さいので 1 回のコールドスタートは速い
- ただし 関数ごとにコールドスタートが起きる
- 低トラフィックだと全関数がコールド状態になります
- ルートごとに 同時実行数の枠を予約できる(Reserved concurrency)
- スケール上限(10 秒ごとに 1,000 実行環境)もルートごとに持てる
B: 大きい関数が 1 つ
- 関数が大きくなる分、1 回のコールドスタートは長い
- ただし esbuild + Hono の構成なら軽量なので実害は小さいです
- 1 関数にトラフィックが集中するので 発生頻度は下がる
- ルート単位でスロットルできず、スケール上限を全ルートで共有する
「Lambdalith はコールドスタートが遅い」とよく言われますが、1 回あたりの長さと発生頻度はトレードオフの関係にあります。
低〜中トラフィックなら B が不利とは限りません。急スパイクやルート別の制御が必要なら A、その要件がなければ B で問題ないというのが結論です。
観点2: 各種機能をどちらの役割にするか

個人的にはここが両者を分ける一番本質的な違いだと思っています。
A: 「Lambda を起動せずに」ルート単位で効かせる
- Authorizer(Cognito / Lambda / IAM)で起動前に 401/403 を返せる
- Request Validator(JSON Schema)で起動前に 400 を返せる
- メソッド単位のスロットル / 使用量プラン / API Key / キャッシュ
- Lambda を通さず DynamoDB や SQS を直接呼ぶ サービス統合も可能
- 弾いたリクエストは Lambda の課金も実行もされない
B: 「Lambda の中で」ルート単位に効かせる
- ルート別の認証はミドルウェアで できる(
app.use("/admin/*", jwt())) - Zod 検証 / ロール認可 / CORS / エラー整形 / アクセスログも Hono の守備範囲
- ただし 判定は必ず Lambda 起動後に実行される
- 認証に失敗するリクエストでも Lambda が起動し、同時実行数を 1 つ消費して課金が発生します
- 手前で弾くなら CloudFront + WAF や API Gateway の
{proxy+}+ Authorizer- ただしこの場合は全 API に一律で効く形になります
つまり「ルート別に認証を変えたい」だけなら B でもできます。
「Lambda を起動せずに」弾きたいという要件があるときに A が必要になる、という整理が正確です。
観点3: 権限(IAM)

ここは素直に A が有利な観点です。
A: 関数ごとに最小権限
- 1 関数 = 1 ロールなので、権限を関数単位で絞れます
- 仮に侵害されても 影響範囲はそのルートだけに閉じます
- Well-Architected が単一目的 Lambda を推す理由そのものですね
B: 1 つのロールが全ルートの権限を持つ
- DB の Secret 読み取り、S3 の read/write、メール配信 API の Secret、Cognito の AdminCreateUser……といった権限が 1 つのロールに集まります
- 最小権限の粒度が「ルート単位」から 「API 単位」に粗くなる
サービス単位の最小権限で十分という考え方もありますが、粒度が粗くなること自体は事実です。
「API 単位で許容できるか」が Lambdalith を選べるかの判断基準になると思います。
観点4: コードの保守性・ローカル実行・テスト

逆にここは B が明確に有利です。
A: ハンドラが AWS のイベント形式に依存する
APIGatewayProxyEventを受け取ってAPIGatewayProxyResultを返す形です- ローカル実行には 別の仕組みが必要(SAM local / LocalStack / 手製イベント JSON など)
- 本番環境には関係ない依存と処理が開発フローに入ってきます
- API 設計書は別で用意する必要がある
- ECS などに移行しようとすると 全ハンドラを書き直すことになる
B: Hono は AWS に依存していない
// lambda.ts
import { handle } from "hono/aws-lambda";
const app = createApp({ ...deps });
export const handler = handle(app);
// index.ts(ローカル)
import { serve } from "@hono/node-server";
const app = createApp({ ...deps });
serve({ fetch: app.fetch, port: 8080 });
ポイントは以下です。
- AWS を知っているのは
hono/aws-lambdaを import している 1 ファイルだけ - ローカル実行は Hono の仕組みで完結(
pnpm dev→localhost:8080) - Hono のエコシステムがそのまま使える
- 担当案件では Zod OpenAPI・Swagger UI・Hono RPC を利用しています
- Zod スキーマから OpenAPI 定義が生成されて Swagger UI が配信されるので、API 設計書を別で作る必要がありません
- コードはほぼそのまま ECS などに乗り換えることも可能
Lambda を「ただの実行環境」として扱えるのが Lambdalith の強みだと感じています。
観点5: ルーティングの表現力

ルーティングを Hono に任せると、実際どこまで表現できるのかを整理しました。
| 材料 | API Gateway REST | Hono |
|---|---|---|
| パスパラメータ | /users/{cardId} |
/users/:cardId |
| 貪欲マッチ | {proxy+}(末尾のみ) |
*(どこでも) |
| 正規表現パラメータ | ❌ | :id{[0-9]+} |
| オプショナルパラメータ | ❌(別リソース定義) | /posts/:id? |
| クエリ / ヘッダーで分岐 | ❌ 手段なし | c.req.query() / header() で分岐 |
| param / query / body の検証 | Request Validator(JSON Schema) | Zod c.req.valid("query") |
結論としては API Gateway でできて Hono でできないルーティング条件はありません。
むしろ正規表現パラメータやオプショナルパラメータ、クエリ / ヘッダーでの分岐は Hono だけができることです。
観点6: CDK の書き方

CDK のコードにどう表れるかを比べます。
A はルーティング表が CDK にあります。
// エンドポイントごとに Lambda 関数を1つ作る
const createUserHandler = new NodejsFunction(this, "CreateUser", {
entry: "src/handlers/create-user.ts", ...
});
// /users を作り、POST に専用ハンドラを割り当てる
const users = api.root.addResource("users");
users.addMethod("POST",
new LambdaIntegration(createUserHandler),
{ authorizer, ... });
// /users/me を作り、GET に別のハンドラを割り当てる
const me = users.addResource("me");
me.addMethod("GET",
new LambdaIntegration(getUserHandler),
{ authorizer, ... });
// ...これがエンドポイント数だけ続く
エンドポイントの数だけこれが続きます。
つまり CDK のコードに API のパスとメソッドの一覧が書かれている状態です。
B は CDK にパスもメソッドも出てきません。
this.handler = new NodejsFunction(this, "Handler", {
entry: "apps/api/src/lambda.ts",
runtime: lambda.Runtime.NODEJS_22_X,
vpc, environment: { ... },
});
new apigateway.LambdaRestApi(this, "Api", {
handler: this.handler,
proxy: true, // ANY /{proxy+} → 全部この Lambda へ
});
// おわり
CDK が知っているのは「全リクエストを 1 つの Lambda に流す」ということだけです。
エンドポイントを追加してもインフラの差分はゼロになります。
エンドポイントを 1 本増やす作業が、A では「ハンドラ + CDK」、B では「ハンドラ」だけで済むという違いですね。
観点7: CloudFormation のリソース数

意外と見落とされがちですが、実務でいちばん困ったのがここでした。
A: 1 本増やすたびに一式が増える
エンドポイントを 1 本追加すると、以下のリソースがまとめて増えます。
Lambda::FunctionIAM::Role/IAM::PolicyLogs::LogGroupApiGateway::Method/ApiGateway::ResourceLambda::Permission- 監視用の
MetricFilter/Alarm(数個)
数えていくと 1 API あたり十数リソースになります。エンドポイント数だけ線形に増えていくわけです。
B: 最初の 1 式だけで増えない
同じ一式が最初に 1 回だけ作られます。ルーティングはコード側にあるので、何 API 増えてもリソースは増えません。
これが効いてくるのが CloudFormation の制限です。

CloudFormation は 1 スタックあたり 500 リソース という上限があります。A は 1 API あたり十数リソースなので、数十 API で到達します。
回避策は NestedStack への分割ですが、これをやると API の分類がスタック境界に漏れ出します。
CDK のコードに、本来アプリケーション領域であるはずの API のルーティング情報が散らばってしまうわけです。
B ではエンドポイントが何本増えてもリソースが増えないので、そもそもこの問題が起きません。
観点別の比較まとめ
7 つの観点を一覧にするとこうなります。

| 観点 | A: 単一目的 Lambda | B: Lambdalith | 有利 |
|---|---|---|---|
| 1 コールドスタート・スケール | 1回速いが頻度多い / ルート単位で制御 | 1回長いが頻度少ない / 全ルート共有 | 条件次第 |
| 2 各種機能の役割 | Lambda 起動前に効かせられる | Lambda 起動後(コード側) | A |
| 3 権限(IAM) | ルート単位の最小権限 | API 単位で粗くなる | A |
| 4 保守性・ローカル・テスト | 実行に別の仕組みが要る | Hono だけで完結 | B |
| 5 ルーティングの表現力 | パス + メソッドのみ | 正規表現 / クエリ分岐 / Zod | B |
| 6 CDK の書き方 | ルーティング表が CDK に | パスが出てこない | B |
| 7 CFn リソース数 | 1 API ごとに十数個増える | API が増えても増えない | B |
個人的な結論としては、
- インフラ層で効かせたい要件(ルート別の認証・スロットル・最小権限)があるなら A
- その要件がなければ、開発体験・保守性とリソース数で優位な B で始めるのがおすすめ
です。
A が有利な 2・3 はどちらも「インフラ層でどこまで制御したいか」という同じ軸の話です。
一方 B が有利な 4〜7 は「アプリケーション開発のしやすさ」に集約されます。
この 2 つを天秤にかける、というのが実際の判断だと思います。
おまけ: Lambda Web Adapter の位置づけ

LT では時間が余ったとき用に用意していた話です。
Lambda Web Adapter は awslabs(AWS 公式 org)が出しているツールです。
- Express / Next.js / Spring Boot / FastAPI といった Web アプリを 無改修で 1 つの Lambda に載せる
- AWS の SA の方も builders.flash や ServerlessDays で積極的に紹介している
これは実態として Lambdalith を作るためのツールですよね。
つまり 公式ガイドは「アンチパターン」と言いつつ、AWS 自身が Lambdalith 用のツールを出しているという状況です。
Hono のアダプタ(hono/aws-lambda)と比べるとこうなります。
| Hono アダプタ | Lambda Web Adapter | |
|---|---|---|
| handler | handle(app) が必要 |
不要(HTTP サーバをそのまま) |
| 移植性 | Node / Workers / Deno | コンテナごと ECS / EKS へ |
| コールドスタート | 軽い | +数十ms、初期化 10 秒制限 |
おまけ: Lambdalith に API Gateway は必要か

もう 1 つのおまけです。
ルーティングをアプリ側に寄せた時点で、API Gateway の仕事は「プロキシ素通し」だけになります。
残る API Gateway の価値は、認可 / スロットル / 使用量プラン / ルート別メトリクス / WebSocket といったところです。
それが不要なら CloudFront + Lambda Function URL で足ります。
- API Gateway の 29 秒制限からも解放される
- API Gateway の費用がかからない
- 担当案件では Function URL を AWS_IAM 認証にして、CloudFront の OAC で署名する構成にしています
逆にそれらが必要なら、API Gateway の {proxy+} → Hono という形でも「ルーティングはアプリ側」は守れます。
**主張は「Function URL を使え」ではなく「ルーティングをアプリに寄せろ」**ということです。
CDK にパスが出てきたら、ルーティングを AWS に任せているサインだと思っています。
まとめ
単一目的 Lambda と Lambdalith を 7 つの観点で比較しました。
A(単一目的 Lambda)が有利な点
- 認証・検証を Lambda 起動前に効かせられる
- IAM 権限をルート単位で絞れる
B(Lambdalith)が有利な点
- ローカル実行とテストが Hono だけで完結する
- ルーティングの表現力が高い
- CDK にパスが出てこない
- CFn リソースが増えない
コールドスタートは「1 回の長さ」と「発生頻度」のトレードオフなので、条件次第という結論でした。
AWS 公式がアンチパターンと呼んでいることもあって身構えてしまいがちですが、実際に両方を担当してみると どちらが優れているかではなく、インフラ層で効かせたい要件があるかどうかで決まる、というのが実感です。
Lambdalith を検討している方の参考になれば幸いです。
参考
The pros and cons of Lambdalith(Yan Cui)
AWS Lambda Web Adapter
Hono - AWS Lambda




