fastify 2.7.1 → 2.11.0

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.
Files changed (72) hide show
  1. package/README.md +15 -4
  2. package/build/build-validation.js +8 -0
  3. package/docs/Benchmarking.md +2 -2
  4. package/docs/ContentTypeParser.md +12 -10
  5. package/docs/Decorators.md +14 -14
  6. package/docs/Ecosystem.md +7 -1
  7. package/docs/Errors.md +13 -8
  8. package/docs/Fluent-Schema.md +9 -12
  9. package/docs/Getting-Started.md +29 -25
  10. package/docs/HTTP2.md +1 -1
  11. package/docs/Hooks.md +201 -186
  12. package/docs/LTS.md +6 -7
  13. package/docs/Logging.md +10 -10
  14. package/docs/Middleware.md +59 -0
  15. package/docs/Plugins-Guide.md +52 -52
  16. package/docs/Plugins.md +3 -0
  17. package/docs/Reply.md +47 -3
  18. package/docs/Routes.md +120 -8
  19. package/docs/Server.md +69 -3
  20. package/docs/Serverless.md +76 -4
  21. package/docs/TypeScript.md +33 -10
  22. package/docs/Validation-and-Serialization.md +137 -1
  23. package/examples/typescript-server.ts +1 -1
  24. package/fastify.d.ts +52 -13
  25. package/fastify.js +68 -7
  26. package/lib/configValidator.js +99 -52
  27. package/lib/contentTypeParser.js +4 -4
  28. package/lib/context.js +2 -1
  29. package/lib/errors.js +21 -18
  30. package/lib/fourOhFour.js +10 -10
  31. package/lib/handleRequest.js +1 -2
  32. package/lib/logger.js +2 -2
  33. package/lib/pluginUtils.js +32 -0
  34. package/lib/reply.js +41 -6
  35. package/lib/route.js +37 -9
  36. package/lib/schemas.js +23 -12
  37. package/lib/symbols.js +4 -1
  38. package/lib/validation.js +15 -9
  39. package/lib/wrapThenable.js +1 -1
  40. package/package.json +34 -26
  41. package/test/404s.test.js +41 -1
  42. package/test/async-await.js +66 -0
  43. package/test/custom-parser.test.js +1 -1
  44. package/test/custom-querystring-parser.test.js +1 -1
  45. package/test/decorator.test.js +48 -0
  46. package/test/emit-warning.test.js +3 -3
  47. package/test/fastify-instance.test.js +29 -0
  48. package/test/helper.js +7 -7
  49. package/test/hooks-async.js +4 -3
  50. package/test/hooks.test.js +27 -8
  51. package/test/input-validation.test.js +126 -0
  52. package/test/internals/errors.test.js +9 -1
  53. package/test/internals/initialConfig.test.js +4 -2
  54. package/test/internals/plugin.test.js +4 -4
  55. package/test/internals/reply.test.js +78 -6
  56. package/test/internals/schemas.test.js +30 -0
  57. package/test/internals/validation.test.js +18 -0
  58. package/test/listen.test.js +1 -1
  59. package/test/logger.test.js +314 -1
  60. package/test/plugin.test.js +171 -0
  61. package/test/promises.test.js +55 -0
  62. package/test/proto-poisoning.test.js +76 -0
  63. package/test/route-hooks.test.js +109 -91
  64. package/test/route-prefix.test.js +1 -1
  65. package/test/schemas.test.js +450 -0
  66. package/test/shared-schemas.test.js +2 -2
  67. package/test/stream.test.js +10 -6
  68. package/test/throw.test.js +48 -2
  69. package/test/types/index.ts +86 -1
  70. package/test/validation-error-handling.test.js +3 -3
  71. package/test/versioned-routes.test.js +1 -1
  72. package/docs/Middlewares.md +0 -59
@@ -140,7 +140,7 @@ test('onSend hook stream', t => {
140
140
  })
141
141
 
