Serverless

Run serverless applications and REST APIs using your existing Fastify application.

Contents

Attention Readers:

Fastify is not designed to run on serverless environments. The Fastify framework is designed to make implementing a traditional HTTP/S server easy. Serverless environments requests differently than a standard HTTP/S server; thus, we cannot guarantee it will work as expected with Fastify. Regardless, based on the examples given in this document, it is possible to use Fastify in a serverless environment. Again, keep in mind that this is not Fastify’s intended use case and we do not test for such integration scenarios.

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 is just one possible way.

app.js

  1. const fastify = require('fastify');
  2. function init() {
  3. const app = fastify();
  4. app.get('/', (request, reply) => reply.send({ hello: 'world' }));
  5. return app;
  6. }
  7. if (require.main === module) {
  8. // called directly i.e. "node app"
  9. init().listen(3000, (err) => {
  10. if (err) console.error(err);
  11. console.log('server listening on 3000');
  12. });
  13. } else {
  14. // required as a module => executed on aws lambda
  15. module.exports = init;
  16. }

When executed in your lambda function we don’t need to listen to a specific port, so we just export the wrapper function init in this case. The lambda.js 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

  1. const awsLambdaFastify = require('aws-lambda-fastify')
  2. const init = require('./app');
  3. const proxy = awsLambdaFastify(init())
  4. // or
  5. // const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
  6. exports.handler = proxy;
  7. // or
  8. // exports.handler = (event, context, callback) => proxy(event, context, callback);
  9. // or
  10. // exports.handler = (event, context) => proxy(event, context);
  11. // or
  12. // exports.handler = async (event, context) => proxy(event, context);

We just require aws-lambda-fastify (make sure you install the dependency npm i --save aws-lambda-fastify) and our app.js 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 lambda handler function. This way all the incoming events (API Gateway requests) are passed to the proxy function of aws-lambda-fastify.

Example

An example deployable with claudia.js can be found here.

Considerations

  • API Gateway doesn’t support streams yet, so you’re not able to handle streams.
  • API Gateway has a timeout of 29 seconds, so it’s important to provide a reply during this time.

Google Cloud Run

Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless container environment. It’s 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.

Adjust Fastfiy server

In order for Fastify to properly listen for requests within the container, be sure to set the correct port and address:

  1. function build() {
  2. const fastify = Fastify({ trustProxy: true })
  3. return fastify
  4. }
  5. async function start() {
  6. // Google Cloud Run will set this environment variable for you, so
  7. // you can also use it to detect if you are running in Cloud Run
  8. const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
  9. // You must listen on the port Cloud Run provides
  10. const port = process.env.PORT || 3000
  11. // You must listen on all IPV4 addresses in Cloud Run
  12. const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
  13. try {
  14. const server = build()
  15. const address = await server.listen(port, address)
  16. console.log(`Listening on ${address}`)
  17. } catch (err) {
  18. console.error(err)
  19. process.exit(1)
  20. }
  21. }
  22. module.exports = build
  23. if (require.main === module) {
  24. start()
  25. }

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.

  1. # Use the official Node.js 10 image.
  2. # https://hub.docker.com/_/node
  3. FROM node:10
  4. # Create and change to the app directory.
  5. WORKDIR /usr/src/app
  6. # Copy application dependency manifests to the container image.
  7. # A wildcard is used to ensure both package.json AND package-lock.json are copied.
  8. # Copying this separately prevents re-running npm install on every code change.
  9. COPY package*.json ./
  10. # Install production dependencies.
  11. RUN npm install --only=production
  12. # Copy local code to the container image.
  13. COPY . .
  14. # Run the web service on container startup.
  15. 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:

  1. Dockerfile
  2. README.md
  3. node_modules
  4. 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):

  1. 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:

  1. 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 do all preparation steps related to AWS Lambda.

Create folder called functions then create server.js (and your endpoint path will be server.js) inside functions folder.

functions/server.js

  1. export { handler } from '../lambda.js'; // Change `lambda.js` path to your `lambda.js` path

netlify.toml

  1. [build]
  2. # This will be run the site build
  3. command = "npm run build:functions"
  4. # This is the directory is publishing to netlify's CDN
  5. # and this is directory of your front of your app
  6. # publish = "build"
  7. # functions build directory
  8. functions = "functions-build" # always appends `-build` folder to your `functions` folder for builds

webpack.config.netlify.js

Don’t forget add this Webpack config, else a lot of problems can occur

  1. const nodeExternals = require('webpack-node-externals');
  2. const dotenv = require('dotenv-safe');
  3. const webpack = require('webpack');
  4. const env = process.env.NODE_ENV || 'production';
  5. const dev = env === 'development';
  6. if (dev) {
  7. dotenv.config({ allowEmptyValues: true });
  8. }
  9. module.exports = {
  10. mode: env,
  11. devtool: dev ? 'eval-source-map' : 'none',
  12. externals: [nodeExternals()],
  13. devServer: {
  14. proxy: {
  15. '/.netlify': {
  16. target: 'http://localhost:9000',
  17. pathRewrite: { '^/.netlify/functions': '' }
  18. }
  19. }
  20. },
  21. module: {
  22. rules: []
  23. },
  24. plugins: [
  25. new webpack.DefinePlugin({
  26. 'process.env.APP_ROOT_PATH': JSON.stringify('/'),
  27. 'process.env.NETLIFY_ENV': true,
  28. 'process.env.CONTEXT': env
  29. })
  30. ]
  31. };

Scripts

Add this command to your package.json scripts

  1. "scripts": {
  2. ...
  3. "build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
  4. ...
  5. }

Then it should work fine

Vercel

Vercel provides zero configuration deployment for Node.js applications. In order to use now, it is as simple as configuring your now.json file like the following:

  1. {
  2. "version": 2,
  3. "builds": [
  4. {
  5. "src": "api/serverless.js",
  6. "use": "@now/node",
  7. "config": {
  8. "helpers": false
  9. }
  10. }
  11. ],
  12. "routes": [
  13. { "src": "/.*", "dest": "/api/serverless.js"}
  14. ]
  15. }

Then, write a api/serverless.js like so:

  1. 'use strict'
  2. const build = require('./index')
  3. const app = build()
  4. module.exports = async function (req, res) {
  5. await app.ready()
  6. app.server.emit('request', req, res)
  7. }

And a api/index.js file:

  1. 'use strict'
  2. const fastify = require('fastify')
  3. function build () {
  4. const app = fastify({
  5. logger: true
  6. })
  7. app.get('/', async (req, res) => {
  8. const { name = 'World' } = req.query
  9. req.log.info({ name }, 'hello world!')
  10. return `Hello ${name}!`
  11. })
  12. return app
  13. }
  14. module.exports = build

Note that you’ll need to use Node 10 by setting it in package.json:

  1. "engines": {
  2. "node": "10.x"
  3. },