V4 Migration Guide

This guide is intended to help with migration from Fastify v3 to v4.

Before migrating to v4, please ensure that you have fixed all deprecation warnings from v3. All v3 deprecations have been removed and they will no longer work after upgrading.

Codemods

Fastify v4 Codemods

To help with the upgrade, we’ve worked with the team at Codemod to publish codemods that will automatically update your code to many of the new APIs and patterns in Fastify v4.

Run the following migration recipe to automatically update your code to Fastify v4:

  1. npx codemod@latest fastify/4/migration-recipe

This will run the following codemods:

Each of these codemods automates the changes listed in the v4 migration guide. For a complete list of available Fastify codemods and further details, see Codemod Registry.

Breaking Changes

Error handling composition (#3261)

When an error is thrown in an async error handler function, the upper-level error handler is executed if set. If there is no upper-level error handler, the default will be executed as it was previously:

  1. import Fastify from 'fastify'
  2. const fastify = Fastify()
  3. fastify.register(async fastify => {
  4. fastify.setErrorHandler(async err => {
  5. console.log(err.message) // 'kaboom'
  6. throw new Error('caught')
  7. })
  8. fastify.get('/encapsulated', async () => {
  9. throw new Error('kaboom')
  10. })
  11. })
  12. fastify.setErrorHandler(async err => {
  13. console.log(err.message) // 'caught'
  14. throw new Error('wrapped')
  15. })
  16. const res = await fastify.inject('/encapsulated')
  17. console.log(res.json().message) // 'wrapped'

The root error handler is Fastify’s generic error handler. This error handler will use the headers and status code in the Error object, if they exist. The headers and status code will not be automatically set if a custom error handler is provided.

Removed app.use() (#3506)

With v4 of Fastify, app.use() has been removed and the use of middleware is no longer supported.

If you need to use middleware, use @fastify/middie or @fastify/express, which will continue to be maintained. However, it is strongly recommended that you migrate to Fastify’s hooks.

Note: Codemod remove app.use() with:

  1. npx codemod@latest fastify/4/remove-app-use

reply.res moved to reply.raw

If you previously used the reply.res attribute to access the underlying Request object you will now need to use reply.raw.

Note: Codemod reply.res to reply.raw with:

  1. npx codemod@latest fastify/4/reply-raw-access

Need to return reply to signal a “fork” of the promise chain

In some situations, like when a response is sent asynchronously or when you are not explicitly returning a response, you will now need to return the reply argument from your router handler.

exposeHeadRoutes true by default

Starting with v4, every GET route will create a sibling HEAD route. You can revert this behavior by setting exposeHeadRoutes: false in the server options.

Synchronous route definitions (#2954)

To improve error reporting in route definitions, route registration is now synchronous. As a result, if you specify an onRoute hook in a plugin you should now either:

  • wrap your routes in a plugin (recommended)

    For example, refactor this:

    1. fastify.register((instance, opts, done) => {
    2. instance.addHook('onRoute', (routeOptions) => {
    3. const { path, method } = routeOptions;
    4. console.log({ path, method });
    5. done();
    6. });
    7. });
    8. fastify.get('/', (request, reply) => { reply.send('hello') });

    Into this:

    1. fastify.register((instance, opts, done) => {
    2. instance.addHook('onRoute', (routeOptions) => {
    3. const { path, method } = routeOptions;
    4. console.log({ path, method });
    5. done();
    6. });
    7. });
    8. fastify.register((instance, opts, done) => {
    9. instance.get('/', (request, reply) => { reply.send('hello') });
    10. done();
    11. });

Note: Codemod synchronous route definitions with:

  1. npx codemod@latest fastify/4/wrap-routes-plugin
  • use await register(...)

    For example, refactor this:

    1. fastify.register((instance, opts, done) => {
    2. instance.addHook('onRoute', (routeOptions) => {
    3. const { path, method } = routeOptions;
    4. console.log({ path, method });
    5. });
    6. done();
    7. });

    Into this:

    1. await fastify.register((instance, opts, done) => {
    2. instance.addHook('onRoute', (routeOptions) => {
    3. const { path, method } = routeOptions;
    4. console.log({ path, method });
    5. });
    6. done();
    7. });

Note: Codemod ‘await register(…)’ with:

  1. npx codemod@latest fastify/4/await-register-calls

Optional URL parameters

If you’ve already used any implicitly optional parameters, you’ll get a 404 error when trying to access the route. You will now need to declare the optional parameters explicitly.

For example, if you have the same route for listing and showing a post, refactor this:

  1. fastify.get('/posts/:id', (request, reply) => {
  2. const { id } = request.params;
  3. });

Into this:

  1. fastify.get('/posts/:id?', (request, reply) => {
  2. const { id } = request.params;
  3. });

Non-Breaking Changes

Deprecation of variadic .listen() signature

The variadic signature of the fastify.listen() method is now deprecated.

Prior to this release, the following invocations of this method were valid:

  • fastify.listen(8000)
  • fastify.listen(8000, ‘127.0.0.1’)
  • fastify.listen(8000, ‘127.0.0.1’, 511)
  • fastify.listen(8000, (err) => { if (err) throw err })
  • fastify.listen({ port: 8000 }, (err) => { if (err) throw err })

With Fastify v4, only the following invocations are valid:

  • fastify.listen()
  • fastify.listen({ port: 8000 })
  • fastify.listen({ port: 8000 }, (err) => { if (err) throw err })

Change of schema for multiple types

Ajv has been upgraded to v8 in Fastify v4, meaning “type” keywords with multiple types other than “null” are now prohibited.

You may encounter a console warning such as:

  1. strict mode: use allowUnionTypes to allow union type keyword at "#/properties/image" (strictTypes)

As such, schemas like below will need to be changed from:

  1. {
  2. type: 'object',
  3. properties: {
  4. api_key: { type: 'string' },
  5. image: { type: ['object', 'array'] }
  6. }
  7. }

Into:

  1. {
  2. type: 'object',
  3. properties: {
  4. api_key: { type: 'string' },
  5. image: {
  6. anyOf: [
  7. { type: 'array' },
  8. { type: 'object' }
  9. ]
  10. }
  11. }
  12. }

Add reply.trailers methods (#3794)

Fastify now supports the HTTP Trailer response headers.