142
142
  test('Destroying streams prematurely', t => {
143
- t.plan(5)
143
+ t.plan(6)
144
144
 
145
145
  let fastify = null
146
146
  const logStream = split(JSON.parse)
@@ -190,7 +190,9 @@ test('Destroying streams prematurely', t => {
190
190
  response.on('readable', function () {
191
191
  response.destroy()
192
192
  })
193
- response.on('close', function () {
193
+
194
+ // Node bug? Node never emits 'close' here.
195
+ response.on('aborted', function () {
194
196
  t.pass('Response closed')
195
197
  })
196
198
  })
@@ -198,7 +200,7 @@ test('Destroying streams prematurely', t => {
198
200
  })
199
201
 
200
202
  test('Destroying streams prematurely should call close method', t => {
201
- t.plan(6)
203
+ t.plan(7)
202
204
 
203
205
  let fastify = null
204
206
  const logStream = split(JSON.parse)
@@ -249,7 +251,8 @@ test('Destroying streams prematurely should call close method', t => {
249
251
  response.on('readable', function () {
250
252
  response.destroy()
251
253
  })
252
- response.on('close', function () {
254
+ // Node bug? Node never emits 'close' here.
255
+ response.on('aborted', function () {
253
256
  t.pass('Response closed')
254
257
  })
255
258
  })
@@ -257,7 +260,7 @@ test('Destroying streams prematurely should call close method', t => {
257
260
  })
258
261
 
259
262
  test('Destroying streams prematurely should call abort method', t => {
260
- t.plan(6)
263
+ t.plan(7)
261
264
 
262
265
  let fastify = null
263
266
  const logStream = split(JSON.parse)
@@ -309,7 +312,8 @@ test('Destroying streams prematurely should call abort method', t => {
309
312
  response.on('readable', function () {
310
313
  response.destroy()
311
314
  })
312
- response.on('close', function () {
315
+ // Node bug? Node never emits 'close' here.
316
+ response.on('aborted', function () {
313
317
  t.pass('Response closed')
314
318
  })
315
319
  })
@@ -25,6 +25,52 @@ test('Fastify should throw on multiple assignment to the same route', t => {
25
25
  })
26
26
  })
27
27
 
28
+ test('Fastify should throw for an invalid schema, printing the error route - headers', t => {
29
+ t.plan(2)
30
+
31
+ const badSchema = {
32
+ type: 'object',
33
+ properties: {
34
+ bad: {
35
+ type: 'bad-type'
36
+ }
37
+ }
38
+ }
39
+
40
+ const fastify = Fastify()
41
+ fastify.get('/', { schema: { headers: badSchema } }, () => {})
42
+ fastify.get('/not-loaded', { schema: { headers: badSchema } }, () => {})
43
+
44
+ fastify.ready(err => {
45
+ t.is(err.code, 'FST_ERR_SCH_BUILD')
46
+ t.isLike(err.message, /Failed building the schema for GET: \//)
47
+ })
48
+ })
49
+
50
+ test('Fastify should throw for an invalid schema, printing the error route - body', t => {
51
+ t.plan(2)
52
+
53
+ const badSchema = {
54
+ type: 'object',
55
+ properties: {
56
+ bad: {
57
+ type: 'bad-type'
58
+ }
59
+ }
60
+ }
61
+
62
+ const fastify = Fastify()
63
+ fastify.register((instance, opts, next) => {
64
+ instance.post('/form', { schema: { body: badSchema } }, () => {})
65
+ next()
66
+ }, { prefix: 'hello' })
67
+
68
+ fastify.ready(err => {
69
+ t.is(err.code, 'FST_ERR_SCH_BUILD')
70
+ t.isLike(err.message, /Failed building the schema for POST: \/hello\/form/)
71
+ })
72
+ })
73
+
28
74
  test('Should throw on unsupported method', t => {
29
75
  t.plan(1)
30
76
  const fastify = Fastify()
@@ -131,7 +177,7 @@ test('Should throw on duplicate request decorator', t => {
131
177
  t.fail()
132
178
  } catch (e) {
133
179
  t.is(e.code, 'FST_ERR_DEC_ALREADY_PRESENT')
134
- t.is(e.message, `FST_ERR_DEC_ALREADY_PRESENT: The decorator 'foo' has already been added!`)
180
+ t.is(e.message, "FST_ERR_DEC_ALREADY_PRESENT: The decorator 'foo' has already been added!")
135
181
  }
136
182
  })
137
183
 
@@ -146,7 +192,7 @@ test('Should throw if request decorator dependencies are not met', t => {
146
192
  t.fail()
147
193
  } catch (e) {
148
194
  t.is(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY')
149
- t.is(e.message, `FST_ERR_DEC_MISSING_DEPENDENCY: The decorator is missing dependency 'world'.`)
195
+ t.is(e.message, "FST_ERR_DEC_MISSING_DEPENDENCY: The decorator is missing dependency 'world'.")
150
196
  }
151
197
  })
152
198
 
@@ -64,7 +64,31 @@ const cors = require('cors')
64
64
  maxParamLength: 200,
65
65
  querystringParser: (str: string) => ({ str: str, strArray: [str] }),
66
66
  modifyCoreObjects: true,
67
- return503OnClosing: true
67
+ return503OnClosing: true,
68
+ genReqId: () => {
69
+ if (Math.random() > 0.5) {
70
+ return Math.random().toString()
71
+ }
72
+ return Math.random()
73
+ },
74
+ requestIdHeader: 'request-id',
75
+ requestIdLogLabel: 'reqId',
76
+ serverFactory: (handler, options) => {
77
+ const server = http.createServer((req, res) => {
78
+ handler(req, res)
79
+ })
80
+ return server
81
+ }
82
+ })
83
+
84
+ // http2 server factory option
85
+ const otherHttp2Server = fastify({
86
+ serverFactory: (handler, options) => {
87
+ const server = http2.createServer((req, res) => {
88
+ handler(req, res)
89
+ })
90
+ return server
91
+ }
68
92
  })
69
93
 
70
94
  // custom types
@@ -102,6 +126,25 @@ server.use('/', (req, res, next) => {
102
126
  console.log(`${req.method} ${req.url}`)
103
127
  })
104
128
 
129
+ // Custom middleware with multiple paths
130
+ server.use(['/foo', '/bar'], (req, res, next) => {
131
+ console.log(`${req.method} ${req.url}`)
132
+ })
133
+
134
+ // Third party plugin
135
+ // Also check if async functions are allowed to be passed to .register()
136
+ // https://github.com/fastify/fastify/pull/1841
137
+ // All function parameters should be inferrable and should not produce 'any'
138
+ const thirdPartyPlugin: fastify.Plugin<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse, {}> = (instance, options, callback) => {}
139
+ const thirdPartyPluginAsync: fastify.Plugin<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse, {}> = async (instance, options) => {}
140
+
141
+ server.register(thirdPartyPlugin)
142
+ server.register(thirdPartyPluginAsync)
143
+
144
+ // Custom plugin
145
+ server.register((instance, options, callback) => {})
146
+ server.register(async (instance, options) => {})
147
+
105
148
  /**
106
149
  * Test various hooks and different signatures
107
150
  */
@@ -117,6 +160,18 @@ server.addHook('preHandler', function (req, reply, next) {
117
160
  }
118
161
  })
119
162
 
163
+ server.addHook('preHandler', async function (req, reply) {
164
+ this.log.debug('`this` is not `any`')
165
+ if (req.body.error) {
166
+ throw new Error('testing if middleware errors can be passed')
167
+ } else {
168
+ // `stream` can be accessed correctly because `server` is an http2 server.
169
+ console.log('req stream', req.req.stream)
170
+ console.log('res stream', reply.res.stream)
171
+ reply.code(200).send('ok')
172
+ }
173
+ })
174
+
120
175
  server.addHook('onRequest', function (req, reply, next) {
121
176
  this.log.debug('`this` is not `any`')
122
177
  console.log(`${req.raw.method} ${req.raw.url}`)
@@ -207,8 +262,13 @@ const opts: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2Se
207
262
  schemaCompiler: (schema: Object) => () => {},
208
263
  bodyLimit: 5000,
209
264
  logLevel: 'trace',
265
+ version: '1.0.0',
210
266
  config: { }
211
267
  }
268
+ const optsWithHandler: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> = {
269
+ ...opts,
270
+ handler (req, reply) { reply.send({ hello: 'route' }) }
271
+ }
212
272
 
213
273
  // Chaining and route definitions
214
274
  server
@@ -250,6 +310,7 @@ server
250
310
  reply.header('Content-Type', 'application/json').code(200)
251
311
  reply.send({ hello: 'world' })
252
312
  })
313
+ .get('/optsWithHandler', optsWithHandler)
253
314
  .get('/status', function (req, reply) {
254
315
  reply.status(204).send()
255
316
  })
@@ -279,17 +340,21 @@ server
279
340
  .headers({ 'Content-Type': 'application/json' })
280
341
  .send({ hello: 'world' })
281
342
  })
343
+ .post('/optsWithHandler', optsWithHandler)
282
344
  .head('/', {}, function (req, reply) {
283
345
  reply.send()
284
346
  })
347
+ .head('/optsWithHandler', optsWithHandler)
285
348
  .delete('/', opts, function (req, reply) {
286
349
  reply.send({ hello: 'world' })
287
350
  })
351
+ .delete('/optsWithHandler', optsWithHandler)
288
352
  .patch('/:id', opts, function (req, reply) {
289
353
  req.log.info(`incoming id is ${req.params.id}`)
290
354
 
291
355
  reply.send({ hello: 'world' })
292
356
  })
357
+ .patch('/optsWithHandler', optsWithHandler)
293
358
  .route({
294
359
  method: ['GET', 'POST', 'PUT'],
295
360
  url: '/multi-route',
@@ -317,6 +382,7 @@ server
317
382
  .all('/all/with-opts', opts, function (req, reply) {
318
383
  reply.send(req.headers)
319
384
  })
385
+ .all('/optsWithHandler', optsWithHandler)
320
386
  .route({
321
387
  method: 'GET',
322
388
  url: '/headers',
@@ -522,6 +588,13 @@ server.setSchemaCompiler(function (schema: object) {
522
588
  return () => true
523
589
  })
524
590
 
591
+ server.setSchemaResolver(function (ref: string) {
592
+ return {
593
+ $id: ref,
594
+ type: 'string'
595
+ }
596
+ })
597
+
525
598
  server.addSchema({})
526
599
 
527
600
  server.addContentTypeParser('*', (req, done) => {
@@ -635,3 +708,15 @@ server3.close(() => {})
635
708
  done()
636
709
  }
637
710
  }
711
+
712
+ type TestReplyDecoration = (this: fastify.FastifyReply<http.ServerResponse>) => void
713
+
714
+ const server4 = fastify()
715
+ const testReplyDecoration: TestReplyDecoration = function () {
716
+ console.log('can access request from reply decorator', this.request.id)
717
+ }
718
+ server4.decorateReply('test-request-accessible-from-reply', testReplyDecoration)
719
+
720
+ server4.get('/', (req, reply) => {
721
+ reply.removeHeader('x-foo').removeHeader('x-bar').send({})
722
+ })
@@ -278,7 +278,7 @@ test('should return a defined output message parsing AJV errors', t => {
278
278
  url: '/'
279
279
  }, (err, res) => {
280
280
  t.error(err)
281
- t.strictEqual(res.payload, `{"statusCode":400,"error":"Bad Request","message":"body should have required property 'name', body should have required property 'work'"}`)
281
+ t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body should have required property \'name\', body should have required property \'work\'"}')
282
282
  })
283
283
  })
284
284
 
@@ -306,7 +306,7 @@ test('should return a defined output message parsing JOI errors', t => {
306
306
  url: '/'
307
307
  }, (err, res) => {
308
308
  t.error(err)
309
- t.strictEqual(res.payload, `{"statusCode":400,"error":"Bad Request","message":"child \\"name\\" fails because [\\"name\\" is required]"}`)
309
+ t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"child \\"name\\" fails because [\\"name\\" is required]"}')
310
310
  })
311
311
  })
312
312
 
@@ -337,6 +337,6 @@ test('should return a defined output message parsing JOI error details', t => {
337
337
  url: '/'
338
338
  }, (err, res) => {
339
339
  t.error(err)
340
- t.strictEqual(res.payload, `{"statusCode":400,"error":"Bad Request","message":"body \\"name\\" is required"}`)
340
+ t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body \\"name\\" is required"}')
341
341
  })
342
342
  })
@@ -417,7 +417,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
417
417
  }
418
418
  },
