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
						
					
					
				| <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);
 | |
| }
 | |
| ```
 |