1 | <h1 align="center">Fastify</h1>
|
2 |
|
3 | ## Validation and Serialization
|
4 | Fastify uses a schema-based approach, and even if it is not mandatory we recommend using [JSON Schema](http://json-schema.org/) to validate your routes and serialize your outputs. Internally, Fastify compiles the schema into a highly performant function.
|
5 |
|
6 | > ## ⚠ Security Notice
|
7 | > As both validation and serialization features dynamically evaluate
|
8 | > code with `new Function()`, it is not safe to use them with
|
9 | > user-provided data. See [Ajv](http://npm.im/ajv) and
|
10 | > [fast-json-stringify](http://npm.im/fast-json-stringify) for more
|
11 | > details.
|
12 |
|
13 | <a name="validation"></a>
|
14 | ### Validation
|
15 | The route validation internally relies upon [Ajv](https://www.npmjs.com/package/ajv), which is a high-performance JSON schema validator. Validating the input is very easy: just add the fields that you need inside the route schema, and you are done! The supported validations are:
|
16 | - `body`: validates the body of the request if it is a POST or a PUT.
|
17 | - `querystring`: validates the query string. This can be a complete JSON Schema object (with a `type` property of `'object'` and a `'properties'` object containing parameters) or a simpler variation in which the `type` and `properties` attributes are forgone and the query parameters are listed at the top level (see the example below).
|
18 | - `params`: validates the route params.
|
19 | - `headers`: validates the request headers.
|
20 |
|
21 | Example:
|
22 | ```js
|
23 | const bodyJsonSchema = {
|
24 | type: 'object',
|
25 | required: ['requiredKey'],
|
26 | properties: {
|
27 | someKey: { type: 'string' },
|
28 | someOtherKey: { type: 'number' },
|
29 | requiredKey: {
|
30 | type: 'array',
|
31 | maxItems: 3,
|
32 | items: { type: 'integer' }
|
33 | },
|
34 | nullableKey: { type: ['number', 'null'] },
|
35 | multipleTypesKey: { type: ['boolean', 'number'] },
|
36 | multipleRestrictedTypesKey: {
|
37 | oneOf: [
|
38 | { type: 'string', maxLength: 5 },
|
39 | { type: 'number', minimum: 10 }
|
40 | ]
|
41 | },
|
42 | enumKey: {
|
43 | type: 'string',
|
44 | enum: ['John', 'Foo']
|
45 | },
|
46 | notTypeKey: {
|
47 | not: { type: 'array' }
|
48 | }
|
49 | }
|
50 | }
|
51 |
|
52 | const queryStringJsonSchema = {
|
53 | name: { type: 'string' },
|
54 | excitement: { type: 'integer' }
|
55 | }
|
56 |
|
57 | const paramsJsonSchema = {
|
58 | type: 'object',
|
59 | properties: {
|
60 | par1: { type: 'string' },
|
61 | par2: { type: 'number' }
|
62 | }
|
63 | }
|
64 |
|
65 | const headersJsonSchema = {
|
66 | type: 'object',
|
67 | properties: {
|
68 | 'x-foo': { type: 'string' }
|
69 | },
|
70 | required: ['x-foo']
|
71 | }
|
72 |
|
73 | const schema = {
|
74 | body: bodyJsonSchema,
|
75 |
|
76 | querystring: queryStringJsonSchema,
|
77 |
|
78 | params: paramsJsonSchema,
|
79 |
|
80 | headers: headersJsonSchema
|
81 | }
|
82 |
|
83 | fastify.post('/the/url', { schema }, handler)
|
84 | ```
|
85 | *Note that Ajv will try to [coerce](https://github.com/epoberezkin/ajv#coercing-data-types) the values to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards.*
|
86 |
|
87 | <a name="shared-schema"></a>
|
88 | #### Adding a shared schema
|
89 | Thanks to the `addSchema` API, you can add multiple schemas to the Fastify instance and then reuse them in multiple parts of your application. As usual, this API is encapsulated.
|
90 |
|
91 | There are two ways to reuse your shared schemas:
|
92 | + **`$ref-way`**: as described in the [standard](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8),
|
93 | you can refer to an external schema. To use it you have to `addSchema` with a valid `$id` absolute URI.
|
94 |
|
95 | ```js
|
96 | fastify.addSchema({
|
97 | $id: 'http://example.com/common.json',
|
98 | type: 'object',
|
99 | properties: {
|
100 | hello: { type: 'string' }
|
101 | }
|
102 | })
|
103 |
|
104 | fastify.route({
|
105 | method: 'POST',
|
106 | url: '/',
|
107 | schema: {
|
108 | body: {
|
109 | type: 'array',
|
110 | items: { $ref: 'http://example.com/common.json#/properties/hello' }
|
111 | }
|
112 | },
|
113 | handler: () => {}
|
114 | })
|
115 | ```
|
116 |
|
117 | + **`replace-way`**: this is a Fastify utility that lets you to substitute some fields with a shared schema.
|
118 | To use it you have to `addSchema` with an `$id` having a relative URI fragment which is a simple string that
|
119 | applies only to alphanumeric chars `[A-Za-z0-9]`.
|
120 |
|
121 | ```js
|
122 | const fastify = require('fastify')()
|
123 |
|
124 | fastify.addSchema({
|
125 | $id: 'greetings',
|
126 | type: 'object',
|
127 | properties: {
|
128 | hello: { type: 'string' }
|
129 | }
|
130 | })
|
131 |
|
132 | fastify.route({
|
133 | method: 'POST',
|
134 | url: '/',
|
135 | schema: {
|
136 | body: 'greetings#'
|
137 | },
|
138 | handler: () => {}
|
139 | })
|
140 |
|
141 | fastify.register((instance, opts, next) => {
|
142 |
|
143 | /**
|
144 | * In children's scope can use schemas defined in upper scope like 'greetings'.
|
145 | * Parent scope can't use the children schemas.
|
146 | */
|
147 | instance.addSchema({
|
148 | $id: 'framework',
|
149 | type: 'object',
|
150 | properties: {
|
151 | fastest: { type: 'string' },
|
152 | hi: 'greetings#'
|
153 | }
|
154 | })
|
155 |
|
156 | instance.route({
|
157 | method: 'POST',
|
158 | url: '/sub',
|
159 | schema: {
|
160 | body: 'framework#'
|
161 | },
|
162 | handler: () => {}
|
163 | })
|
164 |
|
165 | next()
|
166 | })
|
167 | ```
|
168 |
|
169 | You can use the shared schema everywhere, as top level schema or nested inside other schemas:
|
170 | ```js
|
171 | const fastify = require('fastify')()
|
172 |
|
173 | fastify.addSchema({
|
174 | $id: 'greetings',
|
175 | type: 'object',
|
176 | properties: {
|
177 | hello: { type: 'string' }
|
178 | }
|
179 | })
|
180 |
|
181 | fastify.route({
|
182 | method: 'POST',
|
183 | url: '/',
|
184 | schema: {
|
185 | body: {
|
186 | type: 'object',
|
187 | properties: {
|
188 | greeting: 'greetings#',
|
189 | timestamp: { type: 'number' }
|
190 | }
|
191 | }
|
192 | },
|
193 | handler: () => {}
|
194 | })
|
195 | ```
|
196 |
|
197 | <a name="get-shared-schema"></a>
|
198 | #### Retrieving a copy of shared schemas
|
199 |
|
200 | The function `getSchemas` returns the shared schemas available in the selected scope:
|
201 | ```js
|
202 | fastify.addSchema({ $id: 'one', my: 'hello' })
|
203 | fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
|
204 |
|
205 | fastify.register((instance, opts, next) => {
|
206 | instance.addSchema({ $id: 'two', my: 'ciao' })
|
207 | instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })
|
208 |
|
209 | instance.register((subinstance, opts, next) => {
|
210 | subinstance.addSchema({ $id: 'three', my: 'hola' })
|
211 | subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })
|
212 | next()
|
213 | })
|
214 | next()
|
215 | })
|
216 | ```
|
217 | This example will returns:
|
218 |
|
219 | | URL | Schemas |
|
220 | |-------|---------|
|
221 | | / | one |
|
222 | | /sub | one, two |
|
223 | | /deep | one, two, three |
|
224 |
|
225 | <a name="schema-compiler"></a>
|
226 | #### Schema Compiler
|
227 |
|
228 | The `schemaCompiler` is a function that returns a function that validates the body, url parameters, headers, and query string. The default `schemaCompiler` returns a function that implements the [ajv](https://ajv.js.org/) validation interface. Fastify uses it internally to speed the validation up.
|
229 |
|
230 | Fastify's [baseline ajv configuration](https://github.com/epoberezkin/ajv#options-to-modify-validated-data) is:
|
231 |
|
232 | ```js
|
233 | {
|
234 | removeAdditional: true, // remove additional properties
|
235 | useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
|
236 | coerceTypes: true, // change data type of data to match type keyword
|
237 | allErrors: true // check for all errors
|
238 | }
|
239 | ```
|
240 |
|
241 | This baseline configuration cannot be modified. If you want to change or set additional config options, you will need to create your own instance and override the existing one like:
|
242 |
|
243 | ```js
|
244 | const fastify = require('fastify')()
|
245 | const Ajv = require('ajv')
|
246 | const ajv = new Ajv({
|
247 | // the fastify defaults (if needed)
|
248 | removeAdditional: true,
|
249 | useDefaults: true,
|
250 | coerceTypes: true,
|
251 | allErrors: true
|
252 | // any other options
|
253 | // ...
|
254 | })
|
255 | fastify.setSchemaCompiler(function (schema) {
|
256 | return ajv.compile(schema)
|
257 | })
|
258 |
|
259 | // -------
|
260 | // Alternatively, you can set the schema compiler using the setter property:
|
261 | fastify.schemaCompiler = function (schema) { return ajv.compile(schema) })
|
262 | ```
|
263 |
|
264 | But maybe you want to change the validation library. Perhaps you like `Joi`. In this case, you can use it to validate the url parameters, body, and query string!
|
265 |
|
266 | ```js
|
267 | const Joi = require('joi')
|
268 |
|
269 | fastify.post('/the/url', {
|
270 | schema: {
|
271 | body: Joi.object().keys({
|
272 | hello: Joi.string().required()
|
273 | }).required()
|
274 | },
|
275 | schemaCompiler: schema => data => Joi.validate(data, schema)
|
276 | }, handler)
|
277 | ```
|
278 |
|
279 | In that case the function returned by `schemaCompiler` returns an object like:
|
280 | * `error`: filled with an instance of `Error` or a string that describes the validation error
|
281 | * `value`: the coerced value that passed the validation
|
282 |
|
283 | <a name="serialization"></a>
|
284 | ### Serialization
|
285 | Usually you will send your data to the clients via JSON, and Fastify has a powerful tool to help you, [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify), which is used if you have provided an output schema in the route options. We encourage you to use an output schema, as it will increase your throughput by 100-400% depending on your payload and will prevent accidental disclosure of sensitive information.
|
286 |
|
287 | Example:
|
288 | ```js
|
289 | const schema = {
|
290 | response: {
|
291 | 200: {
|
292 | type: 'object',
|
293 | properties: {
|
294 | value: { type: 'string' },
|
295 | otherValue: { type: 'boolean' }
|
296 | }
|
297 | }
|
298 | }
|
299 | }
|
300 |
|
301 | fastify.post('/the/url', { schema }, handler)
|
302 | ```
|
303 |
|
304 | As you can see, the response schema is based on the status code. If you want to use the same schema for multiple status codes, you can use `'2xx'`, for example:
|
305 | ```js
|
306 | const schema = {
|
307 | response: {
|
308 | '2xx': {
|
309 | type: 'object',
|
310 | properties: {
|
311 | value: { type: 'string' },
|
312 | otherValue: { type: 'boolean' }
|
313 | }
|
314 | },
|
315 | 201: {
|
316 | type: 'object',
|
317 | properties: {
|
318 | value: { type: 'string' }
|
319 | }
|
320 | }
|
321 | }
|
322 | }
|
323 |
|
324 | fastify.post('/the/url', { schema }, handler)
|
325 | ```
|
326 |
|
327 | *If you need a custom serializer in a very specific part of your code, you can set one with `reply.serializer(...)`.*
|
328 |
|
329 | ### Error Handling
|
330 | When schema validation fails for a request, Fastify will automtically return a status 400 response including the result from the validator in the payload. As an example, if you have the following schema for your route
|
331 |
|
332 | ```js
|
333 | const schema = {
|
334 | body: {
|
335 | type: 'object',
|
336 | properties: {
|
337 | name: { type: 'string' }
|
338 | },
|
339 | required: ['name']
|
340 | }
|
341 | }
|
342 | ```
|
343 |
|
344 | and fail to satisfy it, the route will immediately return a response with the following payload
|
345 |
|
346 | ```js
|
347 | {
|
348 | "statusCode": 400,
|
349 | "error": "Bad Request",
|
350 | "message": "body should have required property 'name'"
|
351 | }
|
352 | ```
|
353 |
|
354 | If you want to handle errors inside the route, you can specify the `attachValidation` option for your route. 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
|
355 |
|
356 | ```js
|
357 | const fastify = Fastify()
|
358 |
|
359 | fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
|
360 | if (req.validationError) {
|
361 | // `req.validationError.validation` contains the raw validation error
|
362 | reply.code(400).send(req.validationError)
|
363 | }
|
364 | })
|
365 | ```
|
366 |
|
367 | You can also use [setErrorHandler](https://www.fastify.io/docs/latest/Server/#seterrorhandler) to define a custom response for validation errors such as
|
368 |
|
369 | ```js
|
370 | fastify.setErrorHandler(function (error, request, reply) {
|
371 | if (error.validation) {
|
372 | reply.status(422).send(new Error('validation failed'))
|
373 | }
|
374 | })
|
375 | ```
|
376 |
|
377 | ### JSON Schema and Shared Schema support
|
378 |
|
379 | JSON Schema has some type of utilities in order to optimize your schemas that,
|
380 | in conjuction with the Fastify's shared schema, let you reuse all your schemas easily.
|
381 |
|
382 | | Use Case | Validator | Serializer |
|
383 | |-----------------------------------|-----------|------------|
|
384 | | shared schema | ✔️ | ✔️ |
|
385 | | `$ref` to `$id` | ️️✔️ | ✔️ |
|
386 | | `$ref` to `/definitions` | ✔️ | ✔️ |
|
387 | | `$ref` to shared schema `$id` | ✔️ | ✔️ |
|
388 | | `$ref` to shared schema `/definitions` | ✔️ | ✔️ |
|
389 |
|
390 | #### Examples
|
391 |
|
392 | ```js
|
393 | // Usage of the Shared Schema feature
|
394 | fastify.addSchema({
|
395 | $id: 'sharedAddress',
|
396 | type: 'object',
|
397 | properties: {
|
398 | city: { 'type': 'string' }
|
399 | }
|
400 | })
|
401 |
|
402 | const sharedSchema = {
|
403 | type: 'object',
|
404 | properties: {
|
405 | home: 'sharedAddress#',
|
406 | work: 'sharedAddress#'
|
407 | }
|
408 | }
|
409 | ```
|
410 |
|
411 | ```js
|
412 | // Usage of $ref to $id in same JSON Schema
|
413 | const refToId = {
|
414 | type: 'object',
|
415 | definitions: {
|
416 | foo: {
|
417 | $id: '#address',
|
418 | type: 'object',
|
419 | properties: {
|
420 | city: { 'type': 'string' }
|
421 | }
|
422 | }
|
423 | },
|
424 | properties: {
|
425 | home: { $ref: '#address' },
|
426 | work: { $ref: '#address' }
|
427 | }
|
428 | }
|
429 | ```
|
430 |
|
431 |
|
432 | ```js
|
433 | // Usage of $ref to /definitions in same JSON Schema
|
434 | const refToDefinitions = {
|
435 | type: 'object',
|
436 | definitions: {
|
437 | foo: {
|
438 | $id: '#address',
|
439 | type: 'object',
|
440 | properties: {
|
441 | city: { 'type': 'string' }
|
442 | }
|
443 | }
|
444 | },
|
445 | properties: {
|
446 | home: { $ref: '#/definitions/foo' },
|
447 | work: { $ref: '#/definitions/foo' }
|
448 | }
|
449 | }
|
450 | ```
|
451 |
|
452 | ```js
|
453 | // Usage $ref to a shared schema $id as external schema
|
454 | fastify.addSchema({
|
455 | $id: 'http://foo/common.json',
|
456 | type: 'object',
|
457 | definitions: {
|
458 | foo: {
|
459 | $id: '#address',
|
460 | type: 'object',
|
461 | properties: {
|
462 | city: { 'type': 'string' }
|
463 | }
|
464 | }
|
465 | }
|
466 | })
|
467 |
|
468 | const refToSharedSchemaId = {
|
469 | type: 'object',
|
470 | properties: {
|
471 | home: { $ref: 'http://foo/common.json#address' },
|
472 | work: { $ref: 'http://foo/common.json#address' }
|
473 | }
|
474 | }
|
475 | ```
|
476 |
|
477 |
|
478 | ```js
|
479 | // Usage $ref to a shared schema /definitions as external schema
|
480 | fastify.addSchema({
|
481 | $id: 'http://foo/common.json',
|
482 | type: 'object',
|
483 | definitions: {
|
484 | foo: {
|
485 | type: 'object',
|
486 | properties: {
|
487 | city: { 'type': 'string' }
|
488 | }
|
489 | }
|
490 | }
|
491 | })
|
492 |
|
493 | const refToSharedSchemaDefinitions = {
|
494 | type: 'object',
|
495 | properties: {
|
496 | home: { $ref: 'http://foo/common.json#/definitions/foo' },
|
497 | work: { $ref: 'http://foo/common.json#/definitions/foo' }
|
498 | }
|
499 | }
|
500 | ```
|
501 |
|
502 | <a name="resources"></a>
|
503 | ### Resources
|
504 | - [JSON Schema](http://json-schema.org/)
|
505 | - [Understanding JSON schema](https://spacetelescope.github.io/understanding-json-schema/)
|
506 | - [fast-json-stringify documentation](https://github.com/fastify/fast-json-stringify)
|
507 | - [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md)
|