rembrembdocs

Version: latest (v5.8.x)

Routes

The route methods configure the endpoints of the application. Routes can be declared using the shorthand method or the full declaration.

Full declaration

fastify.route(options)

Routes options

ℹ️ Note: The documentation for onRequest, preParsing, preValidation, preHandler, preSerialization, onSend, and onResponse is detailed in Hooks. To send a response before the request is handled by the handler, see Respond to a request from a hook.

Example:

fastify.route({  method: 'GET',  url: '/',  schema: {    querystring: {      type: 'object',      properties: {        name: { type: 'string' },        excitement: { type: 'integer' }      }    },    response: {      200: {        type: 'object',        properties: {          hello: { type: 'string' }        }      }    }  },  handler: function (request, reply) {    reply.send({ hello: 'world' })  }})

Shorthand declaration

The above route declaration is more Hapi-like, but if you prefer an Express/Restify approach, we support it as well:

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

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

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

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

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

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

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

Example:

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

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

The handler may also be supplied via the options object:

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

ℹ️ Note: Specifying the handler in both options and as the third parameter to the shortcut method throws a duplicate handler error.

Url building

Fastify supports both static and dynamic URLs.

To register a parametric path, use a colon before the parameter name. For wildcard, use a star. Static routes are always checked before parametric and wildcard routes.

// parametricfastify.get('/example/:userId', function (request, reply) {  // curl ${app-url}/example/12345  // userId === '12345'  const { userId } = request.params;  // your code here})fastify.get('/example/:userId/:secretToken', function (request, reply) {  // curl ${app-url}/example/12345/abc.zHi  // userId === '12345'  // secretToken === 'abc.zHi'  const { userId, secretToken } = request.params;  // your code here})// wildcardfastify.get('/example/*', function (request, reply) {})

Regular expression routes are supported, but slashes must be escaped. Take note that RegExp is also very expensive in terms of performance!

// parametric with regexpfastify.get('/example/:file(^\\d+).png', function (request, reply) {  // curl ${app-url}/example/12345.png  // file === '12345'  const { file } = request.params;  // your code here})

It is possible to define more than one parameter within the same couple of slash ("/"). Such as:

fastify.get('/example/near/:lat-:lng/radius/:r', function (request, reply) {  // curl ${app-url}/example/near/15°N-30°E/radius/20  // lat === "15°N"  // lng === "30°E"  // r ==="20"  const { lat, lng, r } = request.params;  // your code here})

Remember in this case to use the dash ("-") as parameters separator.

Finally, it is possible to have multiple parameters with RegExp:

fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, reply) {  // curl ${app-url}/example/at/08h24m  // hour === "08"  // minute === "24"  const { hour, minute } = request.params;  // your code here})

In this case as parameter separator it is possible to use whatever character is not matched by the regular expression.

The last parameter can be made optional by adding a question mark ("?") to the end of the parameter name.

fastify.get('/example/posts/:id?', function (request, reply) {  const { id } = request.params;  // your code here})

In this case, /example/posts and /example/posts/1 are both valid. The optional param will be undefined if not specified.

Having a route with multiple parameters may negatively affect performance. Prefer a single parameter approach, especially on routes that are on the hot path of your application. For more details, see find-my-way.

To include a colon in a path without declaring a parameter, use a double colon. For example:

fastify.post('/name::verb') // will be interpreted as /name:verb

Async Await

Are you an async/await user? We have you covered!

fastify.get('/', options, async function (request, reply) {  const data = await getData()  const processed = await processData(data)  return processed})

As shown, reply.send is not called to send data back to the user. Simply return the body and you are done!

If needed, you can also send data back with reply.send. In this case, do not forget to return reply or await reply in your async handler to avoid race conditions.

fastify.get('/', options, async function (request, reply) {  const data = await getData()  const processed = await processData(data)  return reply.send(processed)})

If the route is wrapping a callback-based API that will call reply.send() outside of the promise chain, it is possible to await reply:

fastify.get('/', options, async function (request, reply) {  setImmediate(() => {    reply.send({ hello: 'world' })  })  await reply})

Returning reply also works:

fastify.get('/', options, async function (request, reply) {  setImmediate(() => {    reply.send({ hello: 'world' })  })  return reply})

⚠ Warning:

  • When using both return value and reply.send(value), the first one takes precedence, the second is discarded, and a warn log is emitted.
  • Calling reply.send() outside of the promise is possible but requires special attention. See promise-resolution.
  • undefined cannot be returned. See promise-resolution.

Promise resolution

If the handler is an async function or returns a promise, be aware of the special behavior to support callback and promise control-flow. When the handler's promise resolves, the reply is automatically sent with its value unless you explicitly await or return reply in the handler.

  1. If using async/await or promises but responding with reply.send:
    • Do return reply / await reply.
    • Do not forget to call reply.send.
  2. If using async/await or promises:
    • Do not use reply.send.
    • Do return the value to send.

This approach supports both callback-style and async-await with minimal trade-off. However, it is recommended to use only one style for consistent error handling within your application.

ℹ️ Note: Every async function returns a promise by itself.

Route Prefixing

Sometimes maintaining multiple versions of the same API is necessary. A common approach is to prefix routes with the API version number, e.g., /v1/user. Fastify offers a fast and smart way to create different versions of the same API without changing all the route names by hand, called route prefixing. Here is how it works:

// server.jsconst fastify = require('fastify')()fastify.register(require('./routes/v1/users'), { prefix: '/v1' })fastify.register(require('./routes/v2/users'), { prefix: '/v2' })fastify.listen({ port: 3000 })
// routes/v1/users.jsmodule.exports = function (fastify, opts, done) {  fastify.get('/user', handler_v1)  done()}
// routes/v2/users.jsmodule.exports = function (fastify, opts, done) {  fastify.get('/user', handler_v2)  done()}

Fastify will not complain about using the same name for two different routes because it handles the prefix automatically at compilation time. This ensures performance is not affected.

Now clients will have access to the following routes:

This can be done multiple times and works for nested register. Route parameters are also supported.

To use a prefix for all routes, place them inside a plugin:

const fastify = require('fastify')()const route = {    method: 'POST',    url: '/login',    handler: () => {},    schema: {},}fastify.register(function (app, _, done) {  app.get('/users', () => {})  app.route(route)  done()}, { prefix: '/v1' }) // global route prefixawait fastify.listen({ port: 3000 })

Route Prefixing and fastify-plugin

If using fastify-plugin to wrap routes, this option will not work. To make it work, wrap a plugin in a plugin:

const fp = require('fastify-plugin')const routes = require('./lib/routes')module.exports = fp(async function (app, opts) {  app.register(routes, {    prefix: '/v1',  })}, {  name: 'my-routes'})

Handling of / route inside prefixed plugins

The / route behaves differently based on whether the prefix ends with /. For example, with a prefix /something/, adding a / route matches only /something/. With a prefix /something, adding a / route matches both /something and /something/.

See the prefixTrailingSlash route option above to change this behavior.

Custom Log Level

Different log levels can be set for routes in Fastify by passing the logLevel option to the plugin or route with the desired value.

Be aware that setting logLevel at the plugin level also affects setNotFoundHandler and setErrorHandler.

// server.jsconst fastify = require('fastify')({ logger: true })fastify.register(require('./routes/user'), { logLevel: 'warn' })fastify.register(require('./routes/events'), { logLevel: 'debug' })fastify.listen({ port: 3000 })

Or pass it directly to a route:

fastify.get('/', { logLevel: 'warn' }, (request, reply) => {  reply.send({ hello: 'world' })})

Remember that the custom log level applies only to routes, not to the global Fastify Logger, accessible with fastify.log.

Custom Log Serializer

In some contexts, logging a large object may waste resources. Define custom serializers and attach them in the appropriate context.

const fastify = require('fastify')({ logger: true })fastify.register(require('./routes/user'), {  logSerializers: {    user: (value) => `My serializer one - ${value.name}`  }})fastify.register(require('./routes/events'), {  logSerializers: {    user: (value) => `My serializer two - ${value.name} ${value.surname}`  }})fastify.listen({ port: 3000 })

Serializers can be inherited by context:

const fastify = Fastify({  logger: {    level: 'info',    serializers: {      user (req) {        return {          method: req.method,          url: req.url,          headers: req.headers,          host: req.host,          remoteAddress: req.ip,          remotePort: req.socket.remotePort        }      }    }  }})fastify.register(context1, {  logSerializers: {    user: value => `My serializer father - ${value}`  }})async function context1 (fastify, opts) {  fastify.get('/', (req, reply) => {    req.log.info({ user: 'call father serializer', key: 'another key' })    // shows: { user: 'My serializer father - call father  serializer', key: 'another key' }    reply.send({})  })}fastify.listen({ port: 3000 })

Config

Registering a new handler, you can pass a configuration object to it and retrieve it in the handler.

// server.jsconst fastify = require('fastify')()function handler (req, reply) {  reply.send(reply.routeOptions.config.output)}fastify.get('/en', { config: { output: 'hello world!' } }, handler)fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)fastify.listen({ port: 3000 })

Constraints

Fastify supports constraining routes to match certain requests based on properties like the Host header or any other value via find-my-way constraints. Constraints are specified in the constraints property of the route options. Fastify has two built-in constraints: version and host. Custom constraint strategies can be added to inspect other parts of a request to decide if a route should be executed.

Version Constraints

You can provide a version key in the constraints option to a route. Versioned routes allows multiple handlers to be declared for the same HTTP route path, matched according to the request's Accept-Version header. The Accept-Version header value should follow the semver specification, and routes should be declared with exact semver versions for matching.

Fastify will require a request Accept-Version header to be set if the route has a version set, and will prefer a versioned route to a non-versioned route for the same path. Advanced version ranges and pre-releases currently are not supported.

ℹ️ Note: Using this feature can degrade the router's performance.

fastify.route({  method: 'GET',  url: '/',  constraints: { version: '1.2.0' },  handler: function (request, reply) {    reply.send({ hello: 'world' })  }})fastify.inject({  method: 'GET',  url: '/',  headers: {    'Accept-Version': '1.x' // it could also be '1.2.0' or '1.2.x'  }}, (err, res) => {  // { hello: 'world' }})

⚠ Warning: Set a Vary header in responses with the value used for versioning (e.g., 'Accept-Version') to prevent cache poisoning attacks. This can also be configured in a Proxy/CDN.

const append = require('vary').appendfastify.addHook('onSend', (req, reply, payload, done) => {  if (req.headers['accept-version']) { // or the custom header being used    let value = reply.getHeader('Vary') || ''    const header = Array.isArray(value) ? value.join(', ') : String(value)    if ((value = append(header, 'Accept-Version'))) { // or the custom header being used      reply.header('Vary', value)    }  } done()})

If multiple versions with the same major or minor are declared, Fastify will always choose the highest compatible with the Accept-Version header value.

If the request lacks an Accept-Version header, a 404 error will be returned.

Custom version matching logic can be defined through the constraints configuration when creating a Fastify server instance.

Host Constraints

Provide a host key in the constraints route option to limit the route to certain values of the request Host header. host constraint values can be specified as strings for exact matches or RegExps for arbitrary host matching.

fastify.route({  method: 'GET',  url: '/',  constraints: { host: 'auth.fastify.example' },  handler: function (request, reply) {    reply.send('hello world from auth.fastify.example')  }})fastify.inject({  method: 'GET',  url: '/',  headers: {    'Host': 'fastify.example'  }}, (err, res) => {  // 404 because the host doesn't match the constraint})fastify.inject({  method: 'GET',  url: '/',  headers: {    'Host': 'auth.fastify.dev'  }}, (err, res) => {  // => 'hello world from auth.fastify.dev'})

RegExp host constraints can also be specified allowing constraining to hosts matching wildcard subdomains (or any other pattern):

fastify.route({  method: 'GET',  url: '/',  constraints: { host: /.*\.fastify\.example/ }, // will match any subdomain of fastify.dev  handler: function (request, reply) {    reply.send('hello world from ' + request.headers.host)  }})

Asynchronous Custom Constraints

Custom constraints can be provided, and the constraint criteria can be fetched from another source such as a database. Use asynchronous custom constraints as a last resort, as they impact router performance.

function databaseOperation(field, done) {  done(null, field)}const secret = {  // strategy name for referencing in the route handler `constraints` options  name: 'secret',  // storage factory for storing routes in the find-my-way route tree  storage: function () {    let handlers = {}    return {      get: (type) => { return handlers[type] || null },      set: (type, store) => { handlers[type] = store }    }  },  // function to get the value of the constraint from each incoming request  deriveConstraint: (req, ctx, done) => {    databaseOperation(req.headers['secret'], done)  },  // optional flag marking if handlers without constraints can match requests that have a value for this constraint  mustMatchWhenDerived: true}

⚠ Warning: When using asynchronous constraints, avoid returning errors inside the callback. If errors are unavoidable, provide a custom frameworkErrors handler to manage them. Otherwise, route selection may break or expose sensitive information.

const Fastify = require('fastify')const fastify = Fastify({  frameworkErrors: function (err, req, res) {    if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {      res.code(400)      return res.send("Invalid header provided")    } else {      res.send(err)    }  }})