サーバーレス
既存のFastifyアプリケーションを使用して、サーバーレスアプリケーションとREST APIを実行します。 デフォルトでは、Fastifyは選択したサーバーレスプラットフォームでは動作しません。これを修正するには、いくつかの小さな変更を加える必要があります。 このドキュメントには、最も一般的なサーバーレスプロバイダーと、それらでFastifyを使用する方法に関する簡単なガイドが含まれています。
サーバーレスプラットフォームでFastifyを使用する必要がありますか?
それはあなた次第です! Function as a Serviceは常に小さく焦点を絞った関数を使用する必要がありますが、それらを使用してWebアプリケーション全体を実行することもできます。 アプリケーションが大きくなればなるほど、初期起動が遅くなることを覚えておくことが重要です。 サーバーレス環境でFastifyアプリケーションを実行する最良の方法は、Google Cloud Run、AWS Fargate、Azure Container Instancesなどのプラットフォームを使用することです。これらのプラットフォームでは、サーバーが複数のリクエストを同時に処理し、Fastifyの機能を最大限に活用できます。
サーバーレスアプリケーションでFastifyを使用する最大の利点の1つは、開発の容易さです。 ローカル環境では、追加のツールを必要とせずに常にFastifyアプリケーションを直接実行しますが、追加のコードスニペットを使用して、選択したサーバーレスプラットフォームで同じコードが実行されます。
目次
- AWS
- Google Cloud Functions
- Google Firebase Functions
- Google Cloud Run
- Netlify Lambda
- Platformatic Cloud
- Vercel
AWS
AWSと統合するには、2つのライブラリを選択できます
- API Gatewayのサポートのみを追加しますが、fastify用に高度に最適化されている@fastify/aws-lambdaを使用します。
- AWSイベントごとにHTTPリクエストを作成するため少し遅くなりますが、AWS SQS、AWS SNSなどのより多くのAWSサービスをサポートする@h4ad/serverless-adapterを使用します。
そのため、どちらのオプションが最適かを決めることができますが、両方のライブラリをテストできます。
@fastify/aws-lambdaを使用する
提供されているサンプルを使用すると、AWS LambdaとAmazon API Gateway上にFastifyを使用して、サーバーレスWebアプリケーション/サービスとRESTful APIを簡単に構築できます。
app.js
const fastify = require('fastify');
function init() {
const app = fastify();
app.get('/', (request, reply) => reply.send({ hello: 'world' }));
return app;
}
if (require.main === module) {
// called directly i.e. "node app"
init().listen({ port: 3000 }, (err) => {
if (err) console.error(err);
console.log('server listening on 3000');
});
} else {
// required as a module => executed on aws lambda
module.exports = init;
}
lambda関数で実行する場合、特定のポートをリッスンする必要がないため、この場合はラッパー関数`init`をエクスポートするだけです。 `lambda.js`ファイルはこのエクスポートを使用します。
Fastifyアプリケーションをいつものように実行する場合、つまり`node app.js`(これを検するには`require.main === module`を使用できます)、通常どおりポートをリッスンできるため、Fastify関数をローカルで実行できます。
lambda.js
const awsLambdaFastify = require('@fastify/aws-lambda')
const init = require('./app');
const proxy = awsLambdaFastify(init())
// or
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
exports.handler = proxy;
// or
// exports.handler = (event, context, callback) => proxy(event, context, callback);
// or
// exports.handler = (event, context) => proxy(event, context);
// or
// exports.handler = async (event, context) => proxy(event, context);
@fastify/aws-lambda(依存関係`npm i @fastify/aws-lambda`をインストールしてください)と`app.js`ファイルをrequireし、エクスポートされた`awsLambdaFastify`関数を`app`を唯一のパラメーターとして呼び出します。 結果の`proxy`関数は、lambda`handler`関数として使用するための正しいシグネチャを持っています。 これにより、すべての受信イベント(API Gatewayリクエスト)が@fastify/aws-lambdaの`proxy`関数に渡されます。
例
claudia.jsでデプロイ可能な例はこちらにあります。
考慮事項
- API Gatewayはまだストリームをサポートしていないため、ストリームを処理できません。
- API Gatewayのタイムアウトは29秒であるため、この時間内に返信することが重要です。
API Gatewayを超えて
より多くのAWSサービスと統合する必要がある場合は、@h4ad/serverless-adapter on Fastifyを参照して、統合方法を確認してください。
Google Cloud Functions
Fastifyインスタンスの作成
const fastify = require("fastify")({
logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'}
});
Fastifyインスタンスにカスタム`contentTypeParser`を追加する
issue #946で説明されているように、Google Cloud FunctionsプラットフォームはFastifyインスタンスに到着する前にリクエストの本文を解析するため、`POST`および`PATCH`メソッドの場合に本文のリクエストに問題が発生します。この動作を軽減するには、カスタム`Content-Type Parser`を追加する必要があります。
fastify.addContentTypeParser('application/json', {}, (req, body, done) => {
done(null, body.body);
});
エンドポイントを定義する(例)
簡単な`GET`エンドポイント
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
})
または、スキーマ検証を備えたより完全な`POST`エンドポイント
fastify.route({
method: 'POST',
url: '/hello',
schema: {
body: {
type: 'object',
properties: {
name: { type: 'string'}
},
required: ['name']
},
response: {
200: {
type: 'object',
properties: {
message: {type: 'string'}
}
}
},
},
handler: async (request, reply) => {
const { name } = request.body;
reply.code(200).send({
message: `Hello ${name}!`
})
}
})
関数を実装してエクスポートする
最後の手順として、リクエストを処理し、`request`イベントを`fastify.server`に発行することでFastifyに渡す関数を実装します
const fastifyFunction = async (request, reply) => {
await fastify.ready();
fastify.server.emit('request', request, reply)
}
exports.fastifyFunction = fastifyFunction;
ローカルテスト
Google Functions Framework for Node.jsをインストールします。
グローバルにインストールできます
npm i -g @google-cloud/functions-framework
または、開発ライブラリとして
npm i -D @google-cloud/functions-framework
その後、Functions Frameworkを使用して関数をローカルで実行できます
npx @google-cloud/functions-framework --target=fastifyFunction
または、このコマンドを`package.json`スクリプトに追加します
"scripts": {
...
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
...
}
そして、`npm run dev`で実行します。
デプロイ
gcloud functions deploy fastifyFunction \
--runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated
ログを読む
gcloud functions logs read
`/hello`エンドポイントへのリクエスト例
curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
-H "Content-Type: application/json" \
-d '{ "name": "Fastify" }'
{"message":"Hello Fastify!"}
参考文献
Google Firebase Functions
`onRequest(async (req, res) => {}`で提供されるバニラJavaScriptルーターの代わりに、Firebase FunctionsのHTTPフレームワークとしてFastifyを使用する場合、このガイドに従ってください。
onRequest()ハンドラー
Fastifyアプリケーションインスタンスをラップするには、`onRequest`関数を使用します。
そのため、コードにインポートすることから始めます
const { onRequest } = require("firebase-functions/v2/https")
Fastifyインスタンスの作成
Fastifyインスタンスを作成し、返されたアプリケーションインスタンスを、ルートを登録し、サーバーのプラグイン、フック、その他の設定の処理を待つ関数にカプセル化します。 次のとおりです
const fastify = require("fastify")({
logger: true,
})
const fastifyApp = async (request, reply) => {
await registerRoutes(fastify)
await fastify.ready()
fastify.server.emit("request", request, reply)
}
Fastifyインスタンスにカスタム`contentTypeParser`を追加し、エンドポイントを定義する
Firebase FunctionのHTTPレイヤーはすでにリクエストを解析し、JSONペイロードを利用できるようにしています。 また、解析されていない生の本文へのアクセスも提供し、これはHTTP webhookを検証するためのリクエスト署名を計算するのに役立ちます。
`registerRoutes()`関数に次のように追加します
async function registerRoutes (fastify) {
fastify.addContentTypeParser("application/json", {}, (req, payload, done) => {
// useful to include the request's raw body on the `req` object that will
// later be available in your other routes so you can calculate the HMAC
// if needed
req.rawBody = payload.rawBody
// payload.body is already the parsed JSON so we just fire the done callback
// with it
done(null, payload.body)
})
// define your endpoints here...
fastify.post("/some-route-here", async (request, reply) => {}
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
})
}
Firebase onRequestを使用して関数をエクスポートする
最後の手順は、FastifyアプリインスタンスをFirebase独自の`onRequest()`関数にエクスポートして、リクエストと返信オブジェクトを渡せるようにすることです
exports.app = onRequest(fastifyApp)
ローカルテスト
CLIを使用できるように、Firebaseツール関数をインストールします
npm i -g firebase-tools
次に、を使用して関数をローカルで実行できます
firebase emulators:start --only functions
デプロイ
を使用してFirebase Functionsをデプロイします
firebase deploy --only functions
ログを読む
FirebaseツールCLIを使用します
firebase functions:log
参考文献
- Firebase Functions上のFastify
- Firebase FunctionsとFastifyでのHTTP Webhookに関する記事:Lemon Squeezyを用いた実践的なケーススタディ
Google Cloud Run
AWS LambdaやGoogle Cloud Functionsとは異なり、Google Cloud Runはサーバーレスの**コンテナ**環境です。その主な目的は、任意のコンテナを実行するためのインフラストラクチャ抽象化環境を提供することです。そのため、Fastifyは、通常のFastifyアプリケーションの記述方法からほとんど、あるいは全くコードを変更することなく、Google Cloud Runにデプロイできます。
gcloudに精通している場合は、以下の手順に従ってGoogle Cloud Runにデプロイするか、Google Cloudのクイックスタートに従ってください。.
Fastifyサーバーの調整
Fastifyがコンテナ内でリクエストを適切にリッスンするには、正しいポートとアドレスを設定してください。
function build() {
const fastify = Fastify({ trustProxy: true })
return fastify
}
async function start() {
// Google Cloud Run will set this environment variable for you, so
// you can also use it to detect if you are running in Cloud Run
const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
// You must listen on the port Cloud Run provides
const port = process.env.PORT || 3000
// You must listen on all IPV4 addresses in Cloud Run
const host = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
try {
const server = build()
const address = await server.listen({ port, host })
console.log(`Listening on ${address}`)
} catch (err) {
console.error(err)
process.exit(1)
}
}
module.exports = build
if (require.main === module) {
start()
}
Dockerfileの追加
Nodeアプリをパッケージ化して実行する有効なDockerfile
を追加できます。基本的なDockerfile
は、公式のgcloudドキュメントにあります。
# Use the official Node.js 10 image.
# https://hub.docker.com/_/node
FROM node:10
# Create and change to the app directory.
WORKDIR /usr/src/app
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./
# Install production dependencies.
RUN npm i --production
# Copy local code to the container image.
COPY . .
# Run the web service on container startup.
CMD [ "npm", "start" ]
.dockerignoreの追加
ビルドアーティファクトをコンテナから除外する(コンテナのサイズを小さくし、ビルド時間を短縮する)には、以下のような.dockerignore
ファイルを追加します。
Dockerfile
README.md
node_modules
npm-debug.log
ビルドの送信
次に、次のコマンドを実行して、アプリをDockerイメージにビルドして送信します(PROJECT-ID
とAPP-NAME
をGCPプロジェクトIDとアプリ名に置き換えます)。
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
イメージのデプロイ
イメージがビルドされたら、次のコマンドでデプロイできます。
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
アプリは、GCPが提供するURLからアクセスできます。
netlify-lambda
まず、**AWS Lambda**に関するすべての前準備手順を実行してください。
functions
というフォルダを作成し、その中にserver.js
を作成します(エンドポイントパスはserver.js
になります)。
functions/server.js
export { handler } from '../lambda.js'; // Change `lambda.js` path to your `lambda.js` path
netlify.toml
[build]
# This will be run the site build
command = "npm run build:functions"
# This is the directory is publishing to netlify's CDN
# and this is directory of your front of your app
# publish = "build"
# functions build directory
functions = "functions-build" # always appends `-build` folder to your `functions` folder for builds
webpack.config.netlify.js
このWebpack設定を追加することを忘れないでください。そうしないと、問題が発生する可能性があります。
const nodeExternals = require('webpack-node-externals');
const dotenv = require('dotenv-safe');
const webpack = require('webpack');
const env = process.env.NODE_ENV || 'production';
const dev = env === 'development';
if (dev) {
dotenv.config({ allowEmptyValues: true });
}
module.exports = {
mode: env,
devtool: dev ? 'eval-source-map' : 'none',
externals: [nodeExternals()],
devServer: {
proxy: {
'/.netlify': {
target: 'https://#:9000',
pathRewrite: { '^/.netlify/functions': '' }
}
}
},
module: {
rules: []
},
plugins: [
new webpack.DefinePlugin({
'process.env.APP_ROOT_PATH': JSON.stringify('/'),
'process.env.NETLIFY_ENV': true,
'process.env.CONTEXT': env
})
]
};
スクリプト
このコマンドをpackage.json
のscriptsに追加します。
"scripts": {
...
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
...
}
これで正常に動作するはずです。
Platformatic Cloud
Platformaticは、Node.jsアプリケーションのゼロコンフィギュレーションデプロイを提供します。今すぐ使用するには、以下を実行して、既存のFastifyアプリケーションをPlatformatic Service内にラップする必要があります。
npm create platformatic@latest -- service
ウィザードは、いくつかの質問に答えるように求めます。
? Where would you like to create your project? .
? Do you want to run npm install? yes
? Do you want to use TypeScript? no
? What port do you want to use? 3042
[13:04:14] INFO: Configuration file platformatic.service.json successfully created.
[13:04:14] INFO: Environment file .env successfully created.
[13:04:14] INFO: Plugins folder "plugins" successfully created.
[13:04:14] INFO: Routes folder "routes" successfully created.
? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no
? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no
次に、Platformatic Cloudにアクセスし、GitHubアカウントでサインインします。最初のアプリケーションと静的ワークスペースを作成します。APIキーをenvファイル(例:yourworkspace.txt
)としてダウンロードするように注意してください。
その後、次のコマンドでアプリケーションを簡単にデプロイできます。
platformatic deploy --keys `yourworkspace.txt`
FastifyアプリケーションをPlatformaticにラップする方法については、完全ガイドをご覧ください。
Vercel
Vercelは、Node.jsアプリケーションのゼロコンフィギュレーションデプロイを提供します。今すぐ使用するには、以下のようにvercel.json
ファイルを構成するだけです。
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/serverless.js"
}
]
}
次に、api/serverless.js
を次のように記述します。
"use strict";
// Read the .env file.
import * as dotenv from "dotenv";
dotenv.config();
// Require the framework
import Fastify from "fastify";
// Instantiate Fastify with some config
const app = Fastify({
logger: true,
});
// Register your application as a normal plugin.
app.register(import("../src/app.js"));
export default async (req, res) => {
await app.ready();
app.server.emit('request', req, res);
}
src/app.js
でプラグインを定義します。
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
export default routes;