CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
Validation-and-Serialization.md1016 linesDownload Raw Back to Reference
1<h1 align="center">Fastify</h1>2 3## Validation and Serialization4Fastify uses a schema-based approach, and even if it is not mandatory we5recommend using [JSON Schema](https://json-schema.org/) to validate your routes6and serialize your outputs. Internally, Fastify compiles the schema into a7highly performant function.8 9Validation will only be attempted if the content type is `application-json`, as10described in the documentation for the [content type11parser](./ContentTypeParser.md).12 13All the examples in this section are using the [JSON Schema Draft147](https://json-schema.org/specification-links.html#draft-7) specification.15 16> ## ⚠  Security Notice17> Treat the schema definition as application code. Validation and serialization18> features dynamically evaluate code with `new Function()`, which is not safe to19> use with user-provided schemas. See [Ajv](https://npm.im/ajv) and20> [fast-json-stringify](https://npm.im/fast-json-stringify) for more details.21>22> Regardless the [`$async` Ajv23> feature](https://ajv.js.org/guide/async-validation.html) is supported24> by Fastify, it should not be used as25> part of the first validation strategy. This option is used to access Databases26> and reading them during the validation process may lead to Denial of Service27> Attacks to your application. If you need to run `async` tasks, use [Fastify's28> hooks](./Hooks.md) instead after validation completes, such as `preHandler`.29 30 31### Core concepts32The validation and the serialization tasks are processed by two different, and33customizable, actors:34- [Ajv v8](https://www.npmjs.com/package/ajv) for the validation of a request35- [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for36  the serialization of a response's body37 38These two separate entities share only the JSON schemas added to Fastify's39instance through `.addSchema(schema)`.40 41#### Adding a shared schema42<a id="shared-schema"></a>43 44Thanks to the `addSchema` API, you can add multiple schemas to the Fastify45instance and then reuse them in multiple parts of your application. As usual,46this API is encapsulated.47 48The shared schemas can be reused through the JSON Schema49[**`$ref`**](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8)50keyword. Here is an overview of _how_ references work:51 52+ `myField: { $ref: '#foo' }` will search for field with `$id: '#foo'` inside the53  current schema54+ `myField: { $ref: '#/definitions/foo' }` will search for field55  `definitions.foo` inside the current schema56+ `myField: { $ref: 'http://url.com/sh.json#' }` will search for a shared schema57  added with `$id: 'http://url.com/sh.json'`58+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` will search for59  a shared schema added with `$id: 'http://url.com/sh.json'` and will use the60  field `definitions.foo`61+ `myField: { $ref: 'http://url.com/sh.json#foo' }` will search for a shared62  schema added with `$id: 'http://url.com/sh.json'` and it will look inside of63  it for object with `$id: '#foo'`64 65 66**Simple usage:**67 68```js69fastify.addSchema({70  $id: 'http://example.com/',71  type: 'object',72  properties: {73    hello: { type: 'string' }74  }75})76 77fastify.post('/', {78  handler () {},79  schema: {80    body: {81      type: 'array',82      items: { $ref: 'http://example.com#/properties/hello' }83    }84  }85})86```87 88**`$ref` as root reference:**89 90```js91fastify.addSchema({92  $id: 'commonSchema',93  type: 'object',94  properties: {95    hello: { type: 'string' }96  }97})98 99fastify.post('/', {100  handler () {},101  schema: {102    body: { $ref: 'commonSchema#' },103    headers: { $ref: 'commonSchema#' }104  }105})106```107 108#### Retrieving the shared schemas109<a id="get-shared-schema"></a>110 111If the validator and the serializer are customized, the `.addSchema` method will112not be useful since the actors are no longer controlled by Fastify. To access113the schemas added to the Fastify instance, you can simply use `.getSchemas()`:114 115```js116fastify.addSchema({117  $id: 'schemaId',118  type: 'object',119  properties: {120    hello: { type: 'string' }121  }122})123 124const mySchemas = fastify.getSchemas()125const mySchema = fastify.getSchema('schemaId')126```127 128As usual, the function `getSchemas` is encapsulated and returns the shared129schemas available in the selected scope:130 131```js132fastify.addSchema({ $id: 'one', my: 'hello' })133// will return only `one` schema134fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })135 136fastify.register((instance, opts, done) => {137  instance.addSchema({ $id: 'two', my: 'ciao' })138  // will return `one` and `two` schemas139  instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })140 141  instance.register((subinstance, opts, done) => {142    subinstance.addSchema({ $id: 'three', my: 'hola' })143    // will return `one`, `two` and `three`144    subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })145    done()146  })147  done()148})149```150 151 152### Validation153The route validation internally relies upon [Ajv154v8](https://www.npmjs.com/package/ajv) which is a high-performance JSON Schema155validator. Validating the input is very easy: just add the fields that you need156inside the route schema, and you are done!157 158The supported validations are:159- `body`: validates the body of the request if it is a POST, PUT, or PATCH160  method.161- `querystring` or `query`: validates the query string.162- `params`: validates the route params.163- `headers`: validates the request headers.164 165All the validations can be a complete JSON Schema object (with a `type` property166of `'object'` and a `'properties'` object containing parameters) or a simpler167variation in which the `type` and `properties` attributes are forgone and the168parameters are listed at the top level (see the example below).169 170> ℹ If you need to use the latest version of Ajv (v8) you should read how to do171> it in the [`schemaController`](./Server.md#schema-controller) section.172 173Example:174```js175const bodyJsonSchema = {176  type: 'object',177  required: ['requiredKey'],178  properties: {179    someKey: { type: 'string' },180    someOtherKey: { type: 'number' },181    requiredKey: {182      type: 'array',183      maxItems: 3,184      items: { type: 'integer' }185    },186    nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true }187    multipleTypesKey: { type: ['boolean', 'number'] },188    multipleRestrictedTypesKey: {189      oneOf: [190        { type: 'string', maxLength: 5 },191        { type: 'number', minimum: 10 }192      ]193    },194    enumKey: {195      type: 'string',196      enum: ['John', 'Foo']197    },198    notTypeKey: {199      not: { type: 'array' }200    }201  }202}203 204const queryStringJsonSchema = {205  type: 'object',206  properties: {207    name: { type: 'string' },208    excitement: { type: 'integer' }209  }210}211 212const paramsJsonSchema = {213  type: 'object',214  properties: {215    par1: { type: 'string' },216    par2: { type: 'number' }217  }218}219 220const headersJsonSchema = {221  type: 'object',222  properties: {223    'x-foo': { type: 'string' }224  },225  required: ['x-foo']226}227 228const schema = {229  body: bodyJsonSchema,230  querystring: queryStringJsonSchema,231  params: paramsJsonSchema,232  headers: headersJsonSchema233}234 235fastify.post('/the/url', { schema }, handler)236```237 238For `body` schema, it is further possible to differentiate the schema per content239type by nesting the schemas inside `content` property. The schema validation240will be applied based on the `Content-Type` header in the request.241 242```js243fastify.post('/the/url', {244  schema: {245    body: {246      content: {247        'application/json': {248          schema: { type: 'object' }249        },250        'text/plain': {251          schema: { type: 'string' }252        }253        // Other content types will not be validated254      }255    }256  }257}, handler)258```259 260*Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values261to the types specified in your schema `type` keywords, both to pass the262validation and to use the correctly typed data afterwards.*263 264The Ajv default configuration in Fastify supports coercing array parameters in265`querystring`. Example:266 267```js268const opts = {269  schema: {270    querystring: {271      type: 'object',272      properties: {273        ids: {274          type: 'array',275          default: []276        },277      },278    }279  }280}281 282fastify.get('/', opts, (request, reply) => {283  reply.send({ params: request.query }) // echo the querystring284})285 286fastify.listen({ port: 3000 }, (err) => {287  if (err) throw err288})289```290 291```sh292curl -X GET "http://localhost:3000/?ids=1293 294{"params":{"ids":["1"]}}295```296 297You can also specify a custom schema validator for each parameter type (body,298querystring, params, headers).299 300For example, the following code disable type coercion only for the `body`301parameters, changing the ajv default options:302 303```js304const schemaCompilers = {305  body: new Ajv({306    removeAdditional: false,307    coerceTypes: false,308    allErrors: true309  }),310  params: new Ajv({311    removeAdditional: false,312    coerceTypes: true,313    allErrors: true314  }),315  querystring: new Ajv({316    removeAdditional: false,317    coerceTypes: true,318    allErrors: true319  }),320  headers: new Ajv({321    removeAdditional: false,322    coerceTypes: true,323    allErrors: true324  })325}326 327server.setValidatorCompiler(req => {328    if (!req.httpPart) {329      throw new Error('Missing httpPart')330    }331    const compiler = schemaCompilers[req.httpPart]332    if (!compiler) {333      throw new Error(`Missing compiler for ${req.httpPart}`)334    }335    return compiler.compile(req.schema)336})337```338 339For further information see [here](https://ajv.js.org/coercion.html)340 341#### Ajv Plugins342<a id="ajv-plugins"></a>343 344You can provide a list of plugins you want to use with the default `ajv`345instance. Note that the plugin must be **compatible with the Ajv version shipped346within Fastify**.347 348> Refer to [`ajv options`](./Server.md#ajv) to check plugins format349 350```js351const fastify = require('fastify')({352  ajv: {353    plugins: [354      require('ajv-merge-patch')355    ]356  }357})358 359fastify.post('/', {360  handler (req, reply) { reply.send({ ok: 1 }) },361  schema: {362    body: {363      $patch: {364        source: {365          type: 'object',366          properties: {367            q: {368              type: 'string'369            }370          }371        },372        with: [373          {374            op: 'add',375            path: '/properties/q',376            value: { type: 'number' }377          }378        ]379      }380    }381  }382})383 384fastify.post('/foo', {385  handler (req, reply) { reply.send({ ok: 1 }) },386  schema: {387    body: {388      $merge: {389        source: {390          type: 'object',391          properties: {392            q: {393              type: 'string'394            }395          }396        },397        with: {398          required: ['q']399        }400      }401    }402  }403})404```405 406#### Validator Compiler407<a id="schema-validator"></a>408 409The `validatorCompiler` is a function that returns a function that validates the410body, URL  parameters, headers, and query string. The default411`validatorCompiler` returns a function that implements the412[ajv](https://ajv.js.org/) validation interface. Fastify uses it internally to413speed the validation up.414 415Fastify's [baseline ajv416configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is:417 418```js419{420  coerceTypes: 'array', // change data type of data to match type keyword421  useDefaults: true, // replace missing properties and items with the values from corresponding default keyword422  removeAdditional: true, // remove additional properties if additionalProperties is set to false, see: https://ajv.js.org/guide/modifying-data.html#removing-additional-properties423  uriResolver: require('fast-uri'),424  addUsedSchema: false,425  // Explicitly set allErrors to `false`.426  // When set to `true`, a DoS attack is possible.427  allErrors: false428}429```430 431This baseline configuration can be modified by providing432[`ajv.customOptions`](./Server.md#factory-ajv) to your Fastify factory.433 434If you want to change or set additional config options, you will need to create435your own instance and override the existing one like:436 437```js438const fastify = require('fastify')()439const Ajv = require('ajv')440const ajv = new Ajv({441  removeAdditional: 'all',442  useDefaults: true,443  coerceTypes: 'array',444  // any other options445  // ...446})447fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {448  return ajv.compile(schema)449})450```451_**Note:** If you use a custom instance of any validator (even Ajv), you have to452add schemas to the validator instead of Fastify, since Fastify's default453validator is no longer used, and Fastify's `addSchema` method has no idea what454validator you are using._455 456##### Using other validation libraries457<a id="using-other-validation-libraries"></a>458 459The `setValidatorCompiler` function makes it easy to substitute `ajv` with460almost any JavaScript validation library ([joi](https://github.com/hapijs/joi/),461[yup](https://github.com/jquense/yup/), ...) or a custom one:462 463```js464const Joi = require('joi')465 466fastify.post('/the/url', {467  schema: {468    body: Joi.object().keys({469      hello: Joi.string().required()470    }).required()471  },472  validatorCompiler: ({ schema, method, url, httpPart }) => {473    return data => schema.validate(data)474  }475}, handler)476```477 478```js479const yup = require('yup')480// Validation options to match ajv's baseline options used in Fastify481const yupOptions = {482  strict: false,483  abortEarly: false, // return all errors484  stripUnknown: true, // remove additional properties485  recursive: true486}487 488fastify.post('/the/url', {489  schema: {490    body: yup.object({491      age: yup.number().integer().required(),492      sub: yup.object().shape({493        name: yup.string().required()494      }).required()495    })496  },497  validatorCompiler: ({ schema, method, url, httpPart }) => {498    return function (data) {499      // with option strict = false, yup `validateSync` function returns the500      // coerced value if validation was successful, or throws if validation failed501      try {502        const result = schema.validateSync(data, yupOptions)503        return { value: result }504      } catch (e) {505        return { error: e }506      }507    }508  }509}, handler)510```511 512##### .statusCode property513 514All validation errors will be added a `.statusCode` property set to `400`. This guarantees515that the default error handler will set the status code of the response to `400`.516 517```js518fastify.setErrorHandler(function (error, request, reply) {519  request.log.error(error, `This error has status code ${error.statusCode}`)520  reply.status(error.statusCode).send(error)521})522```523 524##### Validation messages with other validation libraries525 526Fastify's validation error messages are tightly coupled to the default527validation engine: errors returned from `ajv` are eventually run through the528`schemaErrorFormatter` function which is responsible for building human-friendly529error messages. However, the `schemaErrorFormatter` function is written with530`ajv` in mind. As a result, you may run into odd or incomplete error messages531when using other validation libraries.532 533To circumvent this issue, you have 2 main options :534 5351. make sure your validation function (returned by your custom `schemaCompiler`)536   returns errors in the same structure and format as `ajv` (although this could537   prove to be difficult and tricky due to differences between validation538   engines)5392. or use a custom `errorHandler` to intercept and format your 'custom'540   validation errors541 542To help you in writing a custom `errorHandler`, Fastify adds 2 properties to all543validation errors:544 545* `validation`: the content of the `error` property of the object returned by546  the validation function (returned by your custom `schemaCompiler`)547* `validationContext`: the 'context' (body, params, query, headers) where the548  validation error occurred549 550A very contrived example of such a custom `errorHandler` handling validation551errors is shown below:552 553```js554const errorHandler = (error, request, reply) => {555  const statusCode = error.statusCode556  let response557 558  const { validation, validationContext } = error559 560  // check if we have a validation error561  if (validation) {562    response = {563      // validationContext will be 'body' or 'params' or 'headers' or 'query'564      message: `A validation error occurred when validating the ${validationContext}...`,565      // this is the result of your validation library...566      errors: validation567    }568  } else {569    response = {570      message: 'An error occurred...'571    }572  }573 574  // any additional work here, eg. log error575  // ...576 577  reply.status(statusCode).send(response)578}579```580 581### Serialization582<a id="serialization"></a>583 584Usually, you will send your data to the clients as JSON, and Fastify has a585powerful tool to help you,586[fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify), which587is used if you have provided an output schema in the route options. We encourage588you to use an output schema, as it can drastically increase throughput and help589prevent accidental disclosure of sensitive information.590 591Example:592```js593const schema = {594  response: {595    200: {596      type: 'object',597      properties: {598        value: { type: 'string' },599        otherValue: { type: 'boolean' }600      }601    }602  }603}604 605fastify.post('/the/url', { schema }, handler)606```607 608As you can see, the response schema is based on the status code. If you want to609use the same schema for multiple status codes, you can use `'2xx'` or `default`,610for example:611```js612const schema = {613  response: {614    default: {615      type: 'object',616      properties: {617        error: {618          type: 'boolean',619          default: true620        }621      }622    },623    '2xx': {624      type: 'object',625      properties: {626        value: { type: 'string' },627        otherValue: { type: 'boolean' }628      }629    },630    201: {631      // the contract syntax632      value: { type: 'string' }633    }634  }635}636 637fastify.post('/the/url', { schema }, handler)638```639You can even have a specific response schema for different content types.640For example:641```js642const schema = {643  response: {644    200: {645      description: 'Response schema that support different content types'646      content: {647        'application/json': {648          schema: {649            name: { type: 'string' },650            image: { type: 'string' },651            address: { type: 'string' }652          }653        },654        'application/vnd.v1+json': {655          schema: {656            type: 'array',657            items: { $ref: 'test' }658          }659        }660      }661    },662    '3xx': {663      content: {664        'application/vnd.v2+json': {665          schema: {666            fullName: { type: 'string' },667            phone: { type: 'string' }668          }669        }670      }671    },672    default: {673      content: {674        // */* is match-all content-type675        '*/*': {676          schema: {677            desc: { type: 'string' }678          }679        }680      }681    }682  }683}684 685fastify.post('/url', { schema }, handler)686```687 688#### Serializer Compiler689<a id="schema-serializer"></a>690 691The `serializerCompiler` is a function that returns a function that must return692a string from an input object. When you define a response JSON Schema, you can693change the default serialization method by providing a function to serialize694every route where you do.695 696```js697fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {698  return data => JSON.stringify(data)699})700 701fastify.get('/user', {702  handler (req, reply) {703    reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' })704  },705  schema: {706    response: {707      '2xx': {708        type: 'object',709        properties: {710          id: { type: 'number' },711          name: { type: 'string' }712        }713      }714    }715  }716})717```718 719*If you need a custom serializer in a very specific part of your code, you can720set one with [`reply.serializer(...)`](./Reply.md#serializerfunc).*721 722### Error Handling723When schema validation fails for a request, Fastify will automatically return a724status 400 response including the result from the validator in the payload. As725an example, if you have the following schema for your route726 727```js728const schema = {729  body: {730    type: 'object',731    properties: {732      name: { type: 'string' }733    },734    required: ['name']735  }736}737```738 739and fail to satisfy it, the route will immediately return a response with the740following payload741 742```js743{744  "statusCode": 400,745  "error": "Bad Request",746  "message": "body should have required property 'name'"747}748```749 750If you want to handle errors inside the route, you can specify the751`attachValidation` option for your route. If there is a _validation error_, the752`validationError` property of the request will contain the `Error` object with753the raw `validation` result as shown below754 755```js756const fastify = Fastify()757 758fastify.post('/', { schema, attachValidation: true }, function (req, reply) {759  if (req.validationError) {760    // `req.validationError.validation` contains the raw validation error761    reply.code(400).send(req.validationError)762  }763})764```765 766#### `schemaErrorFormatter`767 768If you want to format errors yourself, you can provide a sync function that must769return an error as the `schemaErrorFormatter` option to Fastify when770instantiating. The context function will be the Fastify server instance.771 772`errors` is an array of Fastify schema errors `FastifySchemaValidationError`.773`dataVar` is the currently validated part of the schema. (params | body |774querystring | headers).775 776```js777const fastify = Fastify({778  schemaErrorFormatter: (errors, dataVar) => {779    // ... my formatting logic780    return new Error(myErrorMessage)781  }782})783 784// or785fastify.setSchemaErrorFormatter(function (errors, dataVar) {786  this.log.error({ err: errors }, 'Validation failed')787  // ... my formatting logic788  return new Error(myErrorMessage)789})790```791 792You can also use [setErrorHandler](./Server.md#seterrorhandler) to define a793custom response for validation errors such as794 795```js796fastify.setErrorHandler(function (error, request, reply) {797  if (error.validation) {798     reply.status(422).send(new Error('validation failed'))799  }800})801```802 803If you want a custom error response in the schema without headaches, and804quickly, take a look at805[`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Check out the806[example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js)807usage.808> Make sure to install version 1.0.1 of `ajv-errors`, because later versions of809> it are not compatible with AJV v6 (the version shipped by Fastify v3).810 811Below is an example showing how to add **custom error messages for each812property** of a schema by supplying custom AJV options. Inline comments in the813schema below describe how to configure it to show a different error message for814each case:815 816```js817const fastify = Fastify({818  ajv: {819    customOptions: {820      jsonPointers: true,821      // Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/822      allErrors: true823    },824    plugins: [825      require('ajv-errors')826    ]827  }828})829 830const schema = {831  body: {832    type: 'object',833    properties: {834      name: {835        type: 'string',836        errorMessage: {837          type: 'Bad name'838        }839      },840      age: {841        type: 'number',842        errorMessage: {843          type: 'Bad age', // specify custom message for844          min: 'Too young' // all constraints except required845        }846      }847    },848    required: ['name', 'age'],849    errorMessage: {850      required: {851        name: 'Why no name!', // specify error message for when the852        age: 'Why no age!' // property is missing from input853      }854    }855  }856}857 858fastify.post('/', { schema, }, (request, reply) => {859  reply.send({860    hello: 'world'861  })862})863```864 865If you want to return localized error messages, take a look at866[ajv-i18n](https://github.com/epoberezkin/ajv-i18n)867 868```js869const localize = require('ajv-i18n')870 871const fastify = Fastify()872 873const schema = {874  body: {875    type: 'object',876    properties: {877      name: {878        type: 'string',879      },880      age: {881        type: 'number',882      }883    },884    required: ['name', 'age'],885  }886}887 888fastify.setErrorHandler(function (error, request, reply) {889  if (error.validation) {890    localize.ru(error.validation)891    reply.status(400).send(error.validation)892    return893  }894  reply.send(error)895})896```897 898### JSON Schema support899 900JSON Schema provides utilities to optimize your schemas that, in conjunction901with Fastify's shared schema, let you reuse all your schemas easily.902 903| Use Case                          | Validator | Serializer |904|-----------------------------------|-----------|------------|905| `$ref` to `$id`                   | ️️✔️ | ✔️ |906| `$ref` to `/definitions`          | ✔️ | ✔️ |907| `$ref` to shared schema `$id`          | ✔️ | ✔️ |908| `$ref` to shared schema `/definitions` | ✔️ | ✔️ |909 910#### Examples911 912##### Usage of `$ref` to `$id` in same JSON Schema913 914```js915const refToId = {916  type: 'object',917  definitions: {918    foo: {919      $id: '#address',920      type: 'object',921      properties: {922        city: { type: 'string' }923      }924    }925  },926  properties: {927    home: { $ref: '#address' },928    work: { $ref: '#address' }929  }930}931```932 933 934##### Usage of `$ref` to `/definitions` in same JSON Schema935```js936const refToDefinitions = {937  type: 'object',938  definitions: {939    foo: {940      $id: '#address',941      type: 'object',942      properties: {943        city: { type: 'string' }944      }945    }946  },947  properties: {948    home: { $ref: '#/definitions/foo' },949    work: { $ref: '#/definitions/foo' }950  }951}952```953 954##### Usage `$ref` to a shared schema `$id` as external schema955```js956fastify.addSchema({957  $id: 'http://foo/common.json',958  type: 'object',959  definitions: {960    foo: {961      $id: '#address',962      type: 'object',963      properties: {964        city: { type: 'string' }965      }966    }967  }968})969 970const refToSharedSchemaId = {971  type: 'object',972  properties: {973    home: { $ref: 'http://foo/common.json#address' },974    work: { $ref: 'http://foo/common.json#address' }975  }976}977```978 979##### Usage `$ref` to a shared schema `/definitions` as external schema980```js981fastify.addSchema({982  $id: 'http://foo/shared.json',983  type: 'object',984  definitions: {985    foo: {986      type: 'object',987      properties: {988        city: { type: 'string' }989      }990    }991  }992})993 994const refToSharedSchemaDefinitions = {995  type: 'object',996  properties: {997    home: { $ref: 'http://foo/shared.json#/definitions/foo' },998    work: { $ref: 'http://foo/shared.json#/definitions/foo' }999  }1000}1001```1002 1003### Resources1004<a id="resources"></a>1005 1006- [JSON Schema](https://json-schema.org/)1007- [Understanding JSON1008  Schema](https://spacetelescope.github.io/understanding-json-schema/)1009- [fast-json-stringify1010  documentation](https://github.com/fastify/fast-json-stringify)1011- [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md)1012- [Ajv i18n](https://github.com/epoberezkin/ajv-i18n)1013- [Ajv custom errors](https://github.com/epoberezkin/ajv-errors)1014- Custom error handling with core methods with error file dumping1015  [example](https://github.com/fastify/example/tree/master/validation-messages)1016