node-fastify 5.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.borp.yaml +3 -0
- package/.markdownlint-cli2.yaml +22 -0
- package/.prettierignore +1 -0
- package/GOVERNANCE.md +4 -0
- package/LICENSE +21 -0
- package/PROJECT_CHARTER.md +126 -0
- package/README.md +423 -0
- package/SECURITY.md +220 -0
- package/SPONSORS.md +24 -0
- package/build/build-error-serializer.js +35 -0
- package/build/build-validation.js +169 -0
- package/build/sync-version.js +11 -0
- package/docs/Guides/Benchmarking.md +60 -0
- package/docs/Guides/Database.md +321 -0
- package/docs/Guides/Delay-Accepting-Requests.md +608 -0
- package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
- package/docs/Guides/Ecosystem.md +726 -0
- package/docs/Guides/Fluent-Schema.md +127 -0
- package/docs/Guides/Getting-Started.md +620 -0
- package/docs/Guides/Index.md +43 -0
- package/docs/Guides/Migration-Guide-V3.md +287 -0
- package/docs/Guides/Migration-Guide-V4.md +267 -0
- package/docs/Guides/Migration-Guide-V5.md +727 -0
- package/docs/Guides/Plugins-Guide.md +520 -0
- package/docs/Guides/Prototype-Poisoning.md +383 -0
- package/docs/Guides/Recommendations.md +378 -0
- package/docs/Guides/Serverless.md +604 -0
- package/docs/Guides/Style-Guide.md +246 -0
- package/docs/Guides/Testing.md +481 -0
- package/docs/Guides/Write-Plugin.md +103 -0
- package/docs/Guides/Write-Type-Provider.md +34 -0
- package/docs/Reference/ContentTypeParser.md +271 -0
- package/docs/Reference/Decorators.md +436 -0
- package/docs/Reference/Encapsulation.md +194 -0
- package/docs/Reference/Errors.md +377 -0
- package/docs/Reference/HTTP2.md +94 -0
- package/docs/Reference/Hooks.md +958 -0
- package/docs/Reference/Index.md +73 -0
- package/docs/Reference/LTS.md +86 -0
- package/docs/Reference/Lifecycle.md +99 -0
- package/docs/Reference/Logging.md +268 -0
- package/docs/Reference/Middleware.md +79 -0
- package/docs/Reference/Plugins.md +245 -0
- package/docs/Reference/Principles.md +73 -0
- package/docs/Reference/Reply.md +1001 -0
- package/docs/Reference/Request.md +295 -0
- package/docs/Reference/Routes.md +802 -0
- package/docs/Reference/Server.md +2389 -0
- package/docs/Reference/Type-Providers.md +256 -0
- package/docs/Reference/TypeScript.md +1729 -0
- package/docs/Reference/Validation-and-Serialization.md +1130 -0
- package/docs/Reference/Warnings.md +58 -0
- package/docs/index.md +24 -0
- package/docs/resources/encapsulation_context.drawio +1 -0
- package/docs/resources/encapsulation_context.svg +3 -0
- package/eslint.config.js +35 -0
- package/examples/asyncawait.js +38 -0
- package/examples/benchmark/body.json +3 -0
- package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
- package/examples/benchmark/hooks-benchmark.js +52 -0
- package/examples/benchmark/parser.js +47 -0
- package/examples/benchmark/simple.js +30 -0
- package/examples/benchmark/webstream.js +27 -0
- package/examples/hooks.js +91 -0
- package/examples/http2.js +39 -0
- package/examples/https.js +38 -0
- package/examples/parser.js +53 -0
- package/examples/plugin.js +12 -0
- package/examples/route-prefix.js +38 -0
- package/examples/shared-schema.js +38 -0
- package/examples/simple-stream.js +20 -0
- package/examples/simple.js +32 -0
- package/examples/simple.mjs +27 -0
- package/examples/typescript-server.ts +79 -0
- package/examples/use-plugin.js +29 -0
- package/fastify.d.ts +253 -0
- package/fastify.js +985 -0
- package/integration/server.js +29 -0
- package/integration/test.sh +23 -0
- package/lib/config-validator.js +1266 -0
- package/lib/content-type-parser.js +413 -0
- package/lib/content-type.js +160 -0
- package/lib/context.js +98 -0
- package/lib/decorate.js +152 -0
- package/lib/error-handler.js +173 -0
- package/lib/error-serializer.js +134 -0
- package/lib/error-status.js +14 -0
- package/lib/errors.js +516 -0
- package/lib/four-oh-four.js +190 -0
- package/lib/handle-request.js +195 -0
- package/lib/head-route.js +45 -0
- package/lib/hooks.js +429 -0
- package/lib/initial-config-validation.js +37 -0
- package/lib/logger-factory.js +136 -0
- package/lib/logger-pino.js +68 -0
- package/lib/noop-set.js +10 -0
- package/lib/plugin-override.js +90 -0
- package/lib/plugin-utils.js +169 -0
- package/lib/promise.js +23 -0
- package/lib/reply.js +1030 -0
- package/lib/req-id-gen-factory.js +52 -0
- package/lib/request.js +391 -0
- package/lib/route.js +686 -0
- package/lib/schema-controller.js +164 -0
- package/lib/schemas.js +207 -0
- package/lib/server.js +441 -0
- package/lib/symbols.js +71 -0
- package/lib/validation.js +280 -0
- package/lib/warnings.js +57 -0
- package/lib/wrap-thenable.js +84 -0
- package/package.json +225 -0
- package/scripts/validate-ecosystem-links.js +179 -0
- package/test/404s.test.js +2035 -0
- package/test/500s.test.js +422 -0
- package/test/allow-unsafe-regex.test.js +92 -0
- package/test/als.test.js +65 -0
- package/test/async-await.test.js +705 -0
- package/test/async-dispose.test.js +20 -0
- package/test/async_hooks.test.js +52 -0
- package/test/body-limit.test.js +224 -0
- package/test/buffer.test.js +74 -0
- package/test/build/error-serializer.test.js +36 -0
- package/test/build/version.test.js +14 -0
- package/test/build-certificate.js +109 -0
- package/test/bundler/README.md +29 -0
- package/test/bundler/esbuild/bundler-test.js +32 -0
- package/test/bundler/esbuild/package.json +10 -0
- package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
- package/test/bundler/esbuild/src/index.js +9 -0
- package/test/bundler/webpack/bundler-test.js +32 -0
- package/test/bundler/webpack/package.json +11 -0
- package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
- package/test/bundler/webpack/src/index.js +9 -0
- package/test/bundler/webpack/webpack.config.js +15 -0
- package/test/case-insensitive.test.js +102 -0
- package/test/chainable.test.js +40 -0
- package/test/child-logger-factory.test.js +128 -0
- package/test/client-timeout.test.js +38 -0
- package/test/close-pipelining.test.js +78 -0
- package/test/close.test.js +706 -0
- package/test/conditional-pino.test.js +47 -0
- package/test/connection-timeout.test.js +42 -0
- package/test/constrained-routes.test.js +1138 -0
- package/test/content-length.test.js +174 -0
- package/test/content-parser.test.js +739 -0
- package/test/content-type.test.js +181 -0
- package/test/context-config.test.js +164 -0
- package/test/custom-http-server.test.js +118 -0
- package/test/custom-parser-async.test.js +59 -0
- package/test/custom-parser.0.test.js +701 -0
- package/test/custom-parser.1.test.js +266 -0
- package/test/custom-parser.2.test.js +91 -0
- package/test/custom-parser.3.test.js +208 -0
- package/test/custom-parser.4.test.js +218 -0
- package/test/custom-parser.5.test.js +130 -0
- package/test/custom-querystring-parser.test.js +129 -0
- package/test/decorator.test.js +1330 -0
- package/test/delete.test.js +344 -0
- package/test/diagnostics-channel/404.test.js +49 -0
- package/test/diagnostics-channel/async-delay-request.test.js +65 -0
- package/test/diagnostics-channel/async-request.test.js +64 -0
- package/test/diagnostics-channel/error-before-handler.test.js +35 -0
- package/test/diagnostics-channel/error-request.test.js +53 -0
- package/test/diagnostics-channel/error-status.test.js +123 -0
- package/test/diagnostics-channel/init.test.js +50 -0
- package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
- package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
- package/test/diagnostics-channel/sync-request.test.js +54 -0
- package/test/encapsulated-child-logger-factory.test.js +69 -0
- package/test/encapsulated-error-handler.test.js +237 -0
- package/test/esm/errorCodes.test.mjs +10 -0
- package/test/esm/esm.test.mjs +13 -0
- package/test/esm/index.test.js +8 -0
- package/test/esm/named-exports.mjs +14 -0
- package/test/esm/other.mjs +8 -0
- package/test/esm/plugin.mjs +8 -0
- package/test/fastify-instance.test.js +300 -0
- package/test/find-route.test.js +152 -0
- package/test/fluent-schema.test.js +209 -0
- package/test/genReqId.test.js +426 -0
- package/test/handler-context.test.js +45 -0
- package/test/handler-timeout.test.js +367 -0
- package/test/has-route.test.js +88 -0
- package/test/header-overflow.test.js +55 -0
- package/test/helper.js +496 -0
- package/test/hooks-async.test.js +1099 -0
- package/test/hooks.on-listen.test.js +1162 -0
- package/test/hooks.on-ready.test.js +421 -0
- package/test/hooks.test.js +3578 -0
- package/test/http-methods/copy.test.js +35 -0
- package/test/http-methods/custom-http-methods.test.js +114 -0
- package/test/http-methods/get.test.js +412 -0
- package/test/http-methods/head.test.js +263 -0
- package/test/http-methods/lock.test.js +108 -0
- package/test/http-methods/mkcalendar.test.js +143 -0
- package/test/http-methods/mkcol.test.js +35 -0
- package/test/http-methods/move.test.js +42 -0
- package/test/http-methods/propfind.test.js +136 -0
- package/test/http-methods/proppatch.test.js +105 -0
- package/test/http-methods/report.test.js +142 -0
- package/test/http-methods/search.test.js +233 -0
- package/test/http-methods/trace.test.js +21 -0
- package/test/http-methods/unlock.test.js +38 -0
- package/test/http2/closing.test.js +270 -0
- package/test/http2/constraint.test.js +109 -0
- package/test/http2/head.test.js +34 -0
- package/test/http2/plain.test.js +68 -0
- package/test/http2/secure-with-fallback.test.js +113 -0
- package/test/http2/secure.test.js +67 -0
- package/test/http2/unknown-http-method.test.js +34 -0
- package/test/https/custom-https-server.test.js +58 -0
- package/test/https/https.test.js +136 -0
- package/test/imports.test.js +17 -0
- package/test/inject.test.js +502 -0
- package/test/input-validation.js +335 -0
- package/test/internals/all.test.js +38 -0
- package/test/internals/content-type-parser.test.js +111 -0
- package/test/internals/context.test.js +31 -0
- package/test/internals/decorator.test.js +156 -0
- package/test/internals/errors.test.js +982 -0
- package/test/internals/handle-request.test.js +270 -0
- package/test/internals/hook-runner.test.js +449 -0
- package/test/internals/hooks.test.js +96 -0
- package/test/internals/initial-config.test.js +383 -0
- package/test/internals/logger.test.js +163 -0
- package/test/internals/plugin.test.js +170 -0
- package/test/internals/promise.test.js +63 -0
- package/test/internals/reply-serialize.test.js +714 -0
- package/test/internals/reply.test.js +1920 -0
- package/test/internals/req-id-gen-factory.test.js +133 -0
- package/test/internals/request-validate.test.js +1402 -0
- package/test/internals/request.test.js +506 -0
- package/test/internals/schema-controller-perf.test.js +40 -0
- package/test/internals/server.test.js +91 -0
- package/test/internals/validation.test.js +352 -0
- package/test/issue-4959.test.js +118 -0
- package/test/keep-alive-timeout.test.js +42 -0
- package/test/listen.1.test.js +154 -0
- package/test/listen.2.test.js +113 -0
- package/test/listen.3.test.js +83 -0
- package/test/listen.4.test.js +168 -0
- package/test/listen.5.test.js +122 -0
- package/test/logger/instantiation.test.js +341 -0
- package/test/logger/logger-test-utils.js +47 -0
- package/test/logger/logging.test.js +460 -0
- package/test/logger/options.test.js +579 -0
- package/test/logger/request.test.js +292 -0
- package/test/logger/response.test.js +183 -0
- package/test/logger/tap-parallel-not-ok +0 -0
- package/test/max-requests-per-socket.test.js +113 -0
- package/test/middleware.test.js +37 -0
- package/test/noop-set.test.js +19 -0
- package/test/nullable-validation.test.js +187 -0
- package/test/options.error-handler.test.js +5 -0
- package/test/options.test.js +5 -0
- package/test/output-validation.test.js +140 -0
- package/test/patch.error-handler.test.js +5 -0
- package/test/patch.test.js +5 -0
- package/test/plugin.1.test.js +230 -0
- package/test/plugin.2.test.js +314 -0
- package/test/plugin.3.test.js +287 -0
- package/test/plugin.4.test.js +504 -0
- package/test/plugin.helper.js +8 -0
- package/test/plugin.name.display.js +10 -0
- package/test/post-empty-body.test.js +38 -0
- package/test/pretty-print.test.js +366 -0
- package/test/promises.test.js +125 -0
- package/test/proto-poisoning.test.js +145 -0
- package/test/put.error-handler.test.js +5 -0
- package/test/put.test.js +5 -0
- package/test/register.test.js +184 -0
- package/test/reply-code.test.js +148 -0
- package/test/reply-early-hints.test.js +100 -0
- package/test/reply-error.test.js +815 -0
- package/test/reply-trailers.test.js +445 -0
- package/test/reply-web-stream-locked.test.js +37 -0
- package/test/request-error.test.js +624 -0
- package/test/request-header-host.test.js +339 -0
- package/test/request-id.test.js +118 -0
- package/test/request-timeout.test.js +53 -0
- package/test/route-hooks.test.js +635 -0
- package/test/route-prefix.test.js +904 -0
- package/test/route-shorthand.test.js +48 -0
- package/test/route.1.test.js +259 -0
- package/test/route.2.test.js +100 -0
- package/test/route.3.test.js +213 -0
- package/test/route.4.test.js +127 -0
- package/test/route.5.test.js +211 -0
- package/test/route.6.test.js +306 -0
- package/test/route.7.test.js +406 -0
- package/test/route.8.test.js +225 -0
- package/test/router-options.test.js +1108 -0
- package/test/same-shape.test.js +124 -0
- package/test/schema-examples.test.js +661 -0
- package/test/schema-feature.test.js +2198 -0
- package/test/schema-serialization.test.js +1171 -0
- package/test/schema-special-usage.test.js +1348 -0
- package/test/schema-validation.test.js +1572 -0
- package/test/scripts/validate-ecosystem-links.test.js +339 -0
- package/test/serialize-response.test.js +186 -0
- package/test/server.test.js +347 -0
- package/test/set-error-handler.test.js +69 -0
- package/test/skip-reply-send.test.js +317 -0
- package/test/stream-serializers.test.js +40 -0
- package/test/stream.1.test.js +94 -0
- package/test/stream.2.test.js +129 -0
- package/test/stream.3.test.js +198 -0
- package/test/stream.4.test.js +176 -0
- package/test/stream.5.test.js +188 -0
- package/test/sync-routes.test.js +32 -0
- package/test/throw.test.js +359 -0
- package/test/toolkit.js +63 -0
- package/test/trust-proxy.test.js +162 -0
- package/test/type-provider.test.js +22 -0
- package/test/types/content-type-parser.test-d.ts +72 -0
- package/test/types/decorate-request-reply.test-d.ts +18 -0
- package/test/types/dummy-plugin.ts +9 -0
- package/test/types/errors.test-d.ts +90 -0
- package/test/types/fastify.test-d.ts +352 -0
- package/test/types/hooks.test-d.ts +550 -0
- package/test/types/import.ts +2 -0
- package/test/types/instance.test-d.ts +588 -0
- package/test/types/logger.test-d.ts +277 -0
- package/test/types/plugin.test-d.ts +97 -0
- package/test/types/register.test-d.ts +237 -0
- package/test/types/reply.test-d.ts +254 -0
- package/test/types/request.test-d.ts +188 -0
- package/test/types/route.test-d.ts +553 -0
- package/test/types/schema.test-d.ts +135 -0
- package/test/types/serverFactory.test-d.ts +37 -0
- package/test/types/type-provider.test-d.ts +1213 -0
- package/test/types/using.test-d.ts +17 -0
- package/test/upgrade.test.js +52 -0
- package/test/url-rewriting.test.js +122 -0
- package/test/use-semicolon-delimiter.test.js +168 -0
- package/test/validation-error-handling.test.js +900 -0
- package/test/versioned-routes.test.js +603 -0
- package/test/web-api.test.js +616 -0
- package/test/wrap-thenable.test.js +30 -0
- package/types/content-type-parser.d.ts +75 -0
- package/types/context.d.ts +22 -0
- package/types/errors.d.ts +92 -0
- package/types/hooks.d.ts +875 -0
- package/types/instance.d.ts +609 -0
- package/types/logger.d.ts +107 -0
- package/types/plugin.d.ts +44 -0
- package/types/register.d.ts +42 -0
- package/types/reply.d.ts +81 -0
- package/types/request.d.ts +95 -0
- package/types/route.d.ts +199 -0
- package/types/schema.d.ts +61 -0
- package/types/server-factory.d.ts +19 -0
- package/types/type-provider.d.ts +130 -0
- package/types/utils.d.ts +98 -0
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
<h1 align="center">Fastify</h1>
|
|
2
|
+
|
|
3
|
+
## Validation and Serialization
|
|
4
|
+
Fastify uses a schema-based approach. We recommend using
|
|
5
|
+
[JSON Schema](https://json-schema.org/) to validate routes and serialize outputs.
|
|
6
|
+
Fastify compiles the schema into a highly performant function.
|
|
7
|
+
|
|
8
|
+
Validation is only attempted if the content type is `application/json`,
|
|
9
|
+
unless the body schema uses the [`content`](#body-content-type-validation)
|
|
10
|
+
property to specify validation per content type. When the body schema defines
|
|
11
|
+
a `content` field, it must enumerate all possible content types the
|
|
12
|
+
application expects to handle with the associated handler.
|
|
13
|
+
|
|
14
|
+
All examples use the
|
|
15
|
+
[JSON Schema Draft 7](https://json-schema.org/draft-07)
|
|
16
|
+
specification.
|
|
17
|
+
|
|
18
|
+
> ⚠ Warning:
|
|
19
|
+
> Treat schema definitions as application code. Validation and serialization
|
|
20
|
+
> features use `new Function()`, which is unsafe with user-provided schemas. See
|
|
21
|
+
> [Ajv](https://www.npmjs.com/package/ajv) and
|
|
22
|
+
> [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for details.
|
|
23
|
+
>
|
|
24
|
+
> Whilst Fastify supports the
|
|
25
|
+
> [`$async` Ajv feature](https://ajv.js.org/guide/async-validation.html),
|
|
26
|
+
> it should not be used for initial validation. Accessing databases during
|
|
27
|
+
> validation may lead to Denial of Service attacks. Use
|
|
28
|
+
> [Fastify's hooks](./Hooks.md) like `preHandler` for `async` tasks after validation.
|
|
29
|
+
>
|
|
30
|
+
> When using custom validators with async `preValidation` hooks,
|
|
31
|
+
> validators **must return** `{error}` objects instead of throwing errors.
|
|
32
|
+
> Throwing errors from custom validators will cause unhandled promise rejections
|
|
33
|
+
> that crash the application when combined with async hooks. See the
|
|
34
|
+
> [custom validator examples](#using-other-validation-libraries) below for the
|
|
35
|
+
> correct pattern.
|
|
36
|
+
|
|
37
|
+
### Core concepts
|
|
38
|
+
Validation and serialization are handled by two customizable dependencies:
|
|
39
|
+
- [Ajv v8](https://www.npmjs.com/package/ajv) for request validation
|
|
40
|
+
- [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for
|
|
41
|
+
response body serialization
|
|
42
|
+
|
|
43
|
+
These dependencies share only the JSON schemas added to Fastify's instance via
|
|
44
|
+
`.addSchema(schema)`.
|
|
45
|
+
|
|
46
|
+
#### Adding a shared schema
|
|
47
|
+
<a id="shared-schema"></a>
|
|
48
|
+
|
|
49
|
+
The `addSchema` API allows adding multiple schemas to the Fastify instance for
|
|
50
|
+
reuse throughout the application. This API is encapsulated.
|
|
51
|
+
|
|
52
|
+
Shared schemas can be reused with the JSON Schema
|
|
53
|
+
[**`$ref`**](https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01#section-8)
|
|
54
|
+
keyword. Here is an overview of how references work:
|
|
55
|
+
|
|
56
|
+
+ `myField: { $ref: '#foo' }` searches for `$id: '#foo'` in the current schema
|
|
57
|
+
+ `myField: { $ref: '#/definitions/foo' }` searches for `definitions.foo` in the
|
|
58
|
+
current schema
|
|
59
|
+
+ `myField: { $ref: 'http://url.com/sh.json#' }` searches for a shared schema
|
|
60
|
+
with `$id: 'http://url.com/sh.json'`
|
|
61
|
+
+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` searches for a
|
|
62
|
+
shared schema with `$id: 'http://url.com/sh.json'` and uses `definitions.foo`
|
|
63
|
+
+ `myField: { $ref: 'http://url.com/sh.json#foo' }` searches for a shared schema
|
|
64
|
+
with `$id: 'http://url.com/sh.json'` and looks for `$id: '#foo'` within it
|
|
65
|
+
|
|
66
|
+
**Simple usage:**
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
fastify.addSchema({
|
|
70
|
+
$id: 'http://fastify.example/',
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
hello: { type: 'string' }
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
fastify.post('/', {
|
|
78
|
+
handler () {},
|
|
79
|
+
schema: {
|
|
80
|
+
body: {
|
|
81
|
+
type: 'array',
|
|
82
|
+
items: { $ref: 'http://fastify.example#/properties/hello' }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**`$ref` as root reference:**
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
fastify.addSchema({
|
|
92
|
+
$id: 'commonSchema',
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
hello: { type: 'string' }
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
fastify.post('/', {
|
|
100
|
+
handler () {},
|
|
101
|
+
schema: {
|
|
102
|
+
body: { $ref: 'commonSchema#' },
|
|
103
|
+
headers: { $ref: 'commonSchema#' }
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Retrieving the shared schemas
|
|
109
|
+
<a id="get-shared-schema"></a>
|
|
110
|
+
|
|
111
|
+
If the validator and serializer are customized, `.addSchema` is not useful since
|
|
112
|
+
Fastify no longer controls them. To access schemas added to the Fastify instance,
|
|
113
|
+
use `.getSchemas()`:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
fastify.addSchema({
|
|
117
|
+
$id: 'schemaId',
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
hello: { type: 'string' }
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const mySchemas = fastify.getSchemas()
|
|
125
|
+
const mySchema = fastify.getSchema('schemaId')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The `getSchemas` function is encapsulated and returns shared schemas available
|
|
129
|
+
in the selected scope:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
fastify.addSchema({ $id: 'one', my: 'hello' })
|
|
133
|
+
// will return only `one` schema
|
|
134
|
+
fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
|
|
135
|
+
|
|
136
|
+
fastify.register((instance, opts, done) => {
|
|
137
|
+
instance.addSchema({ $id: 'two', my: 'ciao' })
|
|
138
|
+
// will return `one` and `two` schemas
|
|
139
|
+
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
|
+
### Validation
|
|
153
|
+
Route validation relies on [Ajv v8](https://www.npmjs.com/package/ajv), a
|
|
154
|
+
high-performance JSON Schema validator. To validate input, add the required
|
|
155
|
+
fields to the route schema.
|
|
156
|
+
|
|
157
|
+
Supported validations include:
|
|
158
|
+
- `body`: validates the request body for POST, PUT, or PATCH methods.
|
|
159
|
+
- `querystring` or `query`: validates the query string.
|
|
160
|
+
- `params`: validates the route parameters.
|
|
161
|
+
- `headers`: validates the request headers.
|
|
162
|
+
|
|
163
|
+
Validations can be a complete JSON Schema object with a `type` of `'object'` and
|
|
164
|
+
a `'properties'` object containing parameters, or a simpler variation listing
|
|
165
|
+
parameters at the top level.
|
|
166
|
+
|
|
167
|
+
> ℹ For using the latest Ajv (v8), refer to the
|
|
168
|
+
> [`schemaController`](./Server.md#schema-controller) section.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```js
|
|
172
|
+
const bodyJsonSchema = {
|
|
173
|
+
type: 'object',
|
|
174
|
+
required: ['requiredKey'],
|
|
175
|
+
properties: {
|
|
176
|
+
someKey: { type: 'string' },
|
|
177
|
+
someOtherKey: { type: 'number' },
|
|
178
|
+
requiredKey: {
|
|
179
|
+
type: 'array',
|
|
180
|
+
maxItems: 3,
|
|
181
|
+
items: { type: 'integer' }
|
|
182
|
+
},
|
|
183
|
+
nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true }
|
|
184
|
+
multipleTypesKey: { type: ['boolean', 'number'] },
|
|
185
|
+
multipleRestrictedTypesKey: {
|
|
186
|
+
oneOf: [
|
|
187
|
+
{ type: 'string', maxLength: 5 },
|
|
188
|
+
{ type: 'number', minimum: 10 }
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
enumKey: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
enum: ['John', 'Foo']
|
|
194
|
+
},
|
|
195
|
+
notTypeKey: {
|
|
196
|
+
not: { type: 'array' }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const queryStringJsonSchema = {
|
|
202
|
+
type: 'object',
|
|
203
|
+
properties: {
|
|
204
|
+
name: { type: 'string' },
|
|
205
|
+
excitement: { type: 'integer' }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const paramsJsonSchema = {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {
|
|
212
|
+
par1: { type: 'string' },
|
|
213
|
+
par2: { type: 'number' }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const headersJsonSchema = {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
'x-foo': { type: 'string' }
|
|
221
|
+
},
|
|
222
|
+
required: ['x-foo']
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const schema = {
|
|
226
|
+
body: bodyJsonSchema,
|
|
227
|
+
querystring: queryStringJsonSchema,
|
|
228
|
+
params: paramsJsonSchema,
|
|
229
|
+
headers: headersJsonSchema
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fastify.post('/the/url', { schema }, handler)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Body Content-Type Validation
|
|
236
|
+
<a id="body-content-type-validation"></a>
|
|
237
|
+
|
|
238
|
+
For `body` schema, it is further possible to differentiate the schema per content
|
|
239
|
+
type by nesting the schemas inside `content` property. The schema validation
|
|
240
|
+
will be applied based on the `Content-Type` header in the request.
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
fastify.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 validated
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}, handler)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
> ⚠ Warning:
|
|
261
|
+
> When using [custom content type parsers](./ContentTypeParser.md), the parsed
|
|
262
|
+
> body is validated **only** when the request content type matches a key in the
|
|
263
|
+
> schema `content` map.
|
|
264
|
+
>
|
|
265
|
+
> Schema selection uses an exact match on the request's
|
|
266
|
+
> [essence MIME type](https://mimesniff.spec.whatwg.org/#mime-type-miscellaneous)
|
|
267
|
+
> (for example, `application/json`). If a parser is registered with a regular
|
|
268
|
+
> expression (for example, `/^application\/.*json$/`), the parser can accept
|
|
269
|
+
> more content types than the `content` map covers. Requests in that gap are
|
|
270
|
+
> parsed but **not validated**.
|
|
271
|
+
>
|
|
272
|
+
> Ensure every content type accepted by the parser has a corresponding key in
|
|
273
|
+
> the `content` map, or use a catch-all body schema without `content` when
|
|
274
|
+
> strict per-content-type discrimination is not required.
|
|
275
|
+
>
|
|
276
|
+
> ```js
|
|
277
|
+
> // Add a custom parser for YAML
|
|
278
|
+
> fastify.addContentTypeParser('application/yaml', { parseAs: 'string' }, (req, body, done) => {
|
|
279
|
+
> done(null, YAML.parse(body))
|
|
280
|
+
> })
|
|
281
|
+
>
|
|
282
|
+
> fastify.post('/the/url', {
|
|
283
|
+
> schema: {
|
|
284
|
+
> body: {
|
|
285
|
+
> content: {
|
|
286
|
+
> 'application/json': {
|
|
287
|
+
> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
|
|
288
|
+
> },
|
|
289
|
+
> // Without this entry, application/yaml requests will NOT be validated
|
|
290
|
+
> 'application/yaml': {
|
|
291
|
+
> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
|
|
292
|
+
> }
|
|
293
|
+
> }
|
|
294
|
+
> }
|
|
295
|
+
> }
|
|
296
|
+
> }, handler)
|
|
297
|
+
> ```
|
|
298
|
+
|
|
299
|
+
Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) values to
|
|
300
|
+
the types specified in the schema `type` keywords, both to pass validation and
|
|
301
|
+
to use the correctly typed data afterwards.
|
|
302
|
+
|
|
303
|
+
The Ajv default configuration in Fastify supports coercing array parameters in
|
|
304
|
+
`querystring`. Example:
|
|
305
|
+
|
|
306
|
+
```js
|
|
307
|
+
const opts = {
|
|
308
|
+
schema: {
|
|
309
|
+
querystring: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
ids: {
|
|
313
|
+
type: 'array',
|
|
314
|
+
default: []
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fastify.get('/', opts, (request, reply) => {
|
|
322
|
+
reply.send({ params: request.query }) // echo the querystring
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
fastify.listen({ port: 3000 }, (err) => {
|
|
326
|
+
if (err) throw err
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
curl -X GET "http://localhost:3000/?ids=1
|
|
332
|
+
|
|
333
|
+
{"params":{"ids":["1"]}}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
A custom schema validator can be specified for each parameter type (body,
|
|
337
|
+
querystring, params, headers).
|
|
338
|
+
|
|
339
|
+
For example, the following code disables type coercion only for the `body`
|
|
340
|
+
parameters, changing the Ajv default options:
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
const schemaCompilers = {
|
|
344
|
+
body: new Ajv({
|
|
345
|
+
removeAdditional: false,
|
|
346
|
+
coerceTypes: false,
|
|
347
|
+
allErrors: true
|
|
348
|
+
}),
|
|
349
|
+
params: new Ajv({
|
|
350
|
+
removeAdditional: false,
|
|
351
|
+
coerceTypes: true,
|
|
352
|
+
allErrors: true
|
|
353
|
+
}),
|
|
354
|
+
querystring: new Ajv({
|
|
355
|
+
removeAdditional: false,
|
|
356
|
+
coerceTypes: true,
|
|
357
|
+
allErrors: true
|
|
358
|
+
}),
|
|
359
|
+
headers: new Ajv({
|
|
360
|
+
removeAdditional: false,
|
|
361
|
+
coerceTypes: true,
|
|
362
|
+
allErrors: true
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
server.setValidatorCompiler(req => {
|
|
367
|
+
if (!req.httpPart) {
|
|
368
|
+
throw new Error('Missing httpPart')
|
|
369
|
+
}
|
|
370
|
+
const compiler = schemaCompilers[req.httpPart]
|
|
371
|
+
if (!compiler) {
|
|
372
|
+
throw new Error(`Missing compiler for ${req.httpPart}`)
|
|
373
|
+
}
|
|
374
|
+
return compiler.compile(req.schema)
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
When type coercion is enabled, using `anyOf` with nullable primitive types
|
|
379
|
+
can produce unexpected results. For example, a value of `0` or `false` may be
|
|
380
|
+
coerced to `null` because Ajv evaluates `anyOf` schemas in order and applies
|
|
381
|
+
type coercion during matching. This means the `{ "type": "null" }` branch can
|
|
382
|
+
match before the intended type:
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"anyOf": [
|
|
387
|
+
{ "type": "null" },
|
|
388
|
+
{ "type": "number" }
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
To avoid this, use the `nullable` keyword instead of `anyOf` for primitive
|
|
394
|
+
types:
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"type": "number",
|
|
399
|
+
"nullable": true
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html).
|
|
404
|
+
|
|
405
|
+
#### Ajv Plugins
|
|
406
|
+
<a id="ajv-plugins"></a>
|
|
407
|
+
|
|
408
|
+
A list of plugins can be provided for use with the default `ajv` instance.
|
|
409
|
+
Ensure the plugin is **compatible with the Ajv version shipped within Fastify**.
|
|
410
|
+
|
|
411
|
+
> Refer to [`ajv options`](./Server.md#ajv) to check plugins format.
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
const fastify = require('fastify')({
|
|
415
|
+
ajv: {
|
|
416
|
+
plugins: [
|
|
417
|
+
require('ajv-merge-patch')
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
fastify.post('/', {
|
|
423
|
+
handler (req, reply) { reply.send({ ok: 1 }) },
|
|
424
|
+
schema: {
|
|
425
|
+
body: {
|
|
426
|
+
$patch: {
|
|
427
|
+
source: {
|
|
428
|
+
type: 'object',
|
|
429
|
+
properties: {
|
|
430
|
+
q: {
|
|
431
|
+
type: 'string'
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
with: [
|
|
436
|
+
{
|
|
437
|
+
op: 'add',
|
|
438
|
+
path: '/properties/q',
|
|
439
|
+
value: { type: 'number' }
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
fastify.post('/foo', {
|
|
448
|
+
handler (req, reply) { reply.send({ ok: 1 }) },
|
|
449
|
+
schema: {
|
|
450
|
+
body: {
|
|
451
|
+
$merge: {
|
|
452
|
+
source: {
|
|
453
|
+
type: 'object',
|
|
454
|
+
properties: {
|
|
455
|
+
q: {
|
|
456
|
+
type: 'string'
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
with: {
|
|
461
|
+
required: ['q']
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
#### Validator Compiler
|
|
470
|
+
<a id="schema-validator"></a>
|
|
471
|
+
|
|
472
|
+
The `validatorCompiler` is a function that returns a function to validate the
|
|
473
|
+
body, URL parameters, headers, and query string. The default `validatorCompiler`
|
|
474
|
+
returns a function that implements the [ajv](https://ajv.js.org/) validation
|
|
475
|
+
interface. Fastify uses it internally to speed up validation.
|
|
476
|
+
|
|
477
|
+
Fastify's [baseline ajv
|
|
478
|
+
configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is:
|
|
479
|
+
|
|
480
|
+
```js
|
|
481
|
+
{
|
|
482
|
+
coerceTypes: 'array', // change data type of data to match type keyword
|
|
483
|
+
useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
|
|
484
|
+
removeAdditional: true, // remove additional properties if additionalProperties is set to false, see: https://ajv.js.org/guide/modifying-data.html#removing-additional-properties
|
|
485
|
+
uriResolver: require('fast-uri'),
|
|
486
|
+
addUsedSchema: false,
|
|
487
|
+
// Explicitly set allErrors to `false`.
|
|
488
|
+
// When set to `true`, a DoS attack is possible.
|
|
489
|
+
allErrors: false
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Modify the baseline configuration by providing
|
|
494
|
+
[`ajv.customOptions`](./Server.md#factory-ajv) to the Fastify factory.
|
|
495
|
+
|
|
496
|
+
To change or set additional config options, create a custom instance and
|
|
497
|
+
override the existing one:
|
|
498
|
+
|
|
499
|
+
```js
|
|
500
|
+
const fastify = require('fastify')()
|
|
501
|
+
const Ajv = require('ajv')
|
|
502
|
+
const ajv = new Ajv({
|
|
503
|
+
removeAdditional: 'all',
|
|
504
|
+
useDefaults: true,
|
|
505
|
+
coerceTypes: 'array',
|
|
506
|
+
// any other options
|
|
507
|
+
// ...
|
|
508
|
+
})
|
|
509
|
+
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
|
|
510
|
+
return ajv.compile(schema)
|
|
511
|
+
})
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
> ℹ️ Note:
|
|
515
|
+
> When using a custom validator instance, add schemas to the validator
|
|
516
|
+
> instead of Fastify. Fastify's `addSchema` method will not recognize the custom
|
|
517
|
+
> validator.
|
|
518
|
+
|
|
519
|
+
##### Using other validation libraries
|
|
520
|
+
<a id="using-other-validation-libraries"></a>
|
|
521
|
+
|
|
522
|
+
The `setValidatorCompiler` function allows substituting `ajv` with other
|
|
523
|
+
JavaScript validation libraries like [joi](https://github.com/hapijs/joi/) or
|
|
524
|
+
[yup](https://github.com/jquense/yup/), or a custom one:
|
|
525
|
+
|
|
526
|
+
```js
|
|
527
|
+
const Joi = require('joi')
|
|
528
|
+
|
|
529
|
+
fastify.setValidatorCompiler(({ schema }) => {
|
|
530
|
+
return (data) => {
|
|
531
|
+
try {
|
|
532
|
+
const { error, value } = schema.validate(data)
|
|
533
|
+
if (error) {
|
|
534
|
+
return { error } // Return the error, do not throw it
|
|
535
|
+
}
|
|
536
|
+
return { value }
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return { error: e } // Catch any unexpected errors too
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
fastify.post('/the/url', {
|
|
544
|
+
schema: {
|
|
545
|
+
body: Joi.object().keys({
|
|
546
|
+
hello: Joi.string().required()
|
|
547
|
+
}).required()
|
|
548
|
+
}
|
|
549
|
+
}, handler)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
```js
|
|
553
|
+
const yup = require('yup')
|
|
554
|
+
// Validation options to match ajv's baseline options used in Fastify
|
|
555
|
+
const yupOptions = {
|
|
556
|
+
strict: false,
|
|
557
|
+
abortEarly: false, // return all errors
|
|
558
|
+
stripUnknown: true, // remove additional properties
|
|
559
|
+
recursive: true
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
fastify.post('/the/url', {
|
|
563
|
+
schema: {
|
|
564
|
+
body: yup.object({
|
|
565
|
+
age: yup.number().integer().required(),
|
|
566
|
+
sub: yup.object().shape({
|
|
567
|
+
name: yup.string().required()
|
|
568
|
+
}).required()
|
|
569
|
+
})
|
|
570
|
+
},
|
|
571
|
+
validatorCompiler: ({ schema, method, url, httpPart }) => {
|
|
572
|
+
return function (data) {
|
|
573
|
+
// with option strict = false, yup `validateSync` function returns the
|
|
574
|
+
// coerced value if validation was successful, or throws if validation failed
|
|
575
|
+
try {
|
|
576
|
+
const result = schema.validateSync(data, yupOptions)
|
|
577
|
+
return { value: result }
|
|
578
|
+
} catch (e) {
|
|
579
|
+
return { error: e }
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}, handler)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
##### Custom Validator Best Practices
|
|
587
|
+
|
|
588
|
+
When implementing custom validators, follow these patterns to ensure compatibility
|
|
589
|
+
with all Fastify features:
|
|
590
|
+
|
|
591
|
+
**Always return objects, never throw:**
|
|
592
|
+
```js
|
|
593
|
+
return { value: validatedData } // On success
|
|
594
|
+
return { error: validationError } // On failure
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Use try-catch for safety:**
|
|
598
|
+
```js
|
|
599
|
+
fastify.setValidatorCompiler(({ schema }) => {
|
|
600
|
+
return (data) => {
|
|
601
|
+
try {
|
|
602
|
+
// Validation logic here
|
|
603
|
+
const result = schema.validate(data)
|
|
604
|
+
if (result.error) {
|
|
605
|
+
return { error: result.error }
|
|
606
|
+
}
|
|
607
|
+
return { value: result.value }
|
|
608
|
+
} catch (e) {
|
|
609
|
+
// Catch any unexpected errors
|
|
610
|
+
return { error: e }
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
})
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
This pattern ensures validators work correctly with both sync and async
|
|
617
|
+
`preValidation` hooks, preventing unhandled promise rejections that can crash
|
|
618
|
+
an application.
|
|
619
|
+
|
|
620
|
+
##### .statusCode property
|
|
621
|
+
|
|
622
|
+
All validation errors have a `.statusCode` property set to `400`, ensuring the
|
|
623
|
+
default error handler sets the response status code to `400`.
|
|
624
|
+
|
|
625
|
+
```js
|
|
626
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
627
|
+
request.log.error(error, `This error has status code ${error.statusCode}`)
|
|
628
|
+
reply.status(error.statusCode).send(error)
|
|
629
|
+
})
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
##### Validation messages with other validation libraries
|
|
633
|
+
|
|
634
|
+
Fastify's validation error messages are tightly coupled to the default
|
|
635
|
+
validation engine: errors returned from `ajv` are eventually run through the
|
|
636
|
+
`schemaErrorFormatter` function which builds human-friendly error messages.
|
|
637
|
+
However, the `schemaErrorFormatter` function is written with `ajv` in mind.
|
|
638
|
+
This may result in odd or incomplete error messages when using other validation
|
|
639
|
+
libraries.
|
|
640
|
+
|
|
641
|
+
To circumvent this issue, there are two main options:
|
|
642
|
+
|
|
643
|
+
1. Ensure the validation function (returned by the custom `schemaCompiler`)
|
|
644
|
+
returns errors in the same structure and format as `ajv`.
|
|
645
|
+
2. Use a custom `errorHandler` to intercept and format custom validation errors.
|
|
646
|
+
|
|
647
|
+
Fastify adds two properties to all validation errors to help write a custom
|
|
648
|
+
`errorHandler`:
|
|
649
|
+
|
|
650
|
+
* `validation`: the content of the `error` property of the object returned by
|
|
651
|
+
the validation function (returned by the custom `schemaCompiler`)
|
|
652
|
+
* `validationContext`: the context (body, params, query, headers) where the
|
|
653
|
+
validation error occurred
|
|
654
|
+
|
|
655
|
+
A contrived example of such a custom `errorHandler` handling validation errors
|
|
656
|
+
is shown below:
|
|
657
|
+
|
|
658
|
+
```js
|
|
659
|
+
const errorHandler = (error, request, reply) => {
|
|
660
|
+
const statusCode = error.statusCode
|
|
661
|
+
let response
|
|
662
|
+
|
|
663
|
+
const { validation, validationContext } = error
|
|
664
|
+
|
|
665
|
+
// check if we have a validation error
|
|
666
|
+
if (validation) {
|
|
667
|
+
response = {
|
|
668
|
+
// validationContext will be 'body', 'params', 'headers', or 'query'
|
|
669
|
+
message: `A validation error occurred when validating the ${validationContext}...`,
|
|
670
|
+
// this is the result of the validation library...
|
|
671
|
+
errors: validation
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
response = {
|
|
675
|
+
message: 'An error occurred...'
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// any additional work here, eg. log error
|
|
680
|
+
// ...
|
|
681
|
+
|
|
682
|
+
reply.status(statusCode).send(response)
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Serialization
|
|
687
|
+
<a id="serialization"></a>
|
|
688
|
+
|
|
689
|
+
Fastify uses [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify)
|
|
690
|
+
to send data as JSON if an output schema is provided in the route options. Using
|
|
691
|
+
an output schema can drastically increase throughput and help prevent accidental
|
|
692
|
+
disclosure of sensitive information.
|
|
693
|
+
|
|
694
|
+
Example:
|
|
695
|
+
```js
|
|
696
|
+
const schema = {
|
|
697
|
+
response: {
|
|
698
|
+
200: {
|
|
699
|
+
type: 'object',
|
|
700
|
+
properties: {
|
|
701
|
+
value: { type: 'string' },
|
|
702
|
+
otherValue: { type: 'boolean' }
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fastify.post('/the/url', { schema }, handler)
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
The response schema is based on the status code. To use the same schema for
|
|
712
|
+
multiple status codes, use `'2xx'` or `default`, for example:
|
|
713
|
+
```js
|
|
714
|
+
const schema = {
|
|
715
|
+
response: {
|
|
716
|
+
default: {
|
|
717
|
+
type: 'object',
|
|
718
|
+
properties: {
|
|
719
|
+
error: {
|
|
720
|
+
type: 'boolean',
|
|
721
|
+
default: true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
'2xx': {
|
|
726
|
+
type: 'object',
|
|
727
|
+
properties: {
|
|
728
|
+
value: { type: 'string' },
|
|
729
|
+
otherValue: { type: 'boolean' }
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
201: {
|
|
733
|
+
// the contract syntax
|
|
734
|
+
value: { type: 'string' }
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
fastify.post('/the/url', { schema }, handler)
|
|
740
|
+
```
|
|
741
|
+
A specific response schema can be defined for different content types.
|
|
742
|
+
For example:
|
|
743
|
+
```js
|
|
744
|
+
const schema = {
|
|
745
|
+
response: {
|
|
746
|
+
200: {
|
|
747
|
+
description: 'Response schema that support different content types'
|
|
748
|
+
content: {
|
|
749
|
+
'application/json': {
|
|
750
|
+
schema: {
|
|
751
|
+
type: 'object',
|
|
752
|
+
properties: {
|
|
753
|
+
name: { type: 'string' },
|
|
754
|
+
image: { type: 'string' },
|
|
755
|
+
address: { type: 'string' }
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
'application/vnd.v1+json': {
|
|
760
|
+
schema: {
|
|
761
|
+
type: 'array',
|
|
762
|
+
items: { $ref: 'test' }
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
'3xx': {
|
|
768
|
+
content: {
|
|
769
|
+
'application/vnd.v2+json': {
|
|
770
|
+
schema: {
|
|
771
|
+
type: 'object',
|
|
772
|
+
properties: {
|
|
773
|
+
fullName: { type: 'string' },
|
|
774
|
+
phone: { type: 'string' }
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
default: {
|
|
781
|
+
content: {
|
|
782
|
+
// */* is match-all content-type
|
|
783
|
+
'*/*': {
|
|
784
|
+
schema: {
|
|
785
|
+
type: 'object',
|
|
786
|
+
properties: {
|
|
787
|
+
desc: { type: 'string' }
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
fastify.post('/url', { schema }, handler)
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
#### Serializer Compiler
|
|
800
|
+
<a id="schema-serializer"></a>
|
|
801
|
+
|
|
802
|
+
The `serializerCompiler` returns a function that must return a string from an
|
|
803
|
+
input object. When defining a response JSON Schema, change the default
|
|
804
|
+
serialization method by providing a function to serialize each route.
|
|
805
|
+
|
|
806
|
+
```js
|
|
807
|
+
fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
|
|
808
|
+
return data => JSON.stringify(data)
|
|
809
|
+
})
|
|
810
|
+
|
|
811
|
+
fastify.get('/user', {
|
|
812
|
+
handler (req, reply) {
|
|
813
|
+
reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' })
|
|
814
|
+
},
|
|
815
|
+
schema: {
|
|
816
|
+
response: {
|
|
817
|
+
'2xx': {
|
|
818
|
+
type: 'object',
|
|
819
|
+
properties: {
|
|
820
|
+
id: { type: 'number' },
|
|
821
|
+
name: { type: 'string' }
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
})
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
*To set a custom serializer in a specific part of the code, use
|
|
830
|
+
[`reply.serializer(...)`](./Reply.md#serializerfunc).*
|
|
831
|
+
|
|
832
|
+
### Error Handling
|
|
833
|
+
When schema validation fails for a request, Fastify will automatically return a
|
|
834
|
+
status 400 response including the result from the validator in the payload. For
|
|
835
|
+
example, if the following schema is used for a route:
|
|
836
|
+
|
|
837
|
+
```js
|
|
838
|
+
const schema = {
|
|
839
|
+
body: {
|
|
840
|
+
type: 'object',
|
|
841
|
+
properties: {
|
|
842
|
+
name: { type: 'string' }
|
|
843
|
+
},
|
|
844
|
+
required: ['name']
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
If the request fails to satisfy the schema, the route will return a response
|
|
850
|
+
with the following payload:
|
|
851
|
+
|
|
852
|
+
```js
|
|
853
|
+
{
|
|
854
|
+
"statusCode": 400,
|
|
855
|
+
"error": "Bad Request",
|
|
856
|
+
"message": "body should have required property 'name'"
|
|
857
|
+
}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
> ⚠ Security Consideration: By default, validation error details from the schema
|
|
861
|
+
> are included in the response payload. If your organization requires sanitizing
|
|
862
|
+
> or customizing these error messages (e.g., to avoid exposing internal schema
|
|
863
|
+
> details), configure a custom error handler using
|
|
864
|
+
> [`setErrorHandler()`](./Server.md#seterrorhandler).
|
|
865
|
+
|
|
866
|
+
To handle errors inside the route, specify the `attachValidation` option. If
|
|
867
|
+
there is a validation error, the `validationError` property of the request will
|
|
868
|
+
contain the `Error` object with the raw validation result as shown below:
|
|
869
|
+
|
|
870
|
+
```js
|
|
871
|
+
const fastify = Fastify()
|
|
872
|
+
|
|
873
|
+
fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
|
|
874
|
+
if (req.validationError) {
|
|
875
|
+
// `req.validationError.validation` contains the raw validation error
|
|
876
|
+
reply.code(400).send(req.validationError)
|
|
877
|
+
}
|
|
878
|
+
})
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
#### `schemaErrorFormatter`
|
|
882
|
+
|
|
883
|
+
To format errors, provide a sync function that returns an error as the
|
|
884
|
+
`schemaErrorFormatter` option when instantiating Fastify. The context function
|
|
885
|
+
will be the Fastify server instance.
|
|
886
|
+
|
|
887
|
+
`errors` is an array of Fastify schema errors `FastifySchemaValidationError`.
|
|
888
|
+
`dataVar` is the currently validated part of the schema (params, body,
|
|
889
|
+
querystring, headers).
|
|
890
|
+
|
|
891
|
+
```js
|
|
892
|
+
const fastify = Fastify({
|
|
893
|
+
schemaErrorFormatter: (errors, dataVar) => {
|
|
894
|
+
// ... my formatting logic
|
|
895
|
+
return new Error(myErrorMessage)
|
|
896
|
+
}
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
// or
|
|
900
|
+
fastify.setSchemaErrorFormatter(function (errors, dataVar) {
|
|
901
|
+
this.log.error({ err: errors }, 'Validation failed')
|
|
902
|
+
// ... my formatting logic
|
|
903
|
+
return new Error(myErrorMessage)
|
|
904
|
+
})
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
Use [setErrorHandler](./Server.md#seterrorhandler) to define a custom response
|
|
908
|
+
for validation errors such as:
|
|
909
|
+
|
|
910
|
+
```js
|
|
911
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
912
|
+
if (error.validation) {
|
|
913
|
+
reply.status(422).send(new Error('validation failed'))
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
For custom error responses in the schema, see
|
|
919
|
+
[`ajv-errors`](https://github.com/ajv-validator/ajv-errors). Check out the
|
|
920
|
+
[example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js)
|
|
921
|
+
usage.
|
|
922
|
+
|
|
923
|
+
> Install version 1.0.1 of `ajv-errors`, as later versions are not compatible
|
|
924
|
+
> with AJV v6 (the version shipped by Fastify v3).
|
|
925
|
+
|
|
926
|
+
Below is an example showing how to add **custom error messages for each
|
|
927
|
+
property** of a schema by supplying custom AJV options. Inline comments in the
|
|
928
|
+
schema describe how to configure it to show a different error message for each
|
|
929
|
+
case:
|
|
930
|
+
|
|
931
|
+
```js
|
|
932
|
+
const fastify = Fastify({
|
|
933
|
+
ajv: {
|
|
934
|
+
customOptions: {
|
|
935
|
+
jsonPointers: true,
|
|
936
|
+
// ⚠ Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/
|
|
937
|
+
allErrors: true
|
|
938
|
+
},
|
|
939
|
+
plugins: [
|
|
940
|
+
require('ajv-errors')
|
|
941
|
+
]
|
|
942
|
+
}
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
const schema = {
|
|
946
|
+
body: {
|
|
947
|
+
type: 'object',
|
|
948
|
+
properties: {
|
|
949
|
+
name: {
|
|
950
|
+
type: 'string',
|
|
951
|
+
errorMessage: {
|
|
952
|
+
type: 'Bad name'
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
age: {
|
|
956
|
+
type: 'number',
|
|
957
|
+
errorMessage: {
|
|
958
|
+
type: 'Bad age', // specify custom message for
|
|
959
|
+
min: 'Too young' // all constraints except required
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
required: ['name', 'age'],
|
|
964
|
+
errorMessage: {
|
|
965
|
+
required: {
|
|
966
|
+
name: 'Why no name!', // specify error message for when the
|
|
967
|
+
age: 'Why no age!' // property is missing from input
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
fastify.post('/', { schema, }, (request, reply) => {
|
|
974
|
+
reply.send({
|
|
975
|
+
hello: 'world'
|
|
976
|
+
})
|
|
977
|
+
})
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
To return localized error messages, see
|
|
981
|
+
[ajv-i18n](https://github.com/ajv-validator/ajv-i18n).
|
|
982
|
+
|
|
983
|
+
```js
|
|
984
|
+
const localize = require('ajv-i18n')
|
|
985
|
+
|
|
986
|
+
const fastify = Fastify()
|
|
987
|
+
|
|
988
|
+
const schema = {
|
|
989
|
+
body: {
|
|
990
|
+
type: 'object',
|
|
991
|
+
properties: {
|
|
992
|
+
name: {
|
|
993
|
+
type: 'string',
|
|
994
|
+
},
|
|
995
|
+
age: {
|
|
996
|
+
type: 'number',
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
required: ['name', 'age'],
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
1004
|
+
if (error.validation) {
|
|
1005
|
+
localize.ru(error.validation)
|
|
1006
|
+
reply.status(400).send(error.validation)
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
1009
|
+
reply.send(error)
|
|
1010
|
+
})
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
### JSON Schema support
|
|
1014
|
+
|
|
1015
|
+
JSON Schema provides utilities to optimize schemas. Combined with Fastify's
|
|
1016
|
+
shared schema, all schemas can be easily reused.
|
|
1017
|
+
|
|
1018
|
+
| Use Case | Validator | Serializer |
|
|
1019
|
+
|-----------------------------------|-----------|------------|
|
|
1020
|
+
| `$ref` to `$id` | ️️✔️ | ✔️ |
|
|
1021
|
+
| `$ref` to `/definitions` | ✔️ | ✔️ |
|
|
1022
|
+
| `$ref` to shared schema `$id` | ✔️ | ✔️ |
|
|
1023
|
+
| `$ref` to shared schema `/definitions` | ✔️ | ✔️ |
|
|
1024
|
+
|
|
1025
|
+
#### Examples
|
|
1026
|
+
|
|
1027
|
+
##### Usage of `$ref` to `$id` in same JSON Schema
|
|
1028
|
+
|
|
1029
|
+
```js
|
|
1030
|
+
const refToId = {
|
|
1031
|
+
type: 'object',
|
|
1032
|
+
definitions: {
|
|
1033
|
+
foo: {
|
|
1034
|
+
$id: '#address',
|
|
1035
|
+
type: 'object',
|
|
1036
|
+
properties: {
|
|
1037
|
+
city: { type: 'string' }
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
properties: {
|
|
1042
|
+
home: { $ref: '#address' },
|
|
1043
|
+
work: { $ref: '#address' }
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
##### Usage of `$ref` to `/definitions` in same JSON Schema
|
|
1050
|
+
```js
|
|
1051
|
+
const refToDefinitions = {
|
|
1052
|
+
type: 'object',
|
|
1053
|
+
definitions: {
|
|
1054
|
+
foo: {
|
|
1055
|
+
$id: '#address',
|
|
1056
|
+
type: 'object',
|
|
1057
|
+
properties: {
|
|
1058
|
+
city: { type: 'string' }
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
properties: {
|
|
1063
|
+
home: { $ref: '#/definitions/foo' },
|
|
1064
|
+
work: { $ref: '#/definitions/foo' }
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
##### Usage `$ref` to a shared schema `$id` as external schema
|
|
1070
|
+
```js
|
|
1071
|
+
fastify.addSchema({
|
|
1072
|
+
$id: 'http://foo/common.json',
|
|
1073
|
+
type: 'object',
|
|
1074
|
+
definitions: {
|
|
1075
|
+
foo: {
|
|
1076
|
+
$id: '#address',
|
|
1077
|
+
type: 'object',
|
|
1078
|
+
properties: {
|
|
1079
|
+
city: { type: 'string' }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
const refToSharedSchemaId = {
|
|
1086
|
+
type: 'object',
|
|
1087
|
+
properties: {
|
|
1088
|
+
home: { $ref: 'http://foo/common.json#address' },
|
|
1089
|
+
work: { $ref: 'http://foo/common.json#address' }
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
##### Usage `$ref` to a shared schema `/definitions` as external schema
|
|
1095
|
+
```js
|
|
1096
|
+
fastify.addSchema({
|
|
1097
|
+
$id: 'http://foo/shared.json',
|
|
1098
|
+
type: 'object',
|
|
1099
|
+
definitions: {
|
|
1100
|
+
foo: {
|
|
1101
|
+
type: 'object',
|
|
1102
|
+
properties: {
|
|
1103
|
+
city: { type: 'string' }
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
})
|
|
1108
|
+
|
|
1109
|
+
const refToSharedSchemaDefinitions = {
|
|
1110
|
+
type: 'object',
|
|
1111
|
+
properties: {
|
|
1112
|
+
home: { $ref: 'http://foo/shared.json#/definitions/foo' },
|
|
1113
|
+
work: { $ref: 'http://foo/shared.json#/definitions/foo' }
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
### Resources
|
|
1119
|
+
<a id="resources"></a>
|
|
1120
|
+
|
|
1121
|
+
- [JSON Schema](https://json-schema.org/)
|
|
1122
|
+
- [Understanding JSON
|
|
1123
|
+
Schema](https://json-schema.org/understanding-json-schema/about)
|
|
1124
|
+
- [fast-json-stringify
|
|
1125
|
+
documentation](https://github.com/fastify/fast-json-stringify)
|
|
1126
|
+
- [Ajv documentation](https://github.com/ajv-validator/ajv/blob/master/README.md)
|
|
1127
|
+
- [Ajv i18n](https://github.com/ajv-validator/ajv-i18n)
|
|
1128
|
+
- [Ajv custom errors](https://github.com/ajv-validator/ajv-errors)
|
|
1129
|
+
- Custom error handling with core methods with error file dumping
|
|
1130
|
+
[example](https://github.com/fastify/example/tree/main/validation-messages)
|