A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://fastify.dev/docs/latest/Reference/Validation-and-Serialization/ below:

Validation-and-Serialization | Fastify

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 $async Ajv feature, it should not be used for initial validation. Accessing databases during validation may lead to Denial of Service attacks. Use Fastify's hooks like preHandler for async tasks after validation.

When using custom validators with async preValidation hooks, 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:

These dependencies share only the JSON schemas added to Fastify's instance via .addSchema(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:

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#' }
}
})

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' })

fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })

fastify.register((instance, opts, done) => {
instance.addSchema({ $id: 'two', my: 'ciao' })

instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })

instance.register((subinstance, opts, done) => {
subinstance.addSchema({ $id: 'three', my: 'hola' })

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:

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 schemaController section.

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'] },
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' }
}

}
}
}
}, 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 })
})

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 options to 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']
}
}
}
}
})
Validator Compiler

The validatorCompiler is a function that returns a function to validate the body, URL parameters, headers, and query string. The default validatorCompiler returns a function that implements the ajv validation interface. Fastify uses it internally to speed up validation.

Fastify's baseline ajv configuration is:

{
coerceTypes: 'array',
useDefaults: true,
removeAdditional: true,
uriResolver: require('fast-uri'),
addUsedSchema: false,


allErrors: false
}

Modify the baseline configuration by providing ajv.customOptions to the Fastify factory.

To change or set additional config options, create a custom instance and override the existing one:

const fastify = require('fastify')()
const Ajv = require('ajv')
const ajv = new Ajv({
removeAdditional: 'all',
useDefaults: true,
coerceTypes: 'array',


})
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
return ajv.compile(schema)
})

ℹ️ Note: When using a custom validator instance, add schemas to the validator instead of Fastify. Fastify's addSchema method will not recognize the custom validator.

Using other validation libraries

The setValidatorCompiler function allows substituting ajv with other JavaScript validation libraries like joi or yup, or a custom one:

const Joi = require('joi')

fastify.setValidatorCompiler(({ schema }) => {
return (data) => {
try {
const { error, value } = schema.validate(data)
if (error) {
return { error }
}
return { value }
} catch (e) {
return { error: e }
}
}
})

fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
}
}, handler)
const yup = require('yup')

const yupOptions = {
strict: false,
abortEarly: false,
stripUnknown: true,
recursive: true
}

fastify.post('/the/url', {
schema: {
body: yup.object({
age: yup.number().integer().required(),
sub: yup.object().shape({
name: yup.string().required()
}).required()
})
},
validatorCompiler: ({ schema, method, url, httpPart }) => {
return function (data) {


try {
const result = schema.validateSync(data, yupOptions)
return { value: result }
} catch (e) {
return { error: e }
}
}
}
}, handler)
Custom Validator Best Practices

When implementing custom validators, follow these patterns to ensure compatibility with all Fastify features:

** Always return objects, never throw:**

return { value: validatedData }  
return { error: validationError }

** Use try-catch for safety:**

fastify.setValidatorCompiler(({ schema }) => {
return (data) => {
try {

const result = schema.validate(data)
if (result.error) {
return { error: result.error }
}
return { value: result.value }
} catch (e) {

return { error: e }
}
}
})

This pattern ensures validators work correctly with both sync and async preValidation hooks, preventing unhandled promise rejections that can crash an application.

.statusCode property

All validation errors have a .statusCode property set to 400, ensuring the default error handler sets the response status code to 400.

fastify.setErrorHandler(function (error, request, reply) {
request.log.error(error, `This error has status code ${error.statusCode}`)
reply.status(error.statusCode).send(error)
})
Validation messages with other validation libraries

Fastify's validation error messages are tightly coupled to the default validation engine: errors returned from ajv are eventually run through the schemaErrorFormatter function which builds human-friendly error messages. However, the schemaErrorFormatter function is written with ajv in mind. This may result in odd or incomplete error messages when using other validation libraries.

To circumvent this issue, there are two main options:

  1. Ensure the validation function (returned by the custom schemaCompiler) returns errors in the same structure and format as ajv.
  2. Use a custom errorHandler to intercept and format custom validation errors.

Fastify adds two properties to all validation errors to help write a custom errorHandler:

A contrived example of such a custom errorHandler handling validation errors is shown below:

const errorHandler = (error, request, reply) => {
const statusCode = error.statusCode
let response

const { validation, validationContext } = error


if (validation) {
response = {

message: `A validation error occurred when validating the ${validationContext}...`,

errors: validation
}
} else {
response = {
message: 'An error occurred...'
}
}




reply.status(statusCode).send(response)
}
Serialization

Fastify uses fast-json-stringify to send data as JSON if an output schema is provided in the route options. Using an output schema can drastically increase throughput and help prevent accidental disclosure of sensitive information.

Example:

const schema = {
response: {
200: {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
}
}
}

fastify.post('/the/url', { schema }, handler)

The response schema is based on the status code. To use the same schema for multiple status codes, use '2xx' or default, for example:

const schema = {
response: {
default: {
type: 'object',
properties: {
error: {
type: 'boolean',
default: true
}
}
},
'2xx': {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
},
201: {

value: { type: 'string' }
}
}
}

fastify.post('/the/url', { schema }, handler)

A specific response schema can be defined for different content types. For example:

