Validation and Serialization in Fastify v3
Tags: fastify, node, schema, api
Published: 2020-04-24 18:30:25

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:

![startup architecture](https://dev-to-uploads.s3.amazonaws.com/i/cry3ng3nc35w3y16camq.png)

It is not shown in the figure, but all this pipeline is [fully encapsulated](https://github.com/fastify/fastify/blob/master/docs/Getting-Started.md#loading-order-of-your-plugins) 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](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md).

### How to use it

The default Validator Compiler is [`ajv`](https://www.npmjs.com/package/ajv) with [this setup](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#schema-validator).
It can be customized thanks to the [`ajv option`](https://github.com/fastify/fastify/blob/master/docs/Server.md#factory-ajv) at the server declaration.

The default validator can be changed to use another validation module. There is the new [`setValidatorCompiler`](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#schema-validator)!

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:

```js
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](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#using-other-validation-libraries) with other validators like [`Joi`](https://www.npmjs.com/package/@hapi/joi) or [`yup`](https://www.npmjs.com/package/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`](https://www.npmjs.com/package/fast-json-stringify)!
It doesn't expose any options in the Fastify's server options.
In `v3` it is customizable via [`setSerializerCompiler`](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#schema-serializer)!

Here an example:

```js
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`](https://github.com/fastify/fastify/blob/master/docs/Server.md#set-reply-serializer).
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:

1. update all the schemas that use [`shared schema replace-way`](https://github.com/fastify/fastify/blob/v2.13.0/docs/Validation-and-Serialization.md#adding-a-shared-schema) to the standard `$ref-way`.
1. replace 
  - `setSchemaCompiler()` to `setValidatorCompiler()` in the fastify's instances
  - `schemaCompiler` to `validatorCompiler` in the routes' definitions
1. delete the `setSchemaResolver()`

### Update your schemas

Hard things first: to update your schema to remove the `shared schema replace-way` you have three options:

1. use the new [`fastify.getSchema(id)`](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#get-shared-schema)
1. change the schemas to use [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref) keyword
1. mix the first and second based on your code

Using the [`fastify.getSchema(id)`](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#get-shared-schema)
is the easiest solution if you have the `fastify` server instance at your disposal and not too many cyclic `replace-way` keywords.

```diff
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](https://github.com/fastify/fastify/blob/next/docs/Validation-and-Serialization.md#examples) there are many
examples of how to use `$ref`.

It will be like this:

```diff
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` to `validatorCompiler` in the route's configuration
- from `setSchemaCompiler` to `setValidatorCompiler` in the fastify's instance inizialization
- all the function's parameters must be changed like this:

```diff
-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:

```diff
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](https://twitter.com/ManuEomm)!