Time to Start Fastify

Getting Started

Install

1
yarn add fastify

First Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Require the framework and instantiate it
const fastify = require('fastify')()

// Declare a route
fastify.get('/', (req, reply) => {
reply.send({
hello: 'world',
})
})

// Run the server
fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})

Schema Serialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const fastify = require('fastify')()

const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}

// Declare a route with an output shcema
fastify.get('/', opts, (req, reply) => {
replysend({ hello: 'world' })
})

fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})

Register

Register routes in seperate files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// server.js
const fastify = require('fastify')()

fastify.register(require('./route'))

const opts = {
hello: 'world',
something: true,
}

fastify.register([
require('./another-route'),
require('./yet-another-route'),
], opts, (err) => {
if (err) throw err
})

fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})
1
2
3
4
5
6
7
// route.js
module.exports = (fastify, options, next) {
fastify.get('/', (req, reply) => {
replysend({ hello: 'world' })
})
next()
}

or async/await:

1
2
3
4
5
module.exports = async (fastify, options) => {
fastify.get('/', (req, reply) => {
replysend({ hello: 'world' })
})
}

Server Methods

server

fastify.server: The Node Core server object.

ready

Function called when all the plugins has been loaded. It takes an error parameter if something went wrong.

1
fastify.ready(err => { if (err) throw err })

listen

Starts the server on the given port after all the plugins loaded, internally waits for the .ready() event. The callback is the same as the Node Core.

1
2
3
fastify.listen(3000, err => {
if (err) throw err
})

Specifying an address is also supported:

1
2
3
fastify.listen(3000, '127.0.0.1', err => {
if (err) throw err
})

route

Method to add routes to the server, it also have shorthands.

routes iterator

The fastify instance is an Iterable object with all the registered routes. The route properties are the same the developer has declared.

close

fastify.close(callback), call this function to close the server instance and run the onClose hook.

decorate*

Function useful if you need to decorate the fastify instance, Reply or Request.

register

Fastify allows the user to extends its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever.

use

Function to add middlewares to Fastify.

addHook

Function to add a specific hook in the lifecycle of Fastify.

logger

The logger instance.

Inject

Fake http injection(for testing purpose).

setSchemaCompiler

Set the schema compiler for all routes.

setNotFoundHandler

fastify.setNotFoundHandler(handler(req, reply)): set the 404 handler. This call is fully encapsulated, so different plugins can set different not found handlers.

setErrorHandler

fastify.setErrorHandler(handler(err, reply)): set a function that will be called whenever an error happens. The handler is fully encapsulated, so different plugins can set different error handlers.

Routes

1
fastify.route(options)
  • method: currently it supports DELETE, GET, HEAD, PATCH, POST, PUT, and OPTIONS, it could be an array of methods.

  • url: the path of the url to match this route (alias: path)

  • schema: an object containing the schemas for the request and response. They need to be in JSON Schema format.

    • body: validates the body of the request if it is a POST or PUT.

    • querystring: validates the querystring. This can be a complete JSON Schema Object, with the property type of object and properties object of parameters, or simply the values of what would be contained in the properties object as shown below.

    • params: validates the params.

    • response: fitler and generate a schema for the response, setting a schema allows us to have 10-20% more throughput.

  • beforeHandler(req, res, done): a function called just before the request handler, useful if you need to perform authentication at route level for example, it could also be and array of functions.

  • handler(req, reply): the function that will handle this request.

  • request if defined in Request.

  • reply is defined in Reply.

Exmaple

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
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' },
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
},
},
handler: function (req, reply) {
reply.send({
hello: 'world',
})
}
})

fastify.route({
method: 'GET',
url: '/',
schema: {/*..*/},
beforeHandler: function (req, reply, done) {
// auth
done()
},
handler: function(req, reply) {
reply.send({
hello: 'world',
})
}
})

ShortHands

