Fastify

JaxNode January 2021

Fastify

  • Alternative REST framework for Node.js
  • Created to overcome shortcomings of Express
  • Extremely fast
  • Extendable
  • Schema validation
  • Logging
  • TypeScript ready

Support

  • Created by Matteo Collina and Tomas Della Vedova 
  • Sponsored by NearForm, OpenJS and Microsoft
  • 19 contributors and maintainers
// Require the framework and instantiate it
const fastify = require('fastify')({ logger: true });

// Declare a route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
});

// Run the server!
const start = async () => {
  try {
    await fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start();

Validation Schema

schema: {
    // request needs to have a querystring with a `name` parameter
    querystring: {
      name: { type: 'string' }
    },
    // the response needs to be an object with 
    // an `hello` property of type 'string'
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }

Plugin Ecosystem

  • fastify-swagger
  • fastify-redis
  • fastify-mongodb
  • fastify-oauth2
  • 42 core plugins, 124 community plugins
Feature Express Hapi Fastify
router
middleware
plugins
validation
hooks
decorators
logging
async/await
req/sec 18k 20k 36k

Plugins

  • fastify.register()
  • pluginSignature(fastify, opts, done)
  • Plugins over middleware is encouraged
// our-first-route.js
async function routes (fastify, options) {
  fastify.get('/', async (request, reply) => {
    return { hello: 'world' }
  })
}

module.exports = routes
// server.js

const fastify = require('fastify')({
  logger: true
})

fastify.register(require('./our-first-route'))

fastify.listen(3000, function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

Decorators

  • Allows you to add properties and methods onto the fastify object
  • Think Monkeypatching
  • decorate(name, value, [dependencies])
// Decorate request with a 'user' property
fastify.decorateRequest('user', '')

// Update our property
fastify.addHook('preHandler', (req, reply, done) => {
  req.user = 'Dave Growl'
  done()
})
// And finally access it
fastify.get('/', (req, reply) => {
  reply.send(`Hello, ${req.user}!`)
})

Routing

  • 'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT' and 'OPTIONS' all supported
  • fastify.route()
  • Support for express style short hand version
fastify.route({
  method: 'GET',
  url: '/',
  schema: {
    querystring: {
      name: { type: 'string' },
      excitement: { type: 'integer' }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

Routes long form

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }
}
fastify.get('/', opts, (request, reply) => {
  reply.send({ hello: 'world' })
})

Long Form

Route Formats

  • fastify.get('/example/:userId', (request, reply) => {})
  • fastify.get('/example/*', (request, reply) => {}) // Wildcard
  • fastify.get('/example/:file(^\\d+).png', (request, reply) => {})

Prefix

  • Add option for prefix when registering the plugin
  • fastify.register(require('./routes/v1/users'),
    { prefix: '/v1' })
  • All routes will start with 'v1'
 
// server.js
const fastify = require('fastify')()

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

fastify.listen(3000)
// routes/v1/users.js
module.exports = function (fastify, opts, done) {
  fastify.get('/user', handler_v1)
  done()
}
// routes/v2/users.js
module.exports = function (fastify, opts, done) {
  fastify.get('/user', handler_v2)
  done()
}

Prefix Example

Templating

  • point-of-view decorates reply with 'view' method
  • Supported templating engines include 'marko', 'pug', 'ejs', 'handlebars', 'twig' and 'liquid'
  • set by adding engine parameter when registering the plugin
fastify.register(require('point-of-view'), {
  engine: {
    ejs: require('ejs')
  },
  root: path.join(__dirname, 'view'),
  layout: 'template',
  viewExt: 'html', // it will add the extension to all the views
  options: {}
})

Hooks

  • Can inject code into different parts of the lifecycle
  • Very useful for testing
  • Request Hooks
  • Application Hooks

onRequest
preParsing
preValidation
preHandler
preSerialization
onError
onSend

 

onResponse
onTimeout
onReady
onClose
onRoute
onRegister

Hook Types

fastify.addHook('preParsing', async (request, reply, payload) => {
  // Some code
  await asyncMethod()
  return newPayload
})

Middleware

  • Middleware was added for backwards compatibility
  • Express middleware will run in Fastify
  • Express support starting with 3.0.0
  • Example would be Passport
  • Can use 'middie' for improved performance
await fastify.register(require('fastify-express'))
fastify.use(require('cors')())
fastify.use(require('dns-prefetch-control')())
fastify.use(require('frameguard')())
fastify.use(require('hsts')())
fastify.use(require('ienoopen')())
fastify.use(require('x-xss-protection')())

Express Middleware Example

await fastify.register(require('middie'))
fastify.use(require('cors')())

middie example

Serverless

  • Support for many serverless options
  • aws-lambda
  • netlify lambda
  • Vercel

Demo

Questions?

Fastify

By David Fekke