fastify 2.4.1 → 2.7.1
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/README.md +1 -1
- package/SECURITY.md +33 -0
- package/build/build-validation.js +1 -1
- package/docs/Decorators.md +2 -2
- package/docs/Ecosystem.md +5 -1
- package/docs/Fluent-Schema.md +5 -7
- package/docs/Hooks.md +40 -40
- package/docs/Logging.md +15 -3
- package/docs/Plugins-Guide.md +21 -21
- package/docs/Plugins.md +11 -11
- package/docs/Reply.md +14 -7
- package/docs/Routes.md +6 -6
- package/docs/Server.md +40 -19
- package/docs/Serverless.md +127 -28
- package/docs/Validation-and-Serialization.md +15 -12
- package/fastify.d.ts +22 -14
- package/fastify.js +21 -0
- package/lib/context.js +5 -4
- package/lib/decorate.js +2 -0
- package/lib/errors.js +1 -0
- package/lib/handleRequest.js +2 -2
- package/lib/reply.js +23 -6
- package/lib/request.js +2 -2
- package/lib/route.js +24 -15
- package/lib/schemas.js +24 -3
- package/lib/symbols.js +1 -0
- package/lib/validation.js +19 -0
- package/package.json +19 -18
- package/test/async-await.js +1 -1
- package/test/close-pipelining.test.js +43 -2
- package/test/close.test.js +69 -12
- package/test/content-length.test.js +2 -2
- package/test/decorator.test.js +2 -0
- package/test/fluent-schema.js +54 -0
- package/test/hooks-async.js +80 -1
- package/test/hooks.test.js +4 -4
- package/test/http2/closing.js +86 -33
- package/test/input-validation.test.js +1 -1
- package/test/internals/decorator.test.js +2 -0
- package/test/internals/initialConfig.test.js +1 -1
- package/test/internals/reply.test.js +276 -1
- package/test/internals/validation.test.js +57 -0
- package/test/logger.test.js +33 -1
- package/test/nullable-validation.test.js +52 -0
- package/test/plugin.test.js +2 -0
- package/test/register.test.js +2 -0
- package/test/route-prefix.test.js +31 -0
- package/test/route.test.js +35 -0
- package/test/shared-schemas.test.js +3 -3
- package/test/types/index.ts +33 -2
- package/test/versioned-routes.test.js +3 -3
package/docs/Routes.md
CHANGED
|
@@ -16,7 +16,7 @@ They need to be in
|
|
|
16
16
|
|
|
17
17
|
* `body`: validates the body of the request if it is a POST or a
|
|
18
18
|
PUT.
|
|
19
|
-
* `querystring`: validates the querystring. This can be a complete JSON
|
|
19
|
+
* `querystring` or `query`: validates the querystring. This can be a complete JSON
|
|
20
20
|
Schema object, with the property `type` of `object` and `properties` object of parameters, or
|
|
21
21
|
simply the values of what would be contained in the `properties` object as shown below.
|
|
22
22
|
* `params`: validates the params.
|
|
@@ -216,16 +216,16 @@ fastify.listen(3000)
|
|
|
216
216
|
```
|
|
217
217
|
```js
|
|
218
218
|
// routes/v1/users.js
|
|
219
|
-
module.exports = function (fastify, opts,
|
|
219
|
+
module.exports = function (fastify, opts, done) {
|
|
220
220
|
fastify.get('/user', handler_v1)
|
|
221
|
-
|
|
221
|
+
done()
|
|
222
222
|
}
|
|
223
223
|
```
|
|
224
224
|
```js
|
|
225
225
|
// routes/v2/users.js
|
|
226
|
-
module.exports = function (fastify, opts,
|
|
226
|
+
module.exports = function (fastify, opts, done) {
|
|
227
227
|
fastify.get('/user', handler_v2)
|
|
228
|
-
|
|
228
|
+
done()
|
|
229
229
|
}
|
|
230
230
|
```
|
|
231
231
|
Fastify will not complain because you are using the same name for two different routes, because at compilation time it will handle the prefix automatically *(this also means that the performance will not be affected at all!)*.
|
|
@@ -249,7 +249,7 @@ See the `prefixTrailingSlash` route option above to change this behaviour.
|
|
|
249
249
|
|
|
250
250
|
<a name="custom-log-level"></a>
|
|
251
251
|
### Custom Log Level
|
|
252
|
-
It could happen that you need different log levels in your routes,
|
|
252
|
+
It could happen that you need different log levels in your routes, Fastify achieves this in a very straightforward way.<br/>
|
|
253
253
|
You just need to pass the option `logLevel` to the plugin option or the route option with the [value](https://github.com/pinojs/pino/blob/master/docs/API.md#discussion-3) that you need.
|
|
254
254
|
|
|
255
255
|
Be aware that if you set the `logLevel` at plugin level, also the [`setNotFoundHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#setnotfoundhandler) and [`setErrorHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#seterrorhandler) will be affected.
|
package/docs/Server.md
CHANGED
|
@@ -150,14 +150,14 @@ custom `onRequest` and `onResponse` hooks.
|
|
|
150
150
|
|
|
151
151
|
```js
|
|
152
152
|
// Examples of hooks to replicate the disabled functionality.
|
|
153
|
-
fastify.addHook('onRequest', (req, reply,
|
|
153
|
+
fastify.addHook('onRequest', (req, reply, done) => {
|
|
154
154
|
req.log.info({ url: req.req.url, id: req.id }, 'received request')
|
|
155
|
-
|
|
155
|
+
done()
|
|
156
156
|
})
|
|
157
157
|
|
|
158
|
-
fastify.addHook('onResponse', (req, reply,
|
|
158
|
+
fastify.addHook('onResponse', (req, reply, done) => {
|
|
159
159
|
req.log.info({ url: req.req.originalUrl, statusCode: res.res.statusCode }, 'request completed')
|
|
160
|
-
|
|
160
|
+
done()
|
|
161
161
|
})
|
|
162
162
|
```
|
|
163
163
|
|
|
@@ -230,7 +230,7 @@ Especially in distributed systems, you may want to override the default id gener
|
|
|
230
230
|
```js
|
|
231
231
|
let i = 0
|
|
232
232
|
const fastify = require('fastify')({
|
|
233
|
-
genReqId: function (req) { return
|
|
233
|
+
genReqId: function (req) { return i++ }
|
|
234
234
|
})
|
|
235
235
|
```
|
|
236
236
|
|
|
@@ -352,6 +352,14 @@ fastify.get('/', (request, reply) => {
|
|
|
352
352
|
})
|
|
353
353
|
```
|
|
354
354
|
|
|
355
|
+
<a name="factory-return-503-on-closing"></a>
|
|
356
|
+
### `return503OnClosing`
|
|
357
|
+
|
|
358
|
+
Returns 503 after calling `close` server method.
|
|
359
|
+
If `false`, the server routes the incoming request as usual.
|
|
360
|
+
|
|
361
|
+
+ Default: `true`
|
|
362
|
+
|
|
355
363
|
## Instance
|
|
356
364
|
|
|
357
365
|
### Server Methods
|
|
@@ -368,16 +376,16 @@ It is always executed before the method `fastify.ready`.
|
|
|
368
376
|
|
|
369
377
|
```js
|
|
370
378
|
fastify
|
|
371
|
-
.register((instance, opts,
|
|
379
|
+
.register((instance, opts, done) => {
|
|
372
380
|
console.log('Current plugin')
|
|
373
|
-
|
|
381
|
+
done()
|
|
374
382
|
})
|
|
375
383
|
.after(err => {
|
|
376
384
|
console.log('After current plugin')
|
|
377
385
|
})
|
|
378
|
-
.register((instance, opts,
|
|
386
|
+
.register((instance, opts, done) => {
|
|
379
387
|
console.log('Next plugin')
|
|
380
|
-
|
|
388
|
+
done()
|
|
381
389
|
})
|
|
382
390
|
.ready(err => {
|
|
383
391
|
console.log('Everything has been loaded')
|
|
@@ -516,6 +524,7 @@ Method to add routes to the server, it also has shorthand functions, check [here
|
|
|
516
524
|
#### close
|
|
517
525
|
`fastify.close(callback)`: call this function to close the server instance and run the [`'onClose'`](https://github.com/fastify/fastify/blob/master/docs/Hooks.md#on-close) hook.<br>
|
|
518
526
|
Calling `close` will also cause the server to respond to every new incoming request with a `503` error and destroy that request.
|
|
527
|
+
See [`return503OnClosing` flags](https://github.com/fastify/fastify/blob/master/docs/Server.md#factory-return-503-on-closing) for changing this behaviour.
|
|
519
528
|
|
|
520
529
|
If it is called without any arguments, it will return a Promise:
|
|
521
530
|
|
|
@@ -551,24 +560,24 @@ The full path that will be prefixed to a route.
|
|
|
551
560
|
Example:
|
|
552
561
|
|
|
553
562
|
```js
|
|
554
|
-
fastify.register(function (instance, opts,
|
|
563
|
+
fastify.register(function (instance, opts, done) {
|
|
555
564
|
instance.get('/foo', function (request, reply) {
|
|
556
565
|
// Will log "prefix: /v1"
|
|
557
566
|
request.log.info('prefix: %s', instance.prefix)
|
|
558
567
|
reply.send({ prefix: instance.prefix })
|
|
559
568
|
})
|
|
560
569
|
|
|
561
|
-
instance.register(function (instance, opts,
|
|
570
|
+
instance.register(function (instance, opts, done) {
|
|
562
571
|
instance.get('/bar', function (request, reply) {
|
|
563
572
|
// Will log "prefix: /v1/v2"
|
|
564
573
|
request.log.info('prefix: %s', instance.prefix)
|
|
565
574
|
reply.send({ prefix: instance.prefix })
|
|
566
575
|
})
|
|
567
576
|
|
|
568
|
-
|
|
577
|
+
done()
|
|
569
578
|
}, { prefix: '/v2' })
|
|
570
579
|
|
|
571
|
-
|
|
580
|
+
done()
|
|
572
581
|
}, { prefix: '/v1' })
|
|
573
582
|
```
|
|
574
583
|
|
|
@@ -585,6 +594,18 @@ Fake http injection (for testing purposes) [here](https://github.com/fastify/fas
|
|
|
585
594
|
`fastify.addSchema(schemaObj)`, adds a shared schema to the Fastify instance. This allows you to reuse it everywhere in your application just by writing the schema id that you need.<br/>
|
|
586
595
|
To learn more, see [shared schema example](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#shared-schema) in the [Validation and Serialization](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md) documentation.
|
|
587
596
|
|
|
597
|
+
<a name="set-reply-serializer"></a>
|
|
598
|
+
#### setReplySerializer
|
|
599
|
+
Set the reply serializer for all the routes. This will used as default if a [Reply.serializer(func)](https://github.com/fastify/fastify/blob/master/docs/Reply.md#serializerfunc) has not been set. The handler is fully encapsulated, so different plugins can set different error handlers.
|
|
600
|
+
Note: the function parameter is called only for status `2xx`. Checkout the [`setErrorHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#seterrorhandler) for errors.
|
|
601
|
+
|
|
602
|
+
```js
|
|
603
|
+
fastify.setReplySerializer(function (payload, statusCode){
|
|
604
|
+
// serialize the payload with a sync function
|
|
605
|
+
return `my serialized ${statusCode} content: ${payload}`
|
|
606
|
+
})
|
|
607
|
+
```
|
|
608
|
+
|
|
588
609
|
<a name="set-schema-compiler"></a>
|
|
589
610
|
#### setSchemaCompiler
|
|
590
611
|
Set the schema compiler for all routes [here](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#schema-compiler).
|
|
@@ -602,24 +623,24 @@ You can also register a [`preValidation`](https://www.fastify.io/docs/latest/Hoo
|
|
|
602
623
|
|
|
603
624
|
```js
|
|
604
625
|
fastify.setNotFoundHandler({
|
|
605
|
-
preValidation: (req, reply,
|
|
626
|
+
preValidation: (req, reply, done) => {
|
|
606
627
|
// your code
|
|
607
|
-
|
|
628
|
+
done()
|
|
608
629
|
},
|
|
609
|
-
preHandler: (req, reply,
|
|
630
|
+
preHandler: (req, reply, done) => {
|
|
610
631
|
// your code
|
|
611
|
-
|
|
632
|
+
done()
|
|
612
633
|
}
|
|
613
634
|
}, function (request, reply) {
|
|
614
635
|
// Default not found handler with preValidation and preHandler hooks
|
|
615
636
|
})
|
|
616
637
|
|
|
617
|
-
fastify.register(function (instance, options,
|
|
638
|
+
fastify.register(function (instance, options, done) {
|
|
618
639
|
instance.setNotFoundHandler(function (request, reply) {
|
|
619
640
|
// Handle not found request without preValidation and preHandler hooks
|
|
620
641
|
// to URLs that begin with '/v1'
|
|
621
642
|
})
|
|
622
|
-
|
|
643
|
+
done()
|
|
623
644
|
}, { prefix: '/v1' })
|
|
624
645
|
```
|
|
625
646
|
|
package/docs/Serverless.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Run serverless applications and REST APIs using your existing Fastify application.
|
|
4
4
|
|
|
5
|
+
### Contents
|
|
6
|
+
|
|
7
|
+
- [AWS Lambda](#aws-lambda)
|
|
8
|
+
- [Google Cloud Run](#google-cloud-run)
|
|
9
|
+
|
|
5
10
|
### Attention Readers:
|
|
6
11
|
> Fastify is not designed to run on serverless environments.
|
|
7
12
|
The Fastify framework is designed to make implementing a traditional HTTP/S server easy.
|
|
@@ -12,21 +17,20 @@ it is possible to use Fastify in a serverless environment.
|
|
|
12
17
|
Again, keep in mind that this is not Fastify's intended use case and
|
|
13
18
|
we do not test for such integration scenarios.
|
|
14
19
|
|
|
15
|
-
|
|
16
20
|
## AWS Lambda
|
|
17
21
|
|
|
18
22
|
The sample provided allows you to easily build serverless web applications/services
|
|
19
23
|
and RESTful APIs using Fastify on top of AWS Lambda and Amazon API Gateway.
|
|
20
24
|
|
|
21
|
-
*Note:
|
|
25
|
+
*Note: Using [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) is just one possible way.*
|
|
22
26
|
|
|
23
27
|
### app.js
|
|
24
28
|
|
|
25
29
|
```js
|
|
26
30
|
const fastify = require('fastify');
|
|
27
31
|
|
|
28
|
-
function init(
|
|
29
|
-
const app = fastify(
|
|
32
|
+
function init() {
|
|
33
|
+
const app = fastify();
|
|
30
34
|
app.get('/', (request, reply) => reply.send({ hello: 'world' }));
|
|
31
35
|
return app;
|
|
32
36
|
}
|
|
@@ -43,11 +47,9 @@ if (require.main !== module) {
|
|
|
43
47
|
}
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
You can simply wrap your initialization code by offering to inject an optional [serverFactory](https://www.fastify.io/docs/latest/Server/#serverfactory).
|
|
47
|
-
|
|
48
50
|
When executed in your lambda function we don't need to listen to a specific port,
|
|
49
51
|
so we just export the wrapper function `init` in this case.
|
|
50
|
-
The [`lambda.js`](https://www.fastify.io/docs/latest/
|
|
52
|
+
The [`lambda.js`](https://www.fastify.io/docs/latest/Serverless/#lambda-js) file will use this export.
|
|
51
53
|
|
|
52
54
|
When you execute your Fastify application like always,
|
|
53
55
|
i.e. `node app.js` *(the detection for this could be `require.main === module`)*,
|
|
@@ -56,31 +58,28 @@ you can normally listen to your port, so you can still run your Fastify function
|
|
|
56
58
|
### lambda.js
|
|
57
59
|
|
|
58
60
|
```js
|
|
59
|
-
const
|
|
61
|
+
const awsLambdaFastify = require('aws-lambda-fastify')
|
|
60
62
|
const init = require('./app');
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
awsServerlessExpress.proxy(server, event, context, 'CALLBACK', callback);
|
|
74
|
-
});
|
|
75
|
-
};
|
|
64
|
+
const proxy = awsLambdaFastify(init())
|
|
65
|
+
// or
|
|
66
|
+
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
|
|
67
|
+
|
|
68
|
+
exports.handler = proxy;
|
|
69
|
+
// or
|
|
70
|
+
// exports.handler = (event, context, callback) => proxy(event, context, callback);
|
|
71
|
+
// or
|
|
72
|
+
// exports.handler = (event, context) => proxy(event, context);
|
|
73
|
+
// or
|
|
74
|
+
// exports.handler = async (event, context) => proxy(event, context);
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
We
|
|
79
|
-
(make sure you install the dependency `npm i --save aws-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
We just require [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify)
|
|
78
|
+
(make sure you install the dependency `npm i --save aws-lambda-fastify`) and our
|
|
79
|
+
[`app.js`](https://www.fastify.io/docs/latest/Serverless/#app-js) file and call the
|
|
80
|
+
exported `awsLambdaFastify` function with the `app` as the only parameter.
|
|
81
|
+
The resulting `proxy` function has the correct signature to be used as lambda `handler` function.
|
|
82
|
+
This way all the incoming events (API Gateway requests) are passed to the `proxy` function of [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify).
|
|
84
83
|
|
|
85
84
|
### Example
|
|
86
85
|
|
|
@@ -91,3 +90,103 @@ An example deployable with [claudia.js](https://claudiajs.com/tutorials/serverle
|
|
|
91
90
|
|
|
92
91
|
- API Gateway doesn't support streams yet, so you're not able to handle [streams](https://www.fastify.io/docs/latest/Reply/#streams).
|
|
93
92
|
- API Gateway has a timeout of 29 seconds, so it's important to provide a reply during this time.
|
|
93
|
+
|
|
94
|
+
## Google Cloud Run
|
|
95
|
+
|
|
96
|
+
Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless **container** environment. It's primary purpose is to provide an infrastucture-abstracted environment to run arbitrary containers. As a result, Fastify can be deployed to Google Cloud Run with little-to-no code changes from the way you would write your Fastify app normally.
|
|
97
|
+
|
|
98
|
+
*Follow the steps below to deploy to Google Cloud Run if you are already familiar with gcloud or just follow their [quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*
|
|
99
|
+
|
|
100
|
+
### Adjust Fastfiy server
|
|
101
|
+
|
|
102
|
+
In order for Fastify to properly listen for requests within the container, be sure to set the correct port and address:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
function build() {
|
|
106
|
+
const fastify = Fastify({ trustProxy: true })
|
|
107
|
+
return fastify
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function start() {
|
|
111
|
+
// Google Cloud Run will set this environment variable for you, so
|
|
112
|
+
// you can also use it to detect if you are running in Cloud Run
|
|
113
|
+
const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
|
|
114
|
+
|
|
115
|
+
// You must listen on the port Cloud Run provides
|
|
116
|
+
const port = process.env.PORT || 3000
|
|
117
|
+
|
|
118
|
+
// You must listen on all IPV4 addresses in Cloud Run
|
|
119
|
+
const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const server = build()
|
|
123
|
+
const address = await server.listen(port, address)
|
|
124
|
+
console.log(`Listening on ${address}`)
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(err)
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = build
|
|
132
|
+
|
|
133
|
+
if (require.main === module) {
|
|
134
|
+
start()
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Add a Dockerfile
|
|
139
|
+
|
|
140
|
+
You can add any valid `Dockerfile` that packages and runs a Node app. A basic `Dockerfile` can be found in the official [gcloud docs](https://github.com/knative/docs/blob/2d654d1fd6311750cc57187a86253c52f273d924/docs/serving/samples/hello-world/helloworld-nodejs/Dockerfile).
|
|
141
|
+
|
|
142
|
+
```Dockerfile
|
|
143
|
+
# Use the official Node.js 10 image.
|
|
144
|
+
# https://hub.docker.com/_/node
|
|
145
|
+
FROM node:10
|
|
146
|
+
|
|
147
|
+
# Create and change to the app directory.
|
|
148
|
+
WORKDIR /usr/src/app
|
|
149
|
+
|
|
150
|
+
# Copy application dependency manifests to the container image.
|
|
151
|
+
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
|
|
152
|
+
# Copying this separately prevents re-running npm install on every code change.
|
|
153
|
+
COPY package*.json ./
|
|
154
|
+
|
|
155
|
+
# Install production dependencies.
|
|
156
|
+
RUN npm install --only=production
|
|
157
|
+
|
|
158
|
+
# Copy local code to the container image.
|
|
159
|
+
COPY . .
|
|
160
|
+
|
|
161
|
+
# Run the web service on container startup.
|
|
162
|
+
CMD [ "npm", "start" ]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Add a .dockerignore
|
|
166
|
+
|
|
167
|
+
To keep build artifacts out of your container (which keeps it small and improves build times), add a `.dockerignore` file like the one below:
|
|
168
|
+
|
|
169
|
+
```.dockerignore
|
|
170
|
+
Dockerfile
|
|
171
|
+
README.md
|
|
172
|
+
node_modules
|
|
173
|
+
npm-debug.log
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Submit build
|
|
177
|
+
|
|
178
|
+
Next, submit your app to be built into a Docker image by running the following command (replacing `PROJECT-ID` and `APP-NAME` with your GCP project id and an app name:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Deploy Image
|
|
185
|
+
|
|
186
|
+
After your image has built, you can deploy it with the following command:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Your app will be accessible from the URL GCP provides.
|
|
@@ -4,9 +4,10 @@
|
|
|
4
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
5
|
|
|
6
6
|
> ## ⚠ Security Notice
|
|
7
|
+
> Treat the schema definition as application code.
|
|
7
8
|
> As both validation and serialization features dynamically evaluate
|
|
8
|
-
> code with `new Function()`, it is not safe to use
|
|
9
|
-
> user-provided
|
|
9
|
+
> code with `new Function()`, it is not safe to use
|
|
10
|
+
> user-provided schemas. See [Ajv](http://npm.im/ajv) and
|
|
10
11
|
> [fast-json-stringify](http://npm.im/fast-json-stringify) for more
|
|
11
12
|
> details.
|
|
12
13
|
|
|
@@ -14,7 +15,7 @@ Fastify uses a schema-based approach, and even if it is not mandatory we recomme
|
|
|
14
15
|
### Validation
|
|
15
16
|
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
17
|
- `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
|
+
- `querystring` or `query`: 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
19
|
- `params`: validates the route params.
|
|
19
20
|
- `headers`: validates the request headers.
|
|
20
21
|
|
|
@@ -31,7 +32,7 @@ const bodyJsonSchema = {
|
|
|
31
32
|
maxItems: 3,
|
|
32
33
|
items: { type: 'integer' }
|
|
33
34
|
},
|
|
34
|
-
nullableKey: { type: ['number', 'null'] },
|
|
35
|
+
nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true }
|
|
35
36
|
multipleTypesKey: { type: ['boolean', 'number'] },
|
|
36
37
|
multipleRestrictedTypesKey: {
|
|
37
38
|
oneOf: [
|
|
@@ -155,7 +156,7 @@ fastify.route({
|
|
|
155
156
|
handler: () => {}
|
|
156
157
|
})
|
|
157
158
|
|
|
158
|
-
fastify.register((instance, opts,
|
|
159
|
+
fastify.register((instance, opts, done) => {
|
|
159
160
|
|
|
160
161
|
/**
|
|
161
162
|
* In children's scope can use schemas defined in upper scope like 'greetings'.
|
|
@@ -179,7 +180,7 @@ fastify.register((instance, opts, next) => {
|
|
|
179
180
|
handler: () => {}
|
|
180
181
|
})
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
done()
|
|
183
184
|
})
|
|
184
185
|
```
|
|
185
186
|
|
|
@@ -219,16 +220,16 @@ The function `getSchemas` returns the shared schemas available in the selected s
|
|
|
219
220
|
fastify.addSchema({ $id: 'one', my: 'hello' })
|
|
220
221
|
fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
|
|
221
222
|
|
|
222
|
-
fastify.register((instance, opts,
|
|
223
|
+
fastify.register((instance, opts, done) => {
|
|
223
224
|
instance.addSchema({ $id: 'two', my: 'ciao' })
|
|
224
225
|
instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })
|
|
225
226
|
|
|
226
|
-
instance.register((subinstance, opts,
|
|
227
|
+
instance.register((subinstance, opts, done) => {
|
|
227
228
|
subinstance.addSchema({ $id: 'three', my: 'hola' })
|
|
228
229
|
subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })
|
|
229
|
-
|
|
230
|
+
done()
|
|
230
231
|
})
|
|
231
|
-
|
|
232
|
+
done()
|
|
232
233
|
})
|
|
233
234
|
```
|
|
234
235
|
This example will returns:
|
|
@@ -251,7 +252,8 @@ Fastify's [baseline ajv configuration](https://github.com/epoberezkin/ajv#option
|
|
|
251
252
|
removeAdditional: true, // remove additional properties
|
|
252
253
|
useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
|
|
253
254
|
coerceTypes: true, // change data type of data to match type keyword
|
|
254
|
-
allErrors: true
|
|
255
|
+
allErrors: true, // check for all errors
|
|
256
|
+
nullable: true // support keyword "nullable" from Open API 3 specification.
|
|
255
257
|
}
|
|
256
258
|
```
|
|
257
259
|
|
|
@@ -265,7 +267,8 @@ const ajv = new Ajv({
|
|
|
265
267
|
removeAdditional: true,
|
|
266
268
|
useDefaults: true,
|
|
267
269
|
coerceTypes: true,
|
|
268
|
-
allErrors: true
|
|
270
|
+
allErrors: true,
|
|
271
|
+
nullable: true,
|
|
269
272
|
// any other options
|
|
270
273
|
// ...
|
|
271
274
|
})
|
package/fastify.d.ts
CHANGED
|
@@ -69,9 +69,9 @@ declare namespace fastify {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
type FastifyMiddleware<
|
|
72
|
-
HttpServer,
|
|
73
|
-
HttpRequest,
|
|
74
|
-
HttpResponse,
|
|
72
|
+
HttpServer = http.Server,
|
|
73
|
+
HttpRequest = http.IncomingMessage,
|
|
74
|
+
HttpResponse = http.ServerResponse,
|
|
75
75
|
Query = DefaultQuery,
|
|
76
76
|
Params = DefaultParams,
|
|
77
77
|
Headers = DefaultHeaders,
|
|
@@ -84,9 +84,9 @@ declare namespace fastify {
|
|
|
84
84
|
) => void
|
|
85
85
|
|
|
86
86
|
type FastifyMiddlewareWithPayload<
|
|
87
|
-
HttpServer,
|
|
88
|
-
HttpRequest,
|
|
89
|
-
HttpResponse,
|
|
87
|
+
HttpServer = http.Server,
|
|
88
|
+
HttpRequest = http.IncomingMessage,
|
|
89
|
+
HttpResponse = http.ServerResponse,
|
|
90
90
|
Query = DefaultQuery,
|
|
91
91
|
Params = DefaultParams,
|
|
92
92
|
Headers = DefaultHeaders,
|
|
@@ -100,8 +100,8 @@ declare namespace fastify {
|
|
|
100
100
|
) => void
|
|
101
101
|
|
|
102
102
|
type RequestHandler<
|
|
103
|
-
HttpRequest,
|
|
104
|
-
HttpResponse,
|
|
103
|
+
HttpRequest = http.IncomingMessage,
|
|
104
|
+
HttpResponse = http.ServerResponse,
|
|
105
105
|
Query = DefaultQuery,
|
|
106
106
|
Params = DefaultParams,
|
|
107
107
|
Headers = DefaultHeaders,
|
|
@@ -130,7 +130,7 @@ declare namespace fastify {
|
|
|
130
130
|
* fastify's wrapped version of node.js IncomingMessage
|
|
131
131
|
*/
|
|
132
132
|
interface FastifyRequest<
|
|
133
|
-
HttpRequest,
|
|
133
|
+
HttpRequest = http.IncomingMessage,
|
|
134
134
|
Query = DefaultQuery,
|
|
135
135
|
Params = DefaultParams,
|
|
136
136
|
Headers = DefaultHeaders,
|
|
@@ -166,6 +166,7 @@ declare namespace fastify {
|
|
|
166
166
|
getHeader(name: string): string | undefined
|
|
167
167
|
hasHeader(name: string): boolean
|
|
168
168
|
callNotFound(): void
|
|
169
|
+
getResponseTime(): number
|
|
169
170
|
type(contentType: string): FastifyReply<HttpResponse>
|
|
170
171
|
redirect(url: string): FastifyReply<HttpResponse>
|
|
171
172
|
redirect(statusCode: number, url: string): FastifyReply<HttpResponse>
|
|
@@ -178,6 +179,7 @@ declare namespace fastify {
|
|
|
178
179
|
}
|
|
179
180
|
type TrustProxyFunction = (addr: string, index: number) => boolean
|
|
180
181
|
interface ServerOptions {
|
|
182
|
+
caseSensitive?: boolean,
|
|
181
183
|
ignoreTrailingSlash?: boolean,
|
|
182
184
|
bodyLimit?: number,
|
|
183
185
|
pluginTimeout?: number,
|
|
@@ -196,7 +198,8 @@ declare namespace fastify {
|
|
|
196
198
|
},
|
|
197
199
|
deriveVersion<Context>(req: Object, ctx?: Context) : String,
|
|
198
200
|
},
|
|
199
|
-
modifyCoreObjects?: boolean
|
|
201
|
+
modifyCoreObjects?: boolean,
|
|
202
|
+
return503OnClosing?: boolean
|
|
200
203
|
}
|
|
201
204
|
interface ServerOptionsAsSecure extends ServerOptions {
|
|
202
205
|
https: http2.SecureServerOptions
|
|
@@ -264,9 +267,9 @@ declare namespace fastify {
|
|
|
264
267
|
* Route configuration options such as "url" and "method"
|
|
265
268
|
*/
|
|
266
269
|
interface RouteOptions<
|
|
267
|
-
HttpServer,
|
|
268
|
-
HttpRequest,
|
|
269
|
-
HttpResponse,
|
|
270
|
+
HttpServer = http.Server,
|
|
271
|
+
HttpRequest = http.IncomingMessage,
|
|
272
|
+
HttpResponse = http.ServerResponse,
|
|
270
273
|
Query = DefaultQuery,
|
|
271
274
|
Params = DefaultParams,
|
|
272
275
|
Headers = DefaultHeaders,
|
|
@@ -313,7 +316,7 @@ declare namespace fastify {
|
|
|
313
316
|
req: NodeJS.ReadableStream,
|
|
314
317
|
res: http.ServerResponse
|
|
315
318
|
},
|
|
316
|
-
headers:
|
|
319
|
+
headers: Record<string, string>,
|
|
317
320
|
statusCode: number,
|
|
318
321
|
statusMessage: string,
|
|
319
322
|
payload: string,
|
|
@@ -658,6 +661,11 @@ declare namespace fastify {
|
|
|
658
661
|
*/
|
|
659
662
|
setErrorHandler(handler: (error: FastifyError, request: FastifyRequest<HttpRequest>, reply: FastifyReply<HttpResponse>) => void): void
|
|
660
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Set a function that will be called whenever an error happens
|
|
666
|
+
*/
|
|
667
|
+
setReplySerializer(handler: (payload: string | object | Buffer | NodeJS.ReadableStream, statusCode: number) => string): void
|
|
668
|
+
|
|
661
669
|
/**
|
|
662
670
|
* Set the schema compiler for all routes.
|
|
663
671
|
*/
|
package/fastify.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
kHooks,
|
|
14
14
|
kSchemas,
|
|
15
15
|
kSchemaCompiler,
|
|
16
|
+
kReplySerializerDefault,
|
|
16
17
|
kContentTypeParser,
|
|
17
18
|
kReply,
|
|
18
19
|
kRequest,
|
|
@@ -118,6 +119,7 @@ function build (options) {
|
|
|
118
119
|
[kHooks]: new Hooks(),
|
|
119
120
|
[kSchemas]: schemas,
|
|
120
121
|
[kSchemaCompiler]: null,
|
|
122
|
+
[kReplySerializerDefault]: null,
|
|
121
123
|
[kContentTypeParser]: new ContentTypeParser(bodyLimit, (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning)),
|
|
122
124
|
[kReply]: Reply.buildReply(Reply),
|
|
123
125
|
[kRequest]: Request.buildRequest(Request),
|
|
@@ -167,6 +169,7 @@ function build (options) {
|
|
|
167
169
|
addSchema: addSchema,
|
|
168
170
|
getSchemas: schemas.getSchemas.bind(schemas),
|
|
169
171
|
setSchemaCompiler: setSchemaCompiler,
|
|
172
|
+
setReplySerializer: setReplySerializer,
|
|
170
173
|
// custom parsers
|
|
171
174
|
addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,
|
|
172
175
|
hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,
|
|
@@ -242,6 +245,7 @@ function build (options) {
|
|
|
242
245
|
fastify[kState].closing = true
|
|
243
246
|
router.closeRoutes()
|
|
244
247
|
if (fastify[kState].listening) {
|
|
248
|
+
// No new TCP connections are accepted
|
|
245
249
|
instance.server.close(done)
|
|
246
250
|
} else {
|
|
247
251
|
done(null)
|
|
@@ -279,6 +283,16 @@ function build (options) {
|
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
if (fastify[kState].started) {
|
|
286
|
+
if (fastify[kState].closing) {
|
|
287
|
+
// Force to return an error
|
|
288
|
+
const error = new Error('Server is closed')
|
|
289
|
+
if (cb) {
|
|
290
|
+
cb(error)
|
|
291
|
+
return
|
|
292
|
+
} else {
|
|
293
|
+
return Promise.reject(error)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
282
296
|
return lightMyRequest(httpHandler, opts, cb)
|
|
283
297
|
}
|
|
284
298
|
|
|
@@ -390,6 +404,13 @@ function build (options) {
|
|
|
390
404
|
return this
|
|
391
405
|
}
|
|
392
406
|
|
|
407
|
+
function setReplySerializer (replySerializer) {
|
|
408
|
+
throwIfAlreadyStarted('Cannot call "setReplySerializer" when fastify instance is already started!')
|
|
409
|
+
|
|
410
|
+
this[kReplySerializerDefault] = replySerializer
|
|
411
|
+
return this
|
|
412
|
+
}
|
|
413
|
+
|
|
393
414
|
// wrapper that we expose to the user for configure the custom error handler
|
|
394
415
|
function setErrorHandler (func) {
|
|
395
416
|
throwIfAlreadyStarted('Cannot call "setErrorHandler" when fastify instance is already started!')
|
package/lib/context.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kFourOhFourContext } = require('./symbols.js')
|
|
3
|
+
const { kFourOhFourContext, kReplySerializerDefault } = require('./symbols.js')
|
|
4
4
|
|
|
5
5
|
// Objects that holds the context of every request
|
|
6
6
|
// Every route holds an instance of this object.
|
|
7
|
-
function Context (schema, handler, Reply, Request, contentTypeParser, config, errorHandler, bodyLimit, logLevel, attachValidation) {
|
|
7
|
+
function Context (schema, handler, Reply, Request, contentTypeParser, config, errorHandler, bodyLimit, logLevel, attachValidation, replySerializer) {
|
|
8
8
|
this.schema = schema
|
|
9
9
|
this.handler = handler
|
|
10
10
|
this.Reply = Reply
|
|
@@ -22,14 +22,15 @@ function Context (schema, handler, Reply, Request, contentTypeParser, config, er
|
|
|
22
22
|
this.logLevel = logLevel
|
|
23
23
|
this[kFourOhFourContext] = null
|
|
24
24
|
this.attachValidation = attachValidation
|
|
25
|
+
this[kReplySerializerDefault] = replySerializer
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function defaultErrorHandler (error, request, reply) {
|
|
28
29
|
var res = reply.res
|
|
29
30
|
if (res.statusCode >= 500) {
|
|
30
|
-
|
|
31
|
+
reply.log.error({ req: reply.request.raw, res: res, err: error }, error && error.message)
|
|
31
32
|
} else if (res.statusCode >= 400) {
|
|
32
|
-
|
|
33
|
+
reply.log.info({ res: res, err: error }, error && error.message)
|
|
33
34
|
}
|
|
34
35
|
reply.send(error)
|
|
35
36
|
}
|