Serverless Framework を使用して Express/Fastify アプリを AWS Lambda にデプロイする
Min-jun Kim
Dev Intern · Leapcell

はじめに
今日のペースの速いクラウドネイティブ環境において、開発者は常に、より効率的、費用対効果が高く、スケーラブルな方法でアプリケーションを構築およびデプロイする方法を模索しています。従来のサーバーベースのデプロイメントでは、インフラストラクチャの管理、スケーリング、パッチ適用といったオーバーヘッドが伴うことがよくあります。サーバーレスコンピューティング、特に AWS Lambda は、サーバー管理を抽象化することで魅力的な代替手段を提供し、開発者がアプリケーションロジックのみに集中できるようにします。しかし、既存のモノリシックまたはマイクロサービスベースの Express または Fastify アプリケーションをサーバーレスアーキテクチャに移行することは、 daunting に思えるかもしれません。良いニュースは、常にコードベース全体を書き換える必要はないということです。この記事では、Serverless Framework を使用して、Express または Fastify アプリケーションを AWS Lambda に簡単にデプロイし、使い慣れた JavaScript フレームワークを放棄することなく、サーバーレスのメリットを解き放つ方法を探ります。
サーバーレス変革の理解
デプロイプロセスに入る前に、従来の Web アプリケーションがサーバーレスパラダイムにどのように適合するかを理解するために不可欠な、いくつかのコアコンセプトを明確にしましょう。
主要な用語
- AWS Lambda: サーバーのプロビジョニングまたは管理なしでコードを実行できるコンピューティングサービスです。消費したコンピューティング時間に対してのみ支払います。
- API Gateway: 開発者があらゆる規模の API を簡単に作成、公開、保守、監視、保護できるフルマネージドサービスです。Lambda で実行されるアプリケーションの「フロントドア」として機能します。
- Serverless Framework: AWS を含むさまざまなクラウドプロバイダーでサーバーレスアプリケーションを構築、デプロイ、管理するのに役立つオープンソース CLI ツールです。サーバーレスリソースの定義とデプロイを簡素化します。
- Lambda プロキシ統合: API Gateway の特定の統合タイプで、API Gateway がリクエスト全体を Lambda 関数にそのまま渡し、Lambda の応答全体をクライアントにそのまま返すことを可能にします。これは、HTTP リクエストおよびレスポンスオブジェクトへの直接アクセスを期待する Express や Fastify のようなフレームワークにとって不可欠です。
serverless-http
: HTTP フレームワーク (Express、Fastify など) と AWS Lambda のイベントオブジェクトの間のブリッジとして機能する軽量な Node.js モジュールです。Lambda イベント構造を、これらのフレームワークが期待する標準の Node.jshttp.IncomingMessage
およびhttp.ServerResponse
オブジェクトに適合させます。
仕組み
基本的な考え方は、Express または Fastify アプリケーションを AWS Lambda 関数内に「ラップ」することです。API Gateway 経由でリクエストが到着すると、Lambda 関数を単純なイベントで直接呼び出すのではなく、API Gateway で Lambda プロキシ統合を使用するように構成します。これは、API Gateway が生の HTTP リクエストの詳細 (ヘッダー、ボディ、メソッド、パス、クエリパラメータ) を JSON オブジェクトとして Lambda 関数に送信することを意味します。
Lambda 関数内では、serverless-http
ライブラリが重要な役割を果たします。このライブラリは、API Gateway プロキシイベントをインターセプトし、それを http.IncomingMessage
および http.ServerResponse
オブジェクトに変換してから、これらのオブジェクトを Express または Fastify アプリケーションのリクエストハンドラに渡します。アプリケーションは通常どおりリクエストを処理します。アプリケーションが応答を生成すると、serverless-http
がそれをキャプチャし、API Gateway が期待する形式 (statusCode、headers、body を含む JSON オブジェクト) に変換してから、API Gateway に返します。API Gateway はそれをクライアントに返します。
このアーキテクチャにより、既存の Express/Fastify ロジックをほぼ変更せずに実行でき、Lambda の自動スケーリング、実行ごとの支払いモデル、運用オーバーヘッドの削減といったメリットを享受できます。
実用的なデプロイメント例
Express アプリケーションを使用した例を見てみましょう。Fastify の場合も同様です。
1. プロジェクトのセットアップ
まず、新しい Node.js プロジェクトを初期化します。
mkdir express-lambda-app cd express-lambda-app npm init -y
2. 依存関係のインストール
Express と serverless-http
をインストールします。また、serverless
をグローバルまたはローカルにインストールします。
npm install express serverless-http npm install -g serverless # または npm install --save-dev serverless
3. Express アプリケーションの作成 (app.js
)
Express アプリケーションロジックを含む app.js
という名前のファイルを作成します。
// app.js const express = require('express'); const app = express(); const port = 3000; // Lambda ではこのポートは使用されませんが、ローカルテストには便利です app.use(express.json()); // application/json の解析用 app.get('/', (req, res) => { res.send('Hello from Express on Lambda!'); }); app.get('/users/:id', (req, res) => { const userId = req.params.id; res.json({ message: `Fetching user with ID: ${userId}` }); }); app.post('/data', (req, res) => { const data = req.body; res.json({ message: 'Data received', data: data }); }); // ローカルテスト用 if (process.env.NODE_ENV !== 'production') { app.listen(port, () => { console.log(`Local Express app listening at http://localhost:${port}`); }); } module.exports = app;
4. Lambda ハンドラを作成 (handler.js
)
このファイルには、serverless-http
が使用する実際の Lambda 関数が含まれます。
// handler.js const serverless = require('serverless-http'); const app = require('./app'); // Express アプリを serverless-http でラップします module.exports.handler = serverless(app);
5. Serverless の設定 (serverless.yml
)
これは、サーバーレスデプロイメントの中心となる部分です。serverless.yml
ファイルを作成します。
# serverless.yml service: express-lambda-app frameworkVersion: '3' provider: name: aws runtime: nodejs18.x # 適切な Node.js ランタイムを選択します region: us-east-1 # 希望する AWS リージョン memorySize: 128 # 最小メモリサイズ stage: dev # デプロイメントステージ (例: dev、prod) environment: # Lambda 関数用の環境変数 NODE_ENV: production apiGateway: # API Gateway が Lambda に生の要求を渡すための重要な設定 minimumCompressionSize: 1024 # 1KB より大きい応答に対して gzip 圧縮を有効にします functions: api: handler: handler.handler # handler.js とその export 'handler' を指します events: - http: # API Gateway HTTP エンドポイントを定義します path: / method: any # すべての HTTP メソッド (GET、POST、PUT、DELETE など) をキャッチします cors: true # このエンドポイントの CORS を有効にします - http: path: /{proxy+} method: any cors: true
serverless.yml
の説明:
service
: サーバーレスサービスの名称。frameworkVersion
: 使用されている Serverless Framework のバージョンを指定します。provider
: クラウドプロバイダー (この場合は AWS) を設定します。runtime
: Lambda 関数用の Node.js バージョン。region
: リソースがデプロイされる AWS リージョン。memorySize
: Lambda 関数に割り当てられるメモリ。stage
: デプロイメントステージ。environment
: Lambda 内からアクセス可能な環境変数。apiGateway
: API Gateway の特定の設定。minimumCompressionSize
はパフォーマンスに良いです。
functions
: AWS Lambda 関数を定義します。api
: Lambda 関数の名称。handler
: ファイルとエクスポート関数 (例:handler.js
のhandler
エクスポート) を指定します。events
: Lambda を API Gateway HTTP エンドポイントにリンクする場所です。- 2 つの
http
イベントは重要です。ルートパス/
用の 1 つと、後続のすべてのパス/{proxy+}
用のもう 1 つです。これにより、API Gateway へのすべてのリクエストが 1 つの Lambda 関数にルーティングされ、Express アプリで処理されることが保証されます。proxy+
は、ベースパス以降のすべてのパスをキャプチャするワイルドカードです。 method: any
により、Express アプリはすべての HTTP メソッドを処理できます。cors: true
は、クロスオリジンリソース共有を有効にし、フロントエンドアプリケーションにとってしばしば重要です。
- 2 つの
6. アプリケーションのデプロイ
最後に、Serverless Framework CLI を使用してアプリケーションをデプロイします。
serverless deploy
CLI は、コードをパッケージ化し、必要な AWS リソース (Lambda 関数、API Gateway エンドポイント、IAM ロール) を作成し、デプロイが成功すると API Gateway エンドポイント URL を提供します。
7. アプリケーションのテスト
デプロイ後、提供された API Gateway URL を介して Express アプリケーションにアクセスできます。
URL が https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
であると仮定します。
GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
は "Hello from Express on Lambda!" を返します。GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/123
は{"message": "Fetching user with ID: 123"}
を返します。POST https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/data
に{ "name": "Alice" }
のような JSON ボディを送信すると、{"message": "Data received", "data": {"name": "Alice"}}
が返されます。
Fastify アプリケーションの例
Fastify の場合も同様のプロセスです。
1. 依存関係のインストール
npm install fastify serverless-http # そして以前と同様に serverless
2. Express アプリケーションの作成 (fastify-app.js
)
// fastify-app.js const fastify = require('fastify'); const app = fastify({ logger: true }); // 開発体験向上のためにロガーを有効にします app.get('/', async (request, reply) => { return { message: 'Hello from Fastify on Lambda!' }; }); app.get('/users/:id', async (request, reply) => { const userId = request.params.id; return { message: `Fetching user with ID: ${userId}` }; }); app.post('/data', async (request, reply) => { const data = request.body; return { message: 'Data received', data: data }; }); // ローカルテスト用 if (process.env.NODE_ENV !== 'production') { app.listen({ port: 3000 }, (err, address) => { if (err) { app.log.error(err); process.exit(1); } console.log(`Local Fastify app listening at ${address}`); }); } module.exports = app;
3. Lambda ハンドラを作成 (fastify-handler.js
)
// fastify-handler.js const serverless = require('serverless-http'); const app = require('./fastify-app'); // Fastify アプリを serverless-http でラップします (最初に初期化されていることを確認してください) // 注意: serverless-http は、それを生成する関数だけでなく、Fastify インスタンスを必要とします。 // また、serverless-http に渡す前に Fastify の readyPromise を await する必要があります。 const handler = async (event, context) => { await app.ready(); // Fastify プラグインがロードされていることを確認します const serverlessHandler = serverless(app); return serverlessHandler(event, context); }; module.exports.handler = handler;
Fastify に関する重要な注意: serverless-http
は、すでに初期化されたアプリケーションインスタンスを期待します。Fastify の場合、serverless-http
がリクエストの処理を試みる前に、すべてのプラグインがロードされていることを確認するために、通常 await app.ready()
を呼び出す必要があります。これは、serverless(app)
を async
関数でラップすることによって処理されます。
4. Fastify 用の Serverless 設定 (serverless.yml
)
serverless.yml
の handler
パスを更新するだけです。
# serverless.yml (Fastify 用抜粋) functions: api: handler: fastify-handler.handler # fastify-handler.js とその export 'handler' を指します events: - http: path: / method: any cors: true - http: path: /{proxy+} method: any cors: true
その後、以前と同様に serverless deploy
します。
アプリケーションシナリオとベストプラクティス
このアプローチは、次のような場合に最適です。
- 既存の Web アプリケーションの移行: 完全な書き換えは不要です。
- 確立されたフレームワークを使用した API の構築: サーバーレス環境で Express または Fastify の強力な機能を利用します。
- 迅速なプロトタイピング: サーバーを管理せずに API を迅速にデプロイします。
- マイクロサービス: 各マイクロサービスは、Lambda 上の個別の Express/Fastify アプリとしてデプロイできます。
ベストプラクティス:
- Lambda のコールドスタートを考慮する:
serverless-http
は効率的ですが、複雑な Express/Fastify アプリケーションでもコールドスタートのペナルティが発生する可能性があります。パフォーマンスが重要なエンドポイントには、プロビジョニングされた同時実行を検討してください。 - 依存関係を最適化する: Serverless Framework プラグインを使用して
webpack
やesbuild
のようなツールで、必要な依存関係のみをバンドルし、デプロイパッケージのサイズとコールドスタート時間を削減します。 - 状態管理: Lambda 関数はステートレスであることを忘れないでください。永続的なデータストアには、DynamoDB、RDS、S3、または ElastiCache のような外部サービスを使用してください。
- ロギングと監視: CloudWatch をログとメトリクスに使用します。
- エラーハンドリング: Express/Fastify アプリ内で堅牢なエラーハンドリングを実装し、分散トレーシングには AWS X-Ray のようなサービスを検討してください。
- 環境変数:
serverless.yml
のenvironment
セクションまたは AWS Secrets Manager/Parameter Store を使用して、機密性の高い構成を管理します。
結論
Serverless Framework と serverless-http
を使用して Express または Fastify アプリケーションを AWS Lambda にデプロイすることは、サーバーレス採用への強力な道を提供します。これにより、開発者は、好みの JavaScript Web フレームワークを放棄することなく、Lambda のスケーラビリティ、コスト効率、運用のシンプルさを活用できます。統合メカニズムを理解し、ベストプラクティスに従うことで、最小限の労力で既存の、または新規のサーバーレス Web アプリケーションを正常に移行できます。このアプローチにより、開発者はインフラストラクチャを管理するのではなく、機能の構築に集中できるようになります。