You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					485 lines
				
				13 KiB
			
		
		
			
		
	
	
					485 lines
				
				13 KiB
			| 
											3 years ago
										 | <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 Lambda](#aws-lambda) | ||
|  | - [Google Cloud Functions](#google-cloud-functions) | ||
|  | - [Google Cloud Run](#google-cloud-run) | ||
|  | - [Netlify Lambda](#netlify-lambda) | ||
|  | - [Vercel](#vercel) | ||
|  | 
 | ||
|  | ## 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. | ||
|  | 
 | ||
|  | *Note: Using [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) | ||
|  | is just one possible way.* | ||
|  | 
 | ||
|  | ### 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(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('aws-lambda-fastify') | ||
|  | 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 | ||
|  | [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) (make sure | ||
|  | you install the dependency `npm i --save aws-lambda-fastify`) 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 | ||
|  | [aws-lambda-fastify](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. | ||
|  | 
 | ||
|  | ## 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 into 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) | ||
|  | } | ||
|  | 
 | ||
|  | export.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 --save-dev @google-cloud/functions-framework | ||
|  | ``` | ||
|  | 
 | ||
|  | Than 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 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 address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined | ||
|  | 
 | ||
|  |   try { | ||
|  |     const server = build() | ||
|  |     const address = await server.listen(port, address) | ||
|  |     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 install --only=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 | ||
|  | 
 | ||
|  | 
 | ||
|  | ## Vercel
 | ||
|  | 
 | ||
|  | [Vercel](https://vercel.com) provides zero-configuration deployment for Node.js | ||
|  | applications. In order 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")); | ||
|  | 
 | ||
|  | export default async (req, res) => { | ||
|  |     await app.ready(); | ||
|  |     app.server.emit('request', req, res); | ||
|  | } | ||
|  | ``` |