Spaces:
Runtime error
Runtime error
File size: 18,478 Bytes
23ac194 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 |
<h1 align="center">Serverless</h1>
Run serverless applications and REST APIs using your existing Fastify
application. By default, Fastify will not work on your serverless platform of
choice, you will need to make some small changes to fix this. This document
contains a small guide for the most popular serverless providers and how to use
Fastify with them.
#### Should you use Fastify in a serverless platform?
That is up to you! Keep in mind that functions as a service should always use
small and focused functions, but you can also run an entire web application with
them. It is important to remember that the bigger the application the slower the
initial boot will be. The best way to run Fastify applications in serverless
environments is to use platforms like Google Cloud Run, AWS Fargate, and Azure
Container Instances, where the server can handle multiple requests at the same
time and make full use of Fastify's features.
One of the best features of using Fastify in serverless applications is the ease
of development. In your local environment, you will always run the Fastify
application directly without the need for any additional tools, while the same
code will be executed in your serverless platform of choice with an additional
snippet of code.
### Contents
- [AWS](#aws)
- [Google Cloud Functions](#google-cloud-functions)
- [Google Firebase Functions](#google-firebase-functions)
- [Google Cloud Run](#google-cloud-run)
- [Netlify Lambda](#netlify-lambda)
- [Platformatic Cloud](#platformatic-cloud)
- [Vercel](#vercel)
## AWS
To integrate with AWS, you have two choices of library:
- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify)
which only adds API Gateway support but has heavy optimizations for fastify.
- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter)
which is a little slower as it creates an HTTP request for each AWS event but
has support for more AWS services such as: AWS SQS, AWS SNS and others.
So you can decide which option is best for you, but you can test both libraries.
### Using @fastify/aws-lambda
The sample provided allows you to easily build serverless web
applications/services and RESTful APIs using Fastify on top of AWS Lambda and
Amazon API Gateway.
#### app.js
```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;
}
```
When executed in your lambda function we do not need to listen to a specific
port, so we just export the wrapper function `init` in this case. The
[`lambda.js`](#lambdajs) file will use this export.
When you execute your Fastify application like always, i.e. `node app.js` *(the
detection for this could be `require.main === module`)*, you can normally listen
to your port, so you can still run your Fastify function locally.
#### lambda.js
```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);
```
We just require
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify) (make sure
you install the dependency `npm i @fastify/aws-lambda`) and our
[`app.js`](#appjs) file and call the exported `awsLambdaFastify` function with
the `app` as the only parameter. The resulting `proxy` function has the correct
signature to be used as a lambda `handler` function. This way all the incoming
events (API Gateway requests) are passed to the `proxy` function of
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify).
#### Example
An example deployable with
[claudia.js](https://claudiajs.com/tutorials/serverless-express.html) can be
found
[here](https://github.com/claudiajs/example-projects/tree/master/fastify-app-lambda).
### Considerations
- API Gateway does not support streams yet, so you are not able to handle
[streams](../Reference/Reply.md#streams).
- API Gateway has a timeout of 29 seconds, so it is important to provide a reply
during this time.
#### Beyond API Gateway
If you need to integrate with more AWS services, take a look at
[@h4ad/serverless-adapter](https://viniciusl.com.br/serverless-adapter/docs/main/frameworks/fastify)
on Fastify to find out how to integrate.
## Google Cloud Functions
### Creation of Fastify instance
```js
const fastify = require("fastify")({
logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'}
});
```
### Add Custom `contentTypeParser` to Fastify instance
As explained [in issue
#946](https://github.com/fastify/fastify/issues/946#issuecomment-766319521),
since the Google Cloud Functions platform parses the body of the request before
it arrives at the Fastify instance, troubling the body request in case of `POST`
and `PATCH` methods, you need to add a custom [`Content-Type
Parser`](../Reference/ContentTypeParser.md) to mitigate this behavior.
```js
fastify.addContentTypeParser('application/json', {}, (req, body, done) => {
done(null, body.body);
});
```
### Define your endpoint (examples)
A simple `GET` endpoint:
```js
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
})
```
Or a more complete `POST` endpoint with schema validation:
```js
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}!`
})
}
})
```
### Implement and export the function
Final step, implement the function to handle the request and pass it to Fastify
by emitting `request` event to `fastify.server`:
```js
const fastifyFunction = async (request, reply) => {
await fastify.ready();
fastify.server.emit('request', request, reply)
}
exports.fastifyFunction = fastifyFunction;
```
### Local test
Install [Google Functions Framework for
Node.js](https://github.com/GoogleCloudPlatform/functions-framework-nodejs).
You can install it globally:
```bash
npm i -g @google-cloud/functions-framework
```
Or as a development library:
```bash
npm i -D @google-cloud/functions-framework
```
Then you can run your function locally with Functions Framework:
```bash
npx @google-cloud/functions-framework --target=fastifyFunction
```
Or add this command to your `package.json` scripts:
```json
"scripts": {
...
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
...
}
```
and run it with `npm run dev`.
### Deploy
```bash
gcloud functions deploy fastifyFunction \
--runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated
```
#### Read logs
```bash
gcloud functions logs read
```
#### Example request to `/hello` endpoint
```bash
curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
-H "Content-Type: application/json" \
-d '{ "name": "Fastify" }'
{"message":"Hello Fastify!"}
```
### References
- [Google Cloud Functions - Node.js Quickstart
](https://cloud.google.com/functions/docs/quickstart-nodejs)
## Google Firebase Functions
Follow this guide if you want to use Fastify as the HTTP framework for
Firebase Functions instead of the vanilla JavaScript router provided with
`onRequest(async (req, res) => {}`.
### The onRequest() handler
We use the `onRequest` function to wrap our Fastify application instance.
As such, we'll begin with importing it to the code:
```js
const { onRequest } = require("firebase-functions/v2/https")
```
### Creation of Fastify instance
Create the Fastify instance and encapsulate the returned application instance
in a function which will register routes, await the server's processing of
plugins, hooks and other settings. As follows:
```js
const fastify = require("fastify")({
logger: true,
})
const fastifyApp = async (request, reply) => {
await registerRoutes(fastify)
await fastify.ready()
fastify.server.emit("request", request, reply)
}
```
### Add Custom `contentTypeParser` to Fastify instance and define endpoints
Firebase Function's HTTP layer already parses the request
and makes a JSON payload available. It also provides access
to the raw body, unparsed, which is useful in order to calculate
request signatures to validate HTTP webhooks.
Add as follows to the `registerRoutes()` function:
```js
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!'})
})
}
```
### Export the function using Firebase onRequest
Final step is to export the Fastify app instance to Firebase's own
`onRequest()` function so it can pass the request and reply objects to it:
```js
exports.app = onRequest(fastifyApp)
```
### Local test
Install the Firebase tools functions so you can use the CLI:
```bash
npm i -g firebase-tools
```
Then you can run your function locally with:
```bash
firebase emulators:start --only functions
```
### Deploy
Deploy your Firebase Functions with:
```bash
firebase deploy --only functions
```
#### Read logs
Use the Firebase tools CLI:
```bash
firebase functions:log
```
### References
- [Fastify on Firebase Functions](https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify/blob/main/package.json)
- [An article about HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy](https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy)
## Google Cloud Run
Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless
**container** environment. Its primary purpose is to provide an
infrastructure-abstracted environment to run arbitrary containers. As a result,
Fastify can be deployed to Google Cloud Run with little-to-no code changes from
the way you would write your Fastify app normally.
*Follow the steps below to deploy to Google Cloud Run if you are already
familiar with gcloud or just follow their
[quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*.
### Adjust Fastify server
In order for Fastify to properly listen for requests within the container, be
sure to set the correct port and address:
```js
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()
}
```
### Add a Dockerfile
You can add any valid `Dockerfile` that packages and runs a Node app. A basic
`Dockerfile` can be found in the official [gcloud
docs](https://github.com/knative/docs/blob/2d654d1fd6311750cc57187a86253c52f273d924/docs/serving/samples/hello-world/helloworld-nodejs/Dockerfile).
```Dockerfile
# 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" ]
```
### Add a .dockerignore
To keep build artifacts out of your container (which keeps it small and improves
build times) add a `.dockerignore` file like the one below:
```.dockerignore
Dockerfile
README.md
node_modules
npm-debug.log
```
### Submit build
Next, submit your app to be built into a Docker image by running the following
command (replacing `PROJECT-ID` and `APP-NAME` with your GCP project id and an
app name):
```bash
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
```
### Deploy Image
After your image has built, you can deploy it with the following command:
```bash
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
```
Your app will be accessible from the URL GCP provides.
## netlify-lambda
First, please perform all preparation steps related to **AWS Lambda**.
Create a folder called `functions`, then create `server.js` (and your endpoint
path will be `server.js`) inside the `functions` folder.
### functions/server.js
```js
export { handler } from '../lambda.js'; // Change `lambda.js` path to your `lambda.js` path
```
### netlify.toml
```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
**Do not forget to add this Webpack config, or else problems may occur**
```js
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
})
]
};
```
### Scripts
Add this command to your `package.json` *scripts*
```json
"scripts": {
...
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
...
}
```
Then it should work fine
## Platformatic Cloud
[Platformatic](https://platformatic.dev) provides zero-configuration deployment
for Node.js applications.
To use it now, you should wrap your existing Fastify application inside a
[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction),
by running the following:
```bash
npm create platformatic@latest -- service
```
The wizard would ask you to fill in a few answers:
```
? 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
```
Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in
with your GitHub account.
Create your first application and a static workspace: be careful to download the
API key as an env file, e.g. `yourworkspace.txt`.
Then, you can easily deploy your application with the following command:
```bash
platformatic deploy --keys `yourworkspace.txt`
```
Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service)
on how to wrap Fastify application in Platformatic.
## Vercel
[Vercel](https://vercel.com) provides zero-configuration deployment for Node.js
applications. To use it now, it is as simple as configuring your `vercel.json`
file like the following:
```json
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/serverless.js"
}
]
}
```
Then, write `api/serverless.js` like so:
```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);
}
```
In `src/app.js` define the plugin.
```js
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
export default routes;
```
|