無伺服器
使用您現有的 Fastify 應用程式來執行無伺服器應用程式和 REST API。預設情況下,Fastify 無法在您選擇的無伺服器平台上運作,您需要進行一些小變更才能修正此問題。本文檔包含一個針對最熱門的無伺服器供應商以及如何將 Fastify 與它們一起使用的小型指南。
您應該在無伺服器平台上使用 Fastify 嗎?
這取決於您!請記住,作為服務的功能應始終使用小型且重點明確的功能,但您也可以使用它們來執行整個 Web 應用程式。重要的是要記住,應用程式越大,初始啟動速度就會越慢。在無伺服器環境中執行 Fastify 應用程式的最佳方法是使用 Google Cloud Run、AWS Fargate 和 Azure Container Instances 等平台,這些平台可以同時處理多個請求並充分利用 Fastify 的功能。
在無伺服器應用程式中使用 Fastify 的最佳功能之一是易於開發。在您的本機環境中,您將始終直接執行 Fastify 應用程式,而無需任何額外工具,同時相同的程式碼將在您選擇的無伺服器平台中執行,並帶有一小段額外的程式碼。
目錄
- AWS
- Google Cloud Functions
- Google Firebase Functions
- Google Cloud Run
- Netlify Lambda
- Platformatic Cloud
- Vercel
AWS
要與 AWS 整合,您有兩個程式庫可選擇
- 使用 @fastify/aws-lambda,它只新增 API Gateway 支援,但針對 fastify 進行了大量最佳化。
- 使用 @h4ad/serverless-adapter,它速度稍慢,因為它為每個 AWS 事件建立一個 HTTP 請求,但支援更多 AWS 服務,例如:AWS SQS、AWS SNS 等。
因此,您可以決定哪種選項最適合您,但您可以測試這兩個程式庫。
使用 @fastify/aws-lambda
提供的範例讓您可以輕鬆地使用 Fastify 在 AWS Lambda 和 Amazon API Gateway 之上建置無伺服器 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
檔案,並使用 app
作為唯一參數來呼叫匯出的 awsLambdaFastify
函式。產生的 proxy
函式具有正確的簽名,可作為 lambda handler
函式使用。這樣,所有傳入的事件 (API Gateway 請求) 都會傳遞到 @fastify/aws-lambda 的 proxy
函式。
範例
在 claudia.js 中可部署的範例,可以在這裡找到。
考量事項
- API Gateway 尚不支援串流,因此您無法處理串流。
- API Gateway 的逾時時間為 29 秒,因此在此時間內提供回覆非常重要。
超越 API Gateway
如果您需要與更多 AWS 服務整合,請查看 Fastify 上的 @h4ad/serverless-adapter,以了解如何整合。
Google Cloud Functions
建立 Fastify 執行個體
const fastify = require("fastify")({
logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'}
});
將自訂 contentTypeParser
新增至 Fastify 執行個體
如 問題 #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;
本機測試
安裝 適用於 Node.js 的 Google Functions Framework。
您可以全域安裝它
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
如果您想將 Fastify 用作 Firebase Functions 的 HTTP 架構,而不是 onRequest(async (req, res) => {}
提供的原生 JavaScript 路由器,請遵循本指南。
onRequest() 處理常式
我們使用 onRequest
函式來包裝我們的 Fastify 應用程式執行個體。
因此,我們將從將其匯入到程式碼開始
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)
}
將自訂 contentTypeParser
新增至 Fastify 執行個體並定義端點
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)
本機測試
安裝 Firebase 工具函式,以便您可以使用 CLI
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 可以部署到 Google Cloud Run,而程式碼的變更幾乎與您正常編寫 Fastify 應用程式的方式相同。
如果您已熟悉 gcloud,或者只想按照他們的快速入門指南操作,請按照以下步驟部署到 Google Cloud Run。.
調整 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
您可以新增任何有效的 Dockerfile
,用於打包和執行 Node 應用程式。一個基本的 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
提交建置
接下來,執行以下命令(將 PROJECT-ID
和 APP-NAME
替換為您的 GCP 專案 ID 和應用程式名稱),將您的應用程式提交以建置為 Docker 映像檔。
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
的資料夾,然後在 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: 'http://localhost: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 金鑰下載為環境檔案,例如 yourworkspace.txt
。
然後,您可以使用以下命令輕鬆部署您的應用程式。
platformatic deploy --keys `yourworkspace.txt`
請查看關於如何在 Platformatic 中包裝 Fastify 應用程式的完整指南。
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;