const schema = {
response: {
200: {
description: 'Response schema that support different content types'
content: {
'application/json': {
schema: {
name: { type: 'string' },
image: { type: 'string' },
address: { type: 'string' }
}
},
'application/vnd.v1+json': {
schema: {
type: 'array',
items: { $ref: 'test' }
}
}
}
},
'3xx': {
content: {
'application/vnd.v2+json': {
schema: {
fullName: { type: 'string' },
phone: { type: 'string' }
}
}
}
},
default: {
content: {

'*/*': {
schema: {
desc: { type: 'string' }
}
}
}
}
}
}

fastify.post('/url', { schema }, handler)
Serializer Compiler

The serializerCompiler returns a function that must return a string from an input object. When defining a response JSON Schema, change the default serialization method by providing a function to serialize each route.

fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
return data => JSON.stringify(data)
})

fastify.get('/user', {
handler (req, reply) {
reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' })
},
schema: {
response: {
'2xx': {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' }
}
}
}
}
})

To set a custom serializer in a specific part of the code, use reply.serializer(...).

Error Handling

When schema validation fails for a request, Fastify will automatically return a status 400 response including the result from the validator in the payload. For example, if the following schema is used for a route:

const schema = {
body: {
type: 'object',
properties: {
name: { type: 'string' }
},
required: ['name']
}
}

If the request fails to satisfy the schema, the route will return a response with the following payload:

{
"statusCode": 400,
"error": "Bad Request",
"message": "body should have required property 'name'"
}

To handle errors inside the route, specify the attachValidation option. If there is a validation error, the validationError property of the request will contain the Error object with the raw validation result as shown below:

const fastify = Fastify()

fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
if (req.validationError) {

reply.code(400).send(req.validationError)
}
})
schemaErrorFormatter

To format errors, provide a sync function that returns an error as the schemaErrorFormatter option when instantiating Fastify. The context function will be the Fastify server instance.

errors is an array of Fastify schema errors FastifySchemaValidationError. dataVar is the currently validated part of the schema (params, body, querystring, headers).

const fastify = Fastify({
schemaErrorFormatter: (errors, dataVar) => {

return new Error(myErrorMessage)
}
})


fastify.setSchemaErrorFormatter(function (errors, dataVar) {
this.log.error({ err: errors }, 'Validation failed')

return new Error(myErrorMessage)
})

Use setErrorHandler to define a custom response for validation errors such as:

fastify.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(422).send(new Error('validation failed'))
}
})

For custom error responses in the schema, see ajv-errors. Check out the example usage.

Install version 1.0.1 of ajv-errors, as later versions are not compatible with AJV v6 (the version shipped by Fastify v3).

Below is an example showing how to add custom error messages for each property of a schema by supplying custom AJV options. Inline comments in the schema describe how to configure it to show a different error message for each case:

const fastify = Fastify({
ajv: {
customOptions: {
jsonPointers: true,

allErrors: true
},
plugins: [
require('ajv-errors')
]
}
})

const schema = {
body: {
type: 'object',
properties: {
name: {
type: 'string',
errorMessage: {
type: 'Bad name'
}
},
age: {
type: 'number',
errorMessage: {
type: 'Bad age',
min: 'Too young'
}
}
},
required: ['name', 'age'],
errorMessage: {
required: {
name: 'Why no name!',
age: 'Why no age!'
}
}
}
}

fastify.post('/', { schema, }, (request, reply) => {
reply.send({
hello: 'world'
})
})

To return localized error messages, see ajv-i18n.

const localize = require('ajv-i18n')

const fastify = Fastify()

const schema = {
body: {
type: 'object',
properties: {
name: {
type: 'string',
},
age: {
type: 'number',
}
},
required: ['name', 'age'],
}
}

fastify.setErrorHandler(function (error, request, reply) {
if (error.validation) {
localize.ru(error.validation)
reply.status(400).send(error.validation)
return
}
reply.send(error)
})
JSON Schema support

JSON Schema provides utilities to optimize schemas. Combined with Fastify's shared schema, all schemas can be easily reused.

Use Case Validator Serializer $ref to $id ️️✔️ ✔️ $ref to /definitions ✔️ ✔️ $ref to shared schema $id ✔️ ✔️ $ref to shared schema /definitions ✔️ ✔️ Examples Usage of $ref to $id in same JSON Schema
const refToId = {
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { type: 'string' }
}
}
},
properties: {
home: { $ref: '#address' },
work: { $ref: '#address' }
}
}
Usage of $ref to /definitions in same JSON Schema
const refToDefinitions = {
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { type: 'string' }
}
}
},
properties: {
home: { $ref: '#/definitions/foo' },
work: { $ref: '#/definitions/foo' }
}
}
fastify.addSchema({
$id: 'http://foo/common.json',
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { type: 'string' }
}
}
}
})

const refToSharedSchemaId = {
type: 'object',
properties: {
home: { $ref: 'http://foo/common.json#address' },
work: { $ref: 'http://foo/common.json#address' }
}
}
fastify.addSchema({
$id: 'http://foo/shared.json',
type: 'object',
definitions: {
foo: {
type: 'object',
properties: {
city: { type: 'string' }
}
}
}
})

const refToSharedSchemaDefinitions = {
type: 'object',
properties: {
home: { $ref: 'http://foo/shared.json#/definitions/foo' },
work: { $ref: 'http://foo/shared.json#/definitions/foo' }
}
}
Resources

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4