Categories
Fastify

Server-Side Development with Fastify — Route Level Hooks and Decorators

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Route Level Hooks

We can add hooks at the route level.

To do this, we can write:

const fastify = require('fastify')({})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { },
  onRequest (request, reply, done) {
    done()
  },
  onResponse (request, reply, done) {
    done()
  },
  preParsing (request, reply, done) {
    done()
  },
  preValidation (request, reply, done) {
    done()
  },
  preHandler (request, reply, done) {
    done()
  },
  preSerialization (request, reply, payload, done) {
    done(null, payload)
  },
  handler (request, reply) {
    reply.send({ hello: 'world' })
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add the hooks.

onRequest is run when a request is received.

onResponse is run when a response is sent.

preParsing is run when the request is parsed.

preValidation is run before validation is done on the request.

preHandler is run after the shared preHandler hooks.

preSerialization runs after the shared preSerialization hook, which is run after the response is serialized.

handler is the route handler method.

Decorators

We can use decorators to customize core Fastify objects, such as the request and reply objects.

To add one and use them we can write:

const fastify = require('fastify')({})

fastify.decorateRequest('user', '')

fastify.addHook('preHandler', (req, reply, done) => {
  req.user = 'jane smith'
  done()
})

fastify.get('/', (req, reply) => {
  reply.send(`Hello, ${req.user}!`)
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.decoratorRequest method to add a user property the req object.

Then we set the req.user property in the preHandler hook to 'jane smith' .

Then in our route handler, we can access the value.

Therefore, we get:

Hello, jane smith!

returned as the response.

We can use the fastify.decorate method to decorate the Fastify server instance.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('conf', {
  db: 'some.db',
  port: 3000
})

fastify.get('/', (req, reply) => {
  reply.send(`Hello ${fastify.conf.db}`)
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add data to the fastify object.

We add the conf property to it.

Then we can access the value anywhere in our app.

So we should get:

Hello some.db

from the response.

The decorated Fastify server can also be accessed with the this keyword from the route handler.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('conf', {
  db: 'some.db',
  port: 3000
})

fastify.get('/', async function (request, reply) {
  reply.send({hello: this.conf.db})
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then we get:

{"hello":"some.db"}

from the response.

Conclusion

We can add data to the Fastify instance or requests with Fastify decorators.

Also, we can add hooks at the route level.

Categories
Fastify

Server-Side Development with Fastify — App Hooks

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Respond to a Request from a Hook

We can send responses from hooks.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('onRequest', (request, reply, done) => {
  reply.send('Early response')
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call reply.send in the onRequest hook, so we see:

Early response

in the response.

Also, we can write:

const fastify = require('fastify')({})

fastify.addHook('preHandler', async (request, reply) => {
  reply.send('Early response')
  return reply
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to get the same result with an async function.

Application Hooks

Fastify gs 4 application hooks. They are:

  • onReady — runs the server starts listening to requests
  • onClose — runs when fastify.close() is run to stop the server
  • onRoute — runs when a route is registered
  • onRegister — runs when a new plugin is registered and new encapsulation context is created.

For example we can write:

const fastify = require('fastify')({})

fastify.addHook('onRoute', (routeOptions) => {
  console.log(routeOptions.method)
  console.log(routeOptions.schema)
  console.log(routeOptions.url)
  console.log(routeOptions.path)
  console.log(routeOptions.routePath)
  console.log(routeOptions.bodyLimit)
  console.log(routeOptions.logLevel)
  console.log(routeOptions.logSerializers)
  console.log(routeOptions.prefix)
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to use the onRoute hook.

routeOptions has properties about the route.

method has the request method. url has the request URL.

path is the alias for url .

routePath is the URL of the route without the prefix.

bodyLimit has the max request size.

To use the onRegister hook, we write:

const fastify = require('fastify')({})

fastify.decorate('data', [])

fastify.register(async (instance, opts) => {
  instance.data.push('hello')
  console.log(instance.data)

instance.register(async (instance, opts) => {
    instance.data.push('world')
    console.log(instance.data)
  }, { prefix: '/hola' })
}, { prefix: '/ciao' })

fastify.register(async (instance, opts) => {
  console.log(instance.data)
  console.log(opts.prefix)
}, { prefix: '/hello' })

fastify.addHook('onRegister', (instance, opts) => {
  instance.data = instance.data.slice()
  console.log(opts.prefix)
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call instance.data.push to add data we use in the plugin.

opts.prefix has the prefix property from the 2nd argument.

Hook Scope

Hooks have their own scope.

We can access it with the this object.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('foo', 'bar')

fastify.addHook('onRequest', function (request, reply, done) {
  const self = this
  console.log(self.foo)
  done()
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.decorate to add properties to this in the onRequest hook.

The first argument is the property name and the 2nd is the value.

So self.foo should be 'bar' .

Conclusion

We can add hooks to our Fastify app to run code at various parts of the app’s lifecycle.

Categories
Fastify

Server-Side Development with Fastify — Event and Error Hooks

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

onSend

We can change the response payload being sent with the onSend hook.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('onSend', (request, reply, payload, done) => {
  const err = null;
  const newPayload = payload.replace('some-text', 'some-new-text')
  done(err, newPayload)
})
const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We replace the 'some-text' response with 'some-new-text' .

Then when we make a GET request to / , we see 'some-new-text’ returned.

Equivalently, we can do the same with an async function:

const fastify = require('fastify')({})

fastify.addHook('onSend', async (request, reply, payload) => {
  const newPayload = payload.replace('some-text', 'some-new-text')
  return newPayload
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We can also remove the response payload with:

const fastify = require('fastify')({})

fastify.addHook('onSend', (request, reply, payload, done) => {
  reply.code(304)
  const newPayload = null
  done(null, newPayload)
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

onResponse

The onResponse hook lets us run code when a response is sent.

It’s a useful place to gather statistics.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addHook('onResponse', (request, reply, done) => {
  console.log(reply)
  done()
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to log the reply .

We can also write:

const fastify = require('fastify')({})

fastify.addHook('onResponse', async (request, reply) => {
  console.log(reply)
  return
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to do the same thing.

onTimeout

The onTimeout hook lets us monitor request timeouts.

For instance, we can use it by writing:

const fastify = require('fastify')({})

fastify.addHook('onTimeout', (request, reply, done) => {
  console.log('timed out')
  done()
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

or:

const fastify = require('fastify')({})

fastify.addHook('onTimeout', async (request, reply) => {
  console.log('timed out')
  return
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to log any request timeouts.

Managing Errors in Hooks

We can raise errors in hooks and return error responses if there are any errors.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('preHandler', (request, reply, done) => {
  reply.code(400)
  done(new Error('Some error'))
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add the preHandler hook and return 400 errors.

We can also pass in an Error instance in the done function.

Also, we can write:

const fastify = require('fastify')({})

fastify.addHook('onResponse', async (request, reply) => {
  throw new Error('Some error')
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to do the same thing with async functions.

Conclusion

We can add various hooks with Fastify to handle events and errors.

Categories
Fastify

Server-Side Development with Fastify — Hooks

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

preParsing Hook

The preParsing hook lets us transform the request payload stream before it’s parsed.

If it returns a value, it must return a stream.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('preParsing', (request, reply, payload, done) => {
  done(null, payload)
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send()
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to pass payload into the done function.

Also, we can write:

const fastify = require('fastify')({})

fastify.addHook('preParsing', async (request, reply, payload) => {
  return payload
})
const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send()
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to return a promise resolving to payload .

preValidation

The preValidation hook lets us run code before validating the request.

It runs after the preParsing hook.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addHook('preValidation', (request, reply, done) => {
  done()
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send()
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We can add some checks into the callback.

We can also write:

const fastify = require('fastify')({})

fastify.addHook('preValidation', async (request, reply) => {
  return
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send()
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to do the same thing.

preSerialization

The preSerialization hook letsus change or replace the payload before it’s serialized.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addHook('preSerialization', (request, reply, payload, done) => {
  const err = null
  const newPayload = { wrapped: payload }
  done(err, newPayload)
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send(request.body)
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then when we make a POST request to / with the payload:

{
    "abc": 123
}

We get:

{
    "wrapped": {
        "abc": 123
    }
}

as the response.

Equivalently, we can write:

const fastify = require('fastify')({})

fastify.addHook('preSerialization', async (request, reply, payload) => {
  return { wrapped: payload }
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send(request.body)
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

and get the same result.

This hook isn’t called if the payload is a string, buffer, stream or null .

onError

The onError hook lets us catch errors that occur in our app.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('onError', (request, reply, error, done) => {
  done()
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send(request.body)
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to catch errors. error has the error data.

Likewise, we can write:

const fastify = require('fastify')({})

fastify.addHook('onError', async (request, reply, error) => {
  console.log(error)
})

const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send(request.body)
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to do the same thing.

Conclusion

We can add hooks to listen to various events with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Middleware and Hooks

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Middleware

Middleware support doesn’t come out of the box with the Fastify framework since version 3.

If we want to add support for Express style middleware, we need to install fastify-express or middle plugin.

For example, we can write:

const fastify = require('fastify')()

const start = async () => {
  try {
    await fastify.register(require('fastify-express'))
    fastify.use(require('cors')())

    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We add:

await fastify.register(require('fastify-express'))
fastify.use(require('cors')())

to add the fastify-express plugin with the cors middleware.

To use the middle module, we write:

const fastify = require('fastify')()

const start = async () => {
  try {
    await fastify.register(require('middie'))
    fastify.use(require('cors')())

    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add the middleware and add the cors middleware.

Restrict Middleware Execution to a Certain Paths

We can restrict middleware execution to certain paths.

For example, we can write:

const fastify = require('fastify')({})
const path = require('path')

const start = async () => {
  try {
    await fastify.register(require('fastify-express'))
    const serveStatic = require('serve-static')

    fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))
    fastify.use('/css/(.*)', serveStatic(path.join(__dirname, '/assets')))
    fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))

    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.use with the path that the middleware runs in as the first argument.

The 2nd argument is the middleware that we want to run.

This doesn’t work with route with parameters.

Fastify offers some alternatives to the most commonly used middlewares, such as fastify-helmet in the case of helmet, fastify-cors for cors and fastify-static for serve-static.

Hooks

We can register hooks with the fastify.addHook method.

For example, we can write:

const fastify = require('fastify')({})
const asyncMethod = () => Promise.resolve('foo')

const start = async () => {
  try {
    fastify.addHook('onRequest', async (request, reply) => {
      await asyncMethod()
      return
    })

    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to call addHook for the onRequest event.

Also, we can write:

const fastify = require('fastify')({})
fastify.addHook('onRequest', (request, reply, done) => {
  done()
})
const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to do the same thing. We call done to indicate the hook has finished running.

Conclusion

We can add Express middleware and hooks with Fastify.