16 KiB
Fastify
Routes
The routes methods will configure the endpoints of your application. You have two ways to declare a route with Fastify, the shorthand method and the full declaration.
- Full Declaration
- Route Options
- Shorthand Declaration
- URL Parameters
- Use
async
/await
- Promise resolution
- Route Prefixing
- Logs
- Route handler configuration
- Route's Versioning
Full declaration
fastify.route(options)
Routes option
-
method
: currently it supports'DELETE'
,'GET'
,'HEAD'
,'PATCH'
,'POST'
,'PUT'
and'OPTIONS'
. It could also 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, check here for more info.body
: validates the body of the request if it is a POST or a PUT.querystring
orquery
: validates the querystring. This can be a complete JSON Schema object, with the propertytype
ofobject
andproperties
object of parameters, or simply the values of what would be contained in theproperties
object as shown below.params
: validates the params.response
: filter and generate a schema for the response, setting a schema allows us to have 10-20% more throughput.
-
attachValidation
: attachvalidationError
to request, if there is a schema validation error, instead of sending the error to the error handler. -
onRequest(request, reply, done)
: a function as soon that a request is received, it could also be an array of functions. -
preParsing(request, reply, done)
: a function called before parsing the request, it could also be an array of functions. -
preValidation(request, reply, done)
: a function called after the sharedpreValidation
hooks, useful if you need to perform authentication at route level for example, it could also be an array of functions. -
preHandler(request, reply, done)
: a function called just before the request handler, it could also be an array of functions. -
preSerialization(request, reply, payload, done)
: a function called just before the serialization, it could also be an array of functions. -
onSend(request, reply, payload, done)
: a function called right before a response is sent, it could also be an array of functions. -
onResponse(request, reply, done)
: a function called when a response has been sent, so you will not be able to send more data to the client. It could also be an array of functions. -
handler(request, reply)
: the function that will handle this request. -
schemaCompiler(schema)
: the function that build the schema for the validations. See here -
bodyLimit
: prevents the default JSON body parser from parsing request bodies larger than this number of bytes. Must be an integer. You may also set this option globally when first creating the Fastify instance withfastify(options)
. Defaults to1048576
(1 MiB). -
logLevel
: set log level for this route. See below. -
logSerializers
: set serializers to log for this route. -
config
: object used to store custom configuration. -
version
: a semver compatible string that defined the version of the endpoint. Example. -
prefixTrailingSlash
: string used to determine how to handle passing/
as a route with a prefix.both
(default): Will register both/prefix
and/prefix/
.slash
: Will register only/prefix/
.no-slash
: Will register only/prefix
.
request
is defined in Request.reply
is defined in Reply.
Example:
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' })
}
})
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 (request, reply) {
reply.send({ hello: 'world' })
}
}
fastify.get('/', opts)
Note: if the handler is specified in both the
options
and as the third parameter to the shortcut method then throws duplicatehandler
error.
Url building
Fastify supports both static and dynamic urls.
To register a parametric path, use the colon before the parameter name. For wildcard use the star.
Remember that static routes are always checked before parametric and wildcard.
// parametric
fastify.get('/example/:userId', (request, reply) => {})
fastify.get('/example/:userId/:secretToken', (request, reply) => {})
// wildcard
fastify.get('/example/*', (request, reply) => {})
Regular expression routes are supported as well, but pay attention, RegExp are very expensive in term of performance!
// parametric with regexp
fastify.get('/example/:file(^\\d+).png', (request, reply) => {})
It's possible to define more than one parameter within the same couple of slash ("/"). Such as:
fastify.get('/example/near/:lat-:lng/radius/:r', (request, reply) => {})
Remember in this case to use the dash ("-") as parameters separator.
Finally it's possible to have multiple parameters with RegExp.
fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', (request, reply) => {})
In this case as parameter separator it's possible to use whatever character is not matched by the regular expression.
Having a route with multiple parameters may affect negatively the performance, so prefer single parameter approach whenever possible, especially on routes which are on the hot path of your application. If you are interested in how we handle the routing, checkout find-my-way.
Async Await
Are you an async/await
user? We have you covered!
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
return processed
})
As you can see we are not calling reply.send
to send back the data to the user. You just need to return the body and you are done!
If you need it you can also send back the data to the user with reply.send
.
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
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
andreply.send(value)
at the same time, the first one that happens takes precedence, the second value will be discarded, and a warn log will also be emitted because you tried to send a response twice. - You can't return
undefined
. For more details read promise-resolution.
Promise resolution
If your handler is an async
function or returns a promise, you should be aware of a special behaviour which is necessary to support the callback and promise control-flow. If the handler's promise is resolved with undefined
, it will be ignored causing the request to hang and an error log to be emitted.
- If you want to use
async/await
or promises but respond a value withreply.send
:- Don't
return
any value. - Don't forget to call
reply.send
.
- Don't
- If you want to use
async/await
or promises:- Don't use
reply.send
. - Don't return
undefined
.
- Don't use
In this way, we can support both callback-style
and async-await
, with the minimum trade-off. In spite of so much freedom we highly recommend to go with only one style because error handling should be handled in a consistent way within your application.
Notice: Every async function returns a promise by itself.
Route Prefixing
Sometimes you need to maintain two or more different versions of the same api, a classic approach is to prefix all the routes with the api version number, /v1/user
for example.
Fastify offers you a fast and smart way to create different version of the same api without changing all the route names by hand, route prefixing. Let's see how it works:
// 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()
}
Fastify will not complain because you are using the same name for two different routes, because at compilation time it will handle the prefix automatically (this also means that the performance will not be affected at all!).
Now your clients will have access to the following routes:
/v1/user
/v2/user
You can do this as many times as you want, it works also for nested register
and routes parameter are supported as well.
Be aware that if you use fastify-plugin
this option won't work.
Handling of / route inside prefixed plugins
The /
route has a different behavior depending if the prefix ends with
/
or not. As an example, if we consider a prefix /something/
,
adding a /
route will only match /something/
. If we consider a
prefix /something
, adding a /
route will match both /something
and /something/
.
See the prefixTrailingSlash
route option above to change this behaviour.
Custom Log Level
It could happen that you need different log levels in your routes, Fastify achieves this in a very straightforward way.
You just need to pass the option logLevel
to the plugin option or the route option with the value that you need.
Be aware that if you set the logLevel
at plugin level, also the setNotFoundHandler
and setErrorHandler
will be affected.
// server.js
const fastify = require('fastify')({ logger: true })
fastify.register(require('./routes/user'), { logLevel: 'warn' })
fastify.register(require('./routes/events'), { logLevel: 'debug' })
fastify.listen(3000)
Or you can directly pass it to a route:
fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
reply.send({ hello: 'world' })
})
Remember that the custom log level is applied only to the routes, and not to the global Fastify Logger, accessible with fastify.log
Custom Log Serializer
In some context, you may need to log a large object but it could be a waste of resources for some routes. In this case, you can define some serializer
and attach them in the right 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(3000)
You can inherit serializers by context:
const fastify = Fastify({
logger: {
level: 'info',
serializers: {
user (req) {
return {
method: req.method,
url: req.url,
headers: req.headers,
hostname: req.hostname,
remoteAddress: req.ip,
remotePort: req.connection.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(3000)
Config
Registering a new handler, you can pass a configuration object to it and retrieve it in the handler.
// server.js
const fastify = require('fastify')()
function handler (req, reply) {
reply.send(reply.context.config.output)
}
fastify.get('/en', { config: { output: 'hello world!' } }, handler)
fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)
fastify.listen(3000)
Version
Default
If needed you can provide a version option, which will allow you to declare multiple versions of the same route. The versioning should follow the semver specification.
Fastify will automatically detect the Accept-Version
header and route the request accordingly (advanced ranges and pre-releases currently are not supported).
Be aware that using this feature will cause a degradation of the overall performances of the router.
fastify.route({
method: 'GET',
url: '/',
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' }
})
If you declare multiple versions with the same major or minor, Fastify will always choose the highest compatible with the Accept-Version
header value.
If the request will not have the Accept-Version
header, a 404 error will be returned.
Custom
It's possible to define a custom versioning logic. This can be done through the versioning
configuration, when creating a fastify server instance.