fastify.method(path, [options], handler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const opts = {
schema: {
response: {
200: {
type: 'object',
properites: {
hello: { type: 'string' }
}
}
}
}
}

fastify.get('/', opts, (req, reply) => {
reply.send({
hello: 'world',
})
})

fastify.all(path, [options], handler) will add the same handler to all the supported methods.

Async/Await

1
2
3
4
5
fastify.get('/', options, async (req, reply) => {
var data = await getData()
var processed = await processData(data)
return processed
})

Route Prefixing

1
2
3
4
5
6
7
8
9
10
11
12
// server.js
const fastify = require('fastify')()

fastify.register(require('./routes/v1/users'), {
prefix: '/v1',
})

// routes/v1/users
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v1)
next()
}

Now your client can access to /v1/user.

Be aware that if you use fastify-plugin this option won’t work.

Logging

Logging is disabled by default, and you can enable it by passing { logger: true } or { logger: { level: 'info' } } when you create the fastify instance.

1
2
3
4
5
6
7
8
9
10
const fastify = require('fastify')({
logger: true,
})

fastify.get('/', options, (req, reply) => {
req.log.info('Some info about the current request')
reply.send({
hello: 'world',
})
})

Middlewares

Fastify provides out of the box an asynchronous middleare engine compatible with Express and Restify middlewares.

Fastify middleware don’t support the full syntax middleware(err, req, res, next) because error handling is done inside Fastify.

Also if you are using a middleware taht bundles different, smaller middlewares such as helmet, we recommand to use the single module to get better performances.

1
2
3
4
5
6
7
fastify.use(require('cors')())
fastify.use(require('dns-prefetch-control')())
fastify.use(require('frameguard')())
fastify.use(require('hide-powered-by')())
fastify.use(require('hsts')())
fastify.use(require('ienoopen')())
fastify.use(require('x-xss-protection')())

If you need to run a middleware only under certain path, just pass the path as first parameter to use and you are done.

Note that this does not support routes with parameters, (eg: /users/:id/comments) and wildcard is not supported in multiple paths.

1
2
3
4
5
6
7
8
const serveStatic = require('serve-static')

// single path
fastify.use('/css', servveStatic('/asserts'))
// wildcard path
fastify.use('/css/*', servveStatic('/asserts'))
// multiple paths
fastify.use(['/css', '/js'], servveStatic('/asserts'))

Hook

By using the hook you can interact directly inside the lifecycle of Fastify, there are three different Hooks that you can use(in order of execution):

  • onRequest

  • preHandler

  • onResponse

  • onClose

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fastify.addHook('onRequest', (req, res, next) => {
// some code
next()
})

fastify.addHook('preHandler', (req, res, next) => {
// some code
next()
})

fastify.addHook('onResponse', (res, next) => {
// some code
next()
})

If you want to pass a custom error code to the user, just use the reply.code():

1
2
3
4
5
fastify.addHook('onRequest', (req, res, next) => {
// some code
reply.code(400)
next(new Error('some error'))
})

The error will be handled by Reply

The unique hook that is not inside the lifecycle is 'onClose', this one is triggered when you call fastify.close() to stop the server, and it is useful if you have some plugins that need a ‘shutdown’ part, such as a connection to database.

Only for this hook the parameters of the functions changes.

1
2
3
4
fastify.addHook('onClose', (instance, done) => {
// some code
done()
})

Scope

Except for onClose all the hooks are encapsulated this means that you can decide where your hooks should run by using register.

beforeHandler

beforeHandler is not a standard hook like preHandler, but is a function that your register right in the route option that will be executed only in the specified route.

beforeHandler is executed always after the preHandler hook.

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
fastify.addHook('preHander', (req, reply, done) => {
// your code
done()
})

fastify.route({
method: 'GET',
url: '/',
schema: {/*...*/},
beforeHandler: (req, reply, done) => {/*...*/ ;done()}
handler: (req, reply) => { reply.send({ hello: 'world' }) }
})

fastify.route({
method: 'GET',
url: '/',
shcema: {/*...*/},
beforeHandler: [
function first (req, reply, done) {
done()
},
function second (req, reply, done) {
done()
}
],
handler: function (req, reply) {
reply.send({ hello: 'world' })
}
})

Decorators

If you need to add functionalities to the Fastify instance, the decorator api is what you need.

This api allows you to add new properties to the Fastify instance, a property value is not restricted to be a function, could also be an object or a string for example.

