This article will explain all you need to know to master the new validation and serialization with Fastify v3!
These components have been refactored and improved to give to devs more control, so let's explore them.
Architecture
Here it is the startup of a route with some validation and serialization configured:
It is not shown in the figure, but all this pipeline is fully encapsulated in the Fastify's style!
Validator Compiler
The validation is the process of strict validating the request's input, without async logic like DB access.
It will check if the request's parts are like you expect.
Those parts are:
- headers
- params (aka path parameters)
- body
- querystring (aka query parameters)
Since in Fastify all these domains are converted to JSON, it can be validated through the definition of a JSON Schema (draft-07)!
Fastify will use the Validator Compiler only in the startup phase to create a validation function starting from the Schemas provided to the route definition.
The validation function is attached to the route context, and then it will be executed for every new request when needed.
How to use it
The default Validator Compiler is ajv
with this setup.
It can be customized thanks to the ajv option
at the server declaration.
The default validator can be changed to use another validation module. There is the new setValidatorCompiler
!
As showed in the image, this component will be called for every body
, querystring
, params
and headers
schemas defined in the route!
Now, with the new API interface, it is possible to understand what schema is going to be compiled:
fastify.setValidatorCompiler(function (schemaDefinition) {
const { schema, method, url, httpPart } = schemaDefinition
// schema: the JSON schema that has been settle on the route
// method: the HTTP method of the route
// url: the complete route's url
// httpPart: it can be one of `body`, `querystring`, `params` and `headers`
// all the JSON schemas added to the fastify's context can be read by:
const schemas = fastify.getSchemas()
// it is necessary to return a function, that will be called for every request!
return function validateHttpThePart (data) {
// data is a JSON that represents the incoming request's `httpPart`
// this is a sync function that must return:
// in case of success
return { value: { this: 'will be the `httpPart` object in the handler' } }
// or in case of error
return { error: { this: 'will be a 400' } }
}
})
There are many examples with other validators like Joi
or yup
in the official documentation.
Serializer Compiler
The serialization is the process of transforming an object to a byte stream to pipe in the response.
In our case, we will serialize the JSONs in strings to reply to the client's requests.
The main advantages of creating a response schema are:
- performance: speed up the serialization process
- security: you are not returning data that you don't want to
- documentation: generation of OpenAPI docs site
Note that the serialization will not apply any validation to the data returned to the clients.
It will provide:
- format definition of the JSON
- coerce types of the JSON
properties
Fastify will use the Compiler only in the startup phase to create a serialization function starting from the Schemas provided to the route's response
definition, and it will be linked to the route's context.
How to use it
Under the hood, the default Serializer Compiler is fast-json-stringify
!
It doesn't expose any options in the Fastify's server options.
In v3
it is customizable via setSerializerCompiler
!
Here an example:
fastify.setSerializerCompiler(function (schemaDefinition) {
const { schema, method, url, httpStatus } = schemaDefinition
// schema: the JSON schema that has been settle on the route
// method: the HTTP method of the route
// url: the complete route's url
// httpStatus: it is the status settle in the route's `schema.response` option, usually it will be '2xx'
// return a sync function
return function (data) {
// data is the JSON payload
// now we must return the string that will be sent to the client's request
return JSON.stringify(data)
}
})
Note: in Fastify, there is the replySerializer
.
It has the priority over the SerializerCompiler
in the request's lifecycle, and it will not benefit from the JSON schema boost!
Migrating guidelines
To update your validation and serialization code from v2
to v3
you need to:
- update all the schemas that use
shared schema replace-way
to the standard$ref-way
. - replace
-
setSchemaCompiler()
tosetValidatorCompiler()
in the fastify's instances -
schemaCompiler
tovalidatorCompiler
in the routes' definitions
-
- delete the
setSchemaResolver()
Update your schemas
Hard things first: to update your schema to remove the shared schema replace-way
you have three options:
- use the new
fastify.getSchema(id)
- change the schemas to use
$ref
keyword - mix the first and second based on your code
Using the fastify.getSchema(id)
is the easiest solution if you have the fastify
server instance at your disposal and not too many cyclic replace-way
keywords.
fastify.addSchema({
$id: 'greetings',
type: 'object',
properties: {
hello: { type: 'string' }
}
})
fastify.route({
method: 'POST',
url: '/',
schema: {
- body: 'greetings#'
+ body: fastify.getSchema('greetings')
},
handler: () => {}
})
Updating the schemas to $ref
should be the preferred solution since it is 100% standard and the fastest.
In the official docs there are many
examples of how to use $ref
.
It will be like this:
fastify.addSchema({
$id: 'greetings',
type: 'object',
properties: {
hello: { type: 'string' }
}
})
fastify.route({
method: 'POST',
url: '/',
schema: {
- body: 'greetings#'
+ body: { $ref: 'greetings#' }
},
handler: () => {}
})
Or you may mix these two options based on your needs.
From schemaCompiler
to validatorCompiler
The API for this function has changed, so you need to rename:
- from
schemaCompiler
tovalidatorCompiler
in the route's configuration - from
setSchemaCompiler
tosetValidatorCompiler
in the fastify's instance inizialization - all the function's parameters must be changed like this:
-fastify.setSchemaCompiler(function (schema) {
- return ajv.compile(schema)
+fastify.setValidatorCompiler(function (schemaDefinition) {
+ const { schema, method, url, httpPart } = schemaDefinition
+ return ajv.compile(schema)
})
Or in a stricter sentence:
fastify.post('/the/url', {
schema: {
body: joiBodySchema
},
- schemaCompiler: schema => data => Joi.validate(data, schema)
+ validatorCompiler: ({ schema }) => data => Joi.validate(data, schema)
}, handler)
Delete schemaResolver
Dropping the shared schema replace-way
let fastify to avoid to read and process the JSON schemas, so
it doesn't need to resolve external schemas $id
.
Thank you for reading!
For more Fastify content follow me on twitter!