メインコンテンツにスキップ
バージョン: 最新 (v5.0.x)

サーバーレス

既存の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

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

参考文献

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-IDAPP-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.jsonscriptsに追加します。

"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;