Usage

decorate

Just call the decorate api and pass the name of the new property and its value.

1
2
3
fastify.decorate('utility', () => {
// something very useful
})

As said above, you can decorate the instance with other values and not only functions:

1
2
3
4
fastify.decorate('conf', {
db: 'some db',
port: 3000,
})

Once you decorate the instance you can access the value every time you need by using the name you passed as parameter:

1
2
fastify.utility()
console.log(fastify.conf.db)

Decorators are not overwritable, if you try to declare a decorator already declared, decorate will throw an exception.

decorateReply

As the name suggest, this api is needed if you want to add new methods to the Reply core object. Just call the decorateReply api and pass the name of the new property and its value.

1
2
3
fastify.decorateReply('utility', function () {
// something very useful
})

Note: using an arrow function will break the binding of this to the Fastify reply instance.

decorateRequest
1
2
3
fastify.decorateRequest('utility', function () {
// something very useful
})

Note: using an arrow function will break the binding of this to the Fastify request instance.

extendServerError

If you need to extend the standard server error, this api is what you need.

You must pass a function that returns an Object, Fastify will extend the server error with the returned object of your function. The function will receive the original error object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fastify.extendServerError(err => {
return {
timestamp: new Date(),
}
})

/*
The resulting object will be: {
error: string,
message: string,
statusCode: number,
timestamp: date,
}
*/

Sync and Async

decorate is synchronous API, if you need to add a decorator that has an asynchronous bootstrap, could happen that Fastify boots up before your decorator is ready. To avoid this issue you must use register api in combination with fastify-plugin.

Dependencies

If your decorator depends on another decorator, you can declare the dependencies of your function, it’s pretty easy, you just need to add an array of strings(representing the names of the decorators your are depending on) as third parameter.

1
fastify.decorate('utility', fn, ['greet', 'log'])

If a dependency is not satisfied, decorate will throw an exception.

hasDecorator

1
fastify.hasDecorator('utility')

Validation and Serialize

Fastify uses a schema based approach and even if it is not mandatory we recommend to use JSON Schema to validate you routes and serialize your output, internally Fastify compiles the schema in a highly performance function.

Validation

The route validation internally uses Ajv, which is highly performant JSON validator. Validate the input is very easy, just add the fields that you need inside the route schema and you are done.

The supported validations are

  • body: Validates the body of the request if it is a POST or PUT

  • querystring: Validates the querystring. This can be a complete JSON Schema object, with the property type of object and properties object of parameters, or simply the values of what would be contained in the properties object as shown below.

  • params: Validates teh route params.

  • headers: Validates teh request headers.

Example:

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
const schema = {
body: {
type: 'object',
properties: {
someKey: { type: 'string'},
someOtherKey: { type: 'number' },
}
},
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' },
},
params: {
type: 'object',
properties: {
par1: { type: 'string' },
par2: { type: 'number' },
},
},
headers: {
type: 'object',
properties: {
'x-foo': { type: 'string' },
},
required: ['x-foo'],
}
}

Schema Compiler

The schemaCompiler is a function that returns a function that validates the body, url parameters, headers and query string.

The default schemaCompiler returns a function that implements the ajv validation interface. fastify use it internally to speed the valiation up.

Serialize

Usually you will send your data to the clients via JSON, and Fastify has a powerful tools to help you: fast-json-stringify, which is used if you have provided an output schema in the route options.

1
2
3
4
5
6
7
8
9
10
11
const schema = {
response: {
200: {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
}
}
}
}

As you can see the response schema is based on the status code, if you want to use the same schema for mutliple status code, you can use 2xx.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const schema = {
response: {
'2xx': {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
}
},
201: {
type: 'object',
properties: {
value: { type: 'string' }
}
}
}
}

Lifecycle

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
Incoming Request

└─▶ Instance Logger

└─▶ Routing

404 ◀─┴─▶ onRequest Hook

4**/5** ◀─┴─▶ run Middlewares

4**/5** ◀─┴─▶ Parsing

415 ◀─┴─▶ Validation

400 ◀─┴─▶ preHandler Hook

4**/5** ◀─┴─▶ beforeHandler

4**/5** ◀─┴─▶ User Handler

└─▶ Reply
│ │
│ └─▶ Outgoing Response

└─▶ onResponse Hook

Reply

The second parameter of the handler function is Reply.

Reply is a core Fastify object that exposes the following functions:

  • .code(statusCode)

  • .header(name, value)

  • .type(value): Set the header Content-Type

  • .redirect([code, ] url): default code 302

  • .serializer(function): Set a custom serializer for the payload

  • .send(payload)

  • .sent: A boolean value that you can use if you need to know it send has already been called.

1
2
3
4
5
6
fastify.get('/', options, (request, reply) => {
reply
.code(200)
.type('application/json')
.send({ hello: 'world' })
})

Code

If not set via reply.code, the resulting statusCode will be 200

Sets a custom header to the response.

If you not set a Content-Type header, Fastify assumes that you are using application/json, unless you are send a stream, in that case Fastify recognize it and sets the Content-Type at application/octet-stream.

Redirect

Redirects a request to the specified url, the status code is optional, default to 302.

1
reply.redirect('/home')

Type

Sets the content type for the response.

It’s the shortcut for reply.header('Content-Type', 'application/json')

Serializer

Send

Objects

If you are sending JSON Objects, send will serialize the object with fast-json-stringify if you setted an output schema, otherwise fast-safe-stringify

Promises

Send handle natively the promsies and supports out of the box async-await:

1
2
3
4
5
6
7
8
9
10
11
12
13
fastify.get('/promises', options, (req, reply) => {
const promise = new Prmise(/*...*/)
reply
.code(200)
.send(promise)
})

fastify.get('/async-await', options, async (req, reply) => {
let res = await new Promise(resolve => {
setTimeout(resolve, 200, { hello: 'world' })
})
return res
})

Streams

Send can also handle streams out of box, internally uses pump to avoid leaks of file description. If you are sneding a stream and you have not setted a Content-Type header, send will set it at application/octet-stream.

Errors

If you pass to send an object that is an instance of Error, Fastify will automatically create error structured as the following:

1
2
3
4
5
{
error: string, // the http error message
message: string, // the user error message
statusCode: number, // the http status code
}

Request

The first parameter of the handler function is Request.

Request is a core Fastify object containing the following fields:

  • query

  • body

  • params: the params matching the URL

  • headers

  • req: the incoming HTTP request from Node core

  • log

1
2
3
4
5
6
7
8
fastify.post('/:params', options, (req, reply) => {
console.log(request.body)
console.log(request.query)
console.log(request.params)
console.log(request.headers)
console.log(request.req)
request.log.info('some info')
})

Content Type Parser

Natively Fastify supports only application/json content-type. If you need to support different content types you can use the addContentTypeParser api.

Catch All

1
2
3
4
5
fastify.addContentTypeParser('*', function (req, done) {
var data = ''
req.on('data', chunk => {data += chunk})
req.on('end', () => done(data))
})

Plugins

Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever.

The API you will need for one or more plugins is register.

By default, register creates a new scope, this means that if you do some changes to the Fastify instance(via decorate), this change will not be reflected to the current context ancestors, but only to its sons.

1
2
3
4
5
6
fastify.register(plugin, [options])

fastify.register([
require('./another-route'),
require('./yet-another-route'),
], opts)

Craete a Plugin

Create a plugin is very easy, you just need to create a function that takes three paremters, the fastify instance, an options object and the next callback.

1
2
3
4
5
module.exports = (fastify, opts, next) => {
fastify.decorate('utility', () => {})
fastify.get('/', handler)
next()
}

Handle the Scope

You have two ways to tell Fastify to avoid the creation of a new context:

  • Use the fastify-plugin module

  • Use the skip-override hidden property

We recomended to use the fastify-plugin module, because it solves this problem for you, and you can pass as parameter a version range of Fastify taht your plguin support.

1
2
3
4
5
6
const fp = require('fastify-plguin')

module.exports = fp(function(fastify, opts, next) {
fastify.decorate('utility', () => {})
next()
}, '0.x')

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×