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.

265 lines
7.7 KiB

4 years ago
<h1 align="center">Serverless</h1>
Run serverless applications and REST APIs using your existing Fastify application.
### Contents
- [AWS Lambda](#aws-lambda)
- [Google Cloud Run](#google-cloud-run)
- [Zeit Now](#zeit-now)
### 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](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 don't need to listen to a specific port,
so we just export the wrapper function `init` in this case.
The [`lambda.js`](https://www.fastify.io/docs/latest/Serverless/#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
```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`](https://www.fastify.io/docs/latest/Serverless/#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](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 doesn't support streams yet, so you're not able to handle [streams](https://www.fastify.io/docs/latest/Reply/#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](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.
## Zeit Now
[now](https://zeit.co/home) 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:
```json
{
"version": 2,
"builds": [
{
"src": "api/serverless.js",
"use": "@now/node",
"config": {
"helpers": false
}
}
],
"routes": [
{ "src": "/.*", "dest": "/api/serverless.js"}
]
}
```
Then, write a `api/serverless.js` like so:
```js
'use strict'
const build = require('./index')
const app = build()
module.exports = async function (req, res) {
await app.ready()
app.server.emit('request', req, res)
}
```
And a `api/index.js` file:
```js
'use strict'
const fastify = require('fastify')
function build () {
const app = fastify({
logger: true
})
app.get('/', async (req, res) => {
const { name = 'World' } = req.query
req.log.info({ name }, 'hello world!')
return `Hello ${name}!`
})
return app
}
module.exports = build
```
Note that you'll need to use Node 10 by setting it in `package.json`:
```js
"engines": {
"node": "10.x"
},
```