UNPKG

13.8 kBMarkdownView Raw
1<h1 align="center">Fastify</h1>
2
3## Validation and Serialization
4Fastify 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
15The 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
21Example:
22```js
23const 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
52const queryStringJsonSchema = {
53 name: { type: 'string' },
54 excitement: { type: 'integer' }
55}
56
57const paramsJsonSchema = {
58 type: 'object',
59 properties: {
60 par1: { type: 'string' },
61 par2: { type: 'number' }
62 }
63}
64
65const headersJsonSchema = {
66 type: 'object',
67 properties: {
68 'x-foo': { type: 'string' }
69 },
70 required: ['x-foo']
71}
72
73const schema = {
74 body: bodyJsonSchema,
75
76 querystring: queryStringJsonSchema,
77
78 params: paramsJsonSchema,
79
80 headers: headersJsonSchema
81}
82
83fastify.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
89Thanks 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
91There 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),
93you can refer to an external schema. To use it you have to `addSchema` with a valid `$id` absolute URI.
94
95```js
96fastify.addSchema({
97 $id: 'http://example.com/common.json',
98 type: 'object',
99 properties: {
100 hello: { type: 'string' }
101 }
102})
103
104fastify.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.
118To use it you have to `addSchema` with an `$id` having a relative URI fragment which is a simple string that
119applies only to alphanumeric chars `[A-Za-z0-9]`.
120
121```js
122const fastify = require('fastify')()
123
124fastify.addSchema({
125 $id: 'greetings',
126 type: 'object',
127 properties: {
128 hello: { type: 'string' }
129 }
130})
131
132fastify.route({
133 method: 'POST',
134 url: '/',
135 schema: {
136 body: 'greetings#'
137 },
138 handler: () => {}
139})
140
141fastify.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
169You can use the shared schema everywhere, as top level schema or nested inside other schemas:
170```js
171const fastify = require('fastify')()
172
173fastify.addSchema({
174 $id: 'greetings',
175 type: 'object',
176 properties: {
177 hello: { type: 'string' }
178 }
179})
180
181fastify.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
200The function `getSchemas` returns the shared schemas available in the selected scope:
201```js
202fastify.addSchema({ $id: 'one', my: 'hello' })
203fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
204
205fastify.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```
217This 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
228The `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
230Fastify'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
241This 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
244const fastify = require('fastify')()
245const Ajv = require('ajv')
246const 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})
255fastify.setSchemaCompiler(function (schema) {
256 return ajv.compile(schema)
257})
258
259// -------
260// Alternatively, you can set the schema compiler using the setter property:
261fastify.schemaCompiler = function (schema) { return ajv.compile(schema) })
262```
263
264But 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
267const Joi = require('joi')
268
269fastify.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
279In 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
285Usually 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
287Example:
288```js
289const schema = {
290 response: {
291 200: {
292 type: 'object',
293 properties: {
294 value: { type: 'string' },
295 otherValue: { type: 'boolean' }
296 }
297 }
298 }
299}
300
301fastify.post('/the/url', { schema }, handler)
302```
303
304As 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
306const 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
324fastify.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
330When 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
333const schema = {
334 body: {
335 type: 'object',
336 properties: {
337 name: { type: 'string' }
338 },
339 required: ['name']
340 }
341}
342```
343
344and 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
354If 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
357const fastify = Fastify()
358
359fastify.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
367You 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
370fastify.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
379JSON Schema has some type of utilities in order to optimize your schemas that,
380in 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
394fastify.addSchema({
395 $id: 'sharedAddress',
396 type: 'object',
397 properties: {
398 city: { 'type': 'string' }
399 }
400})
401
402const 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
413const 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
434const 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
454fastify.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
468const 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
480fastify.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
493const 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)