Validation-and-Serialization
Validation and Serialization
Fastify uses a schema-based approach. We recommend using JSON Schema to validate routes and serialize outputs. Fastify compiles the schema into a highly performant function.
Validation is only attempted if the content type is application/json.
All examples use the JSON Schema Draft 7 specification.
⚠ Warning: Treat schema definitions as application code. Validation and serialization features use
new Function(), which is unsafe with user-provided schemas. See Ajv and fast-json-stringify for details.Whilst Fastify supports the
$asyncAjv feature, it should not be used for initial validation. Accessing databases during validation may lead to Denial of Service attacks. Use Fastify's hooks likepreHandlerforasynctasks after validation.When using custom validators with async
preValidationhooks, validators must return{error}objects instead of throwing errors. Throwing errors from custom validators will cause unhandled promise rejections that crash the application when combined with async hooks. See the custom validator examples below for the correct pattern.
Core concepts
Validation and serialization are handled by two customizable dependencies:
- Ajv v8 for request validation
- fast-json-stringify for response body serialization
These dependencies share only the JSON schemas added to Fastify's instance via
.addSchema(schema).
Adding a shared schema
The addSchema API allows adding multiple schemas to the Fastify instance for
reuse throughout the application. This API is encapsulated.
Shared schemas can be reused with the JSON Schema
$ref
keyword. Here is an overview of how references work:
myField: { $ref: '#foo' }searches for$id: '#foo'in the current schemamyField: { $ref: '#/definitions/foo' }searches fordefinitions.fooin the current schemamyField: { $ref: 'http://url.com/sh.json#' }searches for a shared schema with$id: 'http://url.com/sh.json'myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }searches for a shared schema with$id: 'http://url.com/sh.json'and usesdefinitions.foomyField: { $ref: 'http://url.com/sh.json#foo' }searches for a shared schema with$id: 'http://url.com/sh.json'and looks for$id: '#foo'within it
Simple usage:
fastify.addSchema({
$id: 'http://example.com/',
type: 'object',
properties: {
hello: { type: 'string' }
}
})
fastify.post('/', {
handler () {},
schema: {
body: {
type: 'array',
items: { $ref: 'http://example.com#/properties/hello' }
}
}
})
$ref as root reference:
fastify.addSchema({
$id: 'commonSchema',
type: 'object',
properties: {
hello: { type: 'string' }
}
})
fastify.post('/', {
handler () {},
schema: {
body: { $ref: 'commonSchema#' },
headers: { $ref: 'commonSchema#' }
}
})
Retrieving the shared schemas
If the validator and serializer are customized, .addSchema is not useful since
Fastify no longer controls them. To access schemas added to the Fastify instance,
use .getSchemas():
fastify.addSchema({
$id: 'schemaId',
type: 'object',
properties: {
hello: { type: 'string' }
}
})
const mySchemas = fastify.getSchemas()
const mySchema = fastify.getSchema('schemaId')
The getSchemas function is encapsulated and returns shared schemas available
in the selected scope:
fastify.addSchema({ $id: 'one', my: 'hello' })
// will return only `one` schema
fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
fastify.register((instance, opts, done) => {
instance.addSchema({ $id: 'two', my: 'ciao' })
// will return `one` and `two` schemas
instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })
instance.register((subinstance, opts, done) => {
subinstance.addSchema({ $id: 'three', my: 'hola' })
// will return `one`, `two` and `three`
subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })
done()
})
done()
})
Validation
Route validation relies on Ajv v8, a high-performance JSON Schema validator. To validate input, add the required fields to the route schema.
Supported validations include:
body: validates the request body for POST, PUT, or PATCH methods.querystringorquery: validates the query string.params: validates the route parameters.headers: validates the request headers.
Validations can be a complete JSON Schema object with a type of 'object' and
a 'properties' object containing parameters, or a simpler variation listing
parameters at the top level.
ℹ For using the latest Ajv (v8), refer to the
schemaControllersection.
Example:
const bodyJsonSchema = {
type: 'object',
required: ['requiredKey'],
properties: {
someKey: { type: 'string' },
someOtherKey: { type: 'number' },
requiredKey: {
type: 'array',
maxItems: 3,
items: { type: 'integer' }
},
nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true }
multipleTypesKey: { type: ['boolean', 'number'] },
multipleRestrictedTypesKey: {
oneOf: [
{ type: 'string', maxLength: 5 },
{ type: 'number', minimum: 10 }
]
},
enumKey: {
type: 'string',
enum: ['John', 'Foo']
},
notTypeKey: {
not: { type: 'array' }
}
}
}
const queryStringJsonSchema = {
type: 'object',
properties: {
name: { type: 'string' },
excitement: { type: 'integer' }
}
}
const paramsJsonSchema = {
type: 'object',
properties: {
par1: { type: 'string' },
par2: { type: 'number' }
}
}
const headersJsonSchema = {
type: 'object',
properties: {
'x-foo': { type: 'string' }
},
required: ['x-foo']
}
const schema = {
body: bodyJsonSchema,
querystring: queryStringJsonSchema,
params: paramsJsonSchema,
headers: headersJsonSchema
}
fastify.post('/the/url', { schema }, handler)
For body schema, it is further possible to differentiate the schema per content
type by nesting the schemas inside content property. The schema validation
will be applied based on the Content-Type header in the request.
fastify.post('/the/url', {
schema: {
body: {
content: {
'application/json': {
schema: { type: 'object' }
},
'text/plain': {
schema: { type: 'string' }
}
// Other content types will not be validated
}
}
}
}, handler)
Note that Ajv will try to coerce values to
the types specified in the schema type keywords, both to pass validation and
to use the correctly typed data afterwards.
The Ajv default configuration in Fastify supports coercing array parameters in
querystring. Example:
const opts = {
schema: {
querystring: {
type: 'object',
properties: {
ids: {
type: 'array',
default: []
},
},
}
}
}
fastify.get('/', opts, (request, reply) => {
reply.send({ params: request.query }) // echo the querystring
})
fastify.listen({ port: 3000 }, (err) => {
if (err) throw err
})
curl -X GET "http://localhost:3000/?ids=1
{"params":{"ids":["1"]}}
A custom schema validator can be specified for each parameter type (body, querystring, params, headers).
For example, the following code disables type coercion only for the body
parameters, changing the Ajv default options:
const schemaCompilers = {
body: new Ajv({
removeAdditional: false,
coerceTypes: false,
allErrors: true
}),
params: new Ajv({
removeAdditional: false,
coerceTypes: true,
allErrors: true
}),
querystring: new Ajv({
removeAdditional: false,
coerceTypes: true,
allErrors: true
}),
headers: new Ajv({
removeAdditional: false,
coerceTypes: true,
allErrors: true
})
}
server.setValidatorCompiler(req => {
if (!req.httpPart) {
throw new Error('Missing httpPart')
}
const compiler = schemaCompilers[req.httpPart]
if (!compiler) {
throw new Error(`Missing compiler for ${req.httpPart}`)
}
return compiler.compile(req.schema)
})
For more information, see Ajv Coercion.
Ajv Plugins
A list of plugins can be provided for use with the default ajv instance.
Ensure the plugin is compatible with the Ajv version shipped within Fastify.
Refer to
ajv optionsto check plugins format.
const fastify = require('fastify')({
ajv: {
plugins: [
require('ajv-merge-patch')
]
}
})
fastify.post('/', {
handler (req, reply) { reply.send({ ok: 1 }) },
schema: {
body: {
$patch: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: [
{
op: 'add',
path: '/properties/q',
value: { type: 'number' }
}
]
}
}
}
})
fastify.post('/foo', {
handler (req, reply) { reply.send({ ok: 1 }) },
schema: {
body: {
$merge: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: {
required: ['q']
}
}
}
}
})