419
419
  deriveVersion: (req, ctx) => {
420
- return req.headers['accept']
420
+ return req.headers.accept
421
421
  }
422
422
  }
423
423
 
@@ -1,59 +0,0 @@
1
- <h1 align="center">Fastify</h1>
2
-
3
- ## Middlewares
4
-
5
- Fastify out of the box provides an asynchronous [middleware engine](https://github.com/fastify/middie) compatible with [Express](https://expressjs.com/) and [Restify](http://restify.com/) middlewares.
6
-
7
- *If you need a visual feedback to understand when the middlewares are executed take a look to the [lifecycle](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md) page.*
8
-
9
- Fastify middlewares don't support the full syntax `middleware(err, req, res, next)`, because error handling is done inside Fastify.
10
- Furthermore methods added by Express and Restify to the enhanced versions of `req` and `res` are not supported in Fastify middlewares.
11
-
12
- Also, if you are using a middleware that bundles different, smaller middlewares, such as [*helmet*](https://helmetjs.github.io/), we recommend to use the single modules to get better performances.
13
-
14
- ```js
15
- fastify.use(require('cors')())
16
- fastify.use(require('dns-prefetch-control')())
17
- fastify.use(require('frameguard')())
18
- fastify.use(require('hide-powered-by')())
19
- fastify.use(require('hsts')())
20
- fastify.use(require('ienoopen')())
21
- fastify.use(require('x-xss-protection')())
22
- ```
23
-
24
- or, in the specific case of *helmet*, you can use the [*fastify-helmet*](https://github.com/fastify/fastify-helmet) [plugin](Plugins.md), which is an optimized helmet integration for fastify:
25
-
26
- ```js
27
- const fastify = require('fastify')()
28
- const helmet = require('fastify-helmet')
29
-
30
- fastify.register(helmet)
31
- ```
32
-
33
- Remember that middlewares can be encapsulated, this means that you can decide where your middlewares should run by using `register` as explained in the [plugins guide](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md).
34
-
35
- Fastify middlewares also do not expose the `send` method or other methods specific to the Fastify [Reply]('./Reply.md' "Reply") instance. This is because Fastify wraps the incoming `req` and `res` Node instances using the [Request](./Request.md "Request") and [Reply](./Reply.md "Reply") objects internally, but this is done after the middlewares phase. If you need to create a middleware you have to use the Node `req` and `res` instances. Otherwise, you can use the `preHandler` hook that has the [Request](./Request.md "Request") and [Reply](./Reply.md "Reply") Fastify instances. For more information, see [Hooks](./Hooks.md "Hooks").
36
-
37
- <a name="restrict-usage"></a>
38
- #### Restrict middleware execution to a certain path(s)
39
- If you need to run a middleware only under certain path(s), just pass the path as first parameter to `use` and you are done!
40
-
41
- *Note that this does not support routes with parameters, (eg: `/user/:id/comments`) and wildcard is not supported in multiple paths.*
42
-
43
- ```js
44
- const path = require('path')
45
- const serveStatic = require('serve-static')
46
-
47
- // Single path
48
- fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))
49
-
50
- // Wildcard path
51
- fastify.use('/css/*', serveStatic(path.join(__dirname, '/assets')))
52
-
53
- // Multiple paths
54
- fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))
55
- ```
56
-
57
- <a name="express-middleware"></a>
58
- #### Express middleware compatibility
59
- Express modifies the prototype of the node core Request and Response objects heavily so Fastify cannot guarantee full middleware compatibility. Express specific functionality such as `res.sendFile()`, `res.send()` or `express.Router()` instances will not work with Fastify. For example, [cors](https://github.com/expressjs/cors) is compatible while [passport](https://github.com/jaredhanson/passport) is not.