fastify 3.21.0 → 3.21.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 CHANGED
@@ -28,6 +28,19 @@ Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yello
28
28
  An efficient server implies a lower cost of the infrastructure, a better responsiveness under load and happy users.
29
29
  How can you efficiently handle the resources of your server, knowing that you are serving the highest number of requests as possible, without sacrificing security validations and handy development?
30
30
 
31
+ - [Quick start](./README.md#quick-start)
32
+ - [Install](./README.md#install)
33
+ - [Example](./README.md#example)
34
+ - [Fastify v1.x and v2.x](./README.md#fastify-v1x-and-v2x)
35
+ - [Core features](./README.md#core-features)
36
+ - [Benchmarks](./README.md#benchmarks)
37
+ - [Documentation](./README.md#documentation)
38
+ - [Ecosystem](./README.md#ecosystem)
39
+ - [Support](./README.md#support)
40
+ - [Team](./README.md#team)
41
+ - [Hosted by](./README.md#hosted-by)
42
+ - [License](./README.md#license)
43
+
31
44
  Enter Fastify. Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.
32
45
 
33
46
  ### Quick start
package/docs/Ecosystem.md CHANGED
@@ -47,7 +47,7 @@ Plugins maintained by the Fastify team are listed under [Core](#core) while plug
47
47
  - [`@fastify/session`](https://github.com/fastify/session) a session plugin for Fastify.
48
48
  - [`fastify-static`](https://github.com/fastify/fastify-static) Plugin for serving static files as fast as possible.
49
49
  - [`fastify-swagger`](https://github.com/fastify/fastify-swagger) Plugin for serving Swagger/OpenAPI documentation for Fastify, supporting dynamic generation.
50
- - [`fastify-websocket`](https://github.com/fastify/fastify-websocket) WebSocket support for Fastify. Built upon [websocket-stream](https://github.com/maxogden/websocket-stream).
50
+ - [`fastify-websocket`](https://github.com/fastify/fastify-websocket) WebSocket support for Fastify. Built upon [ws](https://github.com/websockets/ws).
51
51
  - [`fastify-url-data`](https://github.com/fastify/fastify-url-data) Decorate the `Request` object with a method to access raw URL components.
52
52
  - [`point-of-view`](https://github.com/fastify/point-of-view) Templates rendering (_ejs, pug, handlebars, marko_) plugin support for Fastify.
53
53
  - [`under-pressure`](https://github.com/fastify/under-pressure) Measure process load with automatic handling of _"Service Unavailable"_ plugin for Fastify.
@@ -39,7 +39,9 @@ For a concrete example, consider the situation where:
39
39
 
40
40
  There are many reverse proxy solutions available, and your environment may
41
41
  dictate the solution to use, e.g. AWS or GCP. Given the above, we could use
42
- [HAProxy][haproxy] to solve these requirements:
42
+ [HAProxy][haproxy] or [Nginx][nginx] to solve these requirements:
43
+
44
+ ### HAProxy
43
45
 
44
46
  ```conf
45
47
  # The global section defines base HAProxy (engine) instance configuration.
@@ -161,6 +163,83 @@ backend static-backend
161
163
  [why-use]: https://web.archive.org/web/20190821102906/https://medium.com/intrinsic/why-should-i-use-a-reverse-proxy-if-node-js-is-production-ready-5a079408b2ca
162
164
  [haproxy]: https://www.haproxy.org/
163
165
 
166
+ ### Nginx
167
+
168
+ ```nginx
169
+ upstream fastify_app {
170
+ # more info: http://nginx.org/en/docs/http/ngx_http_upstream_module.html
171
+ server 10.10.11.1:80;
172
+ server 10.10.11.2:80;
173
+ server 10.10.11.3:80 backup;
174
+ }
175
+
176
+ server {
177
+ # default server
178
+ listen 80 default_server;
179
+ listen [::]:80 default_server;
180
+
181
+ # specify host
182
+ # listen 80;
183
+ # listen [::]:80;
184
+ # server_name example.tld;
185
+
186
+ location / {
187
+ return 301 https://$host$request_uri;
188
+ }
189
+ }
190
+
191
+ server {
192
+ # default server
193
+ listen 443 ssl http2 default_server;
194
+ listen [::]:443 ssl http2 default_server;
195
+
196
+ # specify host
197
+ # listen 443 ssl http2;
198
+ # listen [::]:443 ssl http2;
199
+ # server_name example.tld;
200
+
201
+ # public private keys
202
+ ssl_certificate /path/to/fullchain.pem;
203
+ ssl_certificate_key /path/to/private.pem;
204
+ ssl_trusted_certificate /path/to/chain.pem;
205
+
206
+ # use https://ssl-config.mozilla.org/ for best practice configuration
207
+ ssl_session_timeout 1d;
208
+ ssl_session_cache shared:FastifyApp:10m;
209
+ ssl_session_tickets off;
210
+
211
+ # modern configuration
212
+ ssl_protocols TLSv1.3;
213
+ ssl_prefer_server_ciphers off;
214
+
215
+ # HSTS (ngx_http_headers_module is required) (63072000 seconds)
216
+ add_header Strict-Transport-Security "max-age=63072000" always;
217
+
218
+ # OCSP stapling
219
+ ssl_stapling on;
220
+ ssl_stapling_verify on;
221
+
222
+ # custom resolver
223
+ # resolver 127.0.0.1;
224
+
225
+ location / {
226
+ # more info: http://nginx.org/en/docs/http/ngx_http_proxy_module.html
227
+ proxy_http_version 1.1;
228
+ proxy_cache_bypass $http_upgrade;
229
+ proxy_set_header Upgrade $http_upgrade;
230
+ proxy_set_header Connection 'upgrade';
231
+ proxy_set_header Host $host;
232
+ proxy_set_header X-Real-IP $remote_addr;
233
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
234
+ proxy_set_header X-Forwarded-Proto $scheme;
235
+
236
+ proxy_pass http://fastify_app:3000;
237
+ }
238
+ }
239
+ ```
240
+
241
+ [nginx]: http://nginx.org/
242
+
164
243
  ## Kubernetes
165
244
  <a id="kubernetes"></a>
166
245
 
package/docs/Server.md CHANGED
@@ -8,6 +8,38 @@ The Fastify module exports a factory function that is used to create new
8
8
  an options object which is used to customize the resulting instance. This
9
9
  document describes the properties available in that options object.
10
10
 
11
+ - [http2](./Server.md#http2)
12
+ - [https](./Server.md#https)
13
+ - [connectionTimeout](./Server.md#connectiontimeout)
14
+ - [keepAliveTimeout](./Server.md#keepalivetimeout)
15
+ - [ignoreTrailingSlash](./Server.md#ignoretrailingslash)
16
+ - [maxParamLength](./Server.md#maxparamlength)
17
+ - [onProtoPoisoning](./Server.md#onprotopoisoning)
18
+ - [onConstructorPoisoning](./Server.md#onconstructorpoisoning)
19
+ - [logger](./Server.md#logger)
20
+ - [serverFactory](./Server.md#serverfactory)
21
+ - [jsonShorthand](./Server.md#jsonshorthand)
22
+ - [caseSensitive](./Server.md#casesensitive)
23
+ - [requestIdHeader](./Server.md#requestidheader)
24
+ - [requestIdLogLabel](./Server.md#requestidloglabel)
25
+ - [genReqId](./Server.md#genreqid)
26
+ - [trustProxy](./Server.md#trustProxy)
27
+ - [pluginTimeout](./Server.md#plugintimeout)
28
+ - [querystringParser](./Server.md#querystringParser)
29
+ - [exposeHeadRoutes](./Server.md#exposeheadroutes)
30
+ - [constraints](./Server.md#constraints)
31
+ - [return503OnClosing](./Server.md#return503onclosing)
32
+ - [ajv](./Server.md#ajv)
33
+ - [serializerOpts](./Server.md#serializeropts)
34
+ - [http2SessionTimeout](./Server.md#http2sessiontimeout)
35
+ - [frameworkErrors](./Server.md#frameworkerrors)
36
+ - [clientErrorHandler](./Server.md#clienterrorhandler)
37
+ - [rewriteUrl](./Server.md#rewriteurl)
38
+ - [Instance](./Server.md#instance)
39
+ - [Server Methods](./Server.md#server-methods)
40
+ - [initialConfig](./Server.md#initialConfig)
41
+
42
+
11
43
  <a name="factory-http2"></a>
12
44
  ### `http2`
13
45
 
@@ -255,6 +255,46 @@ curl -X GET "http://localhost:3000/?ids=1
255
255
  {"params":{"hello":["1"]}}
256
256
  ```
257
257
 
258
+ You can also specify a custom schema validator for each parameter type (body, querystring, params, headers).
259
+
260
+ For example, the following code disable type cohercion only for the `body` parameters, changing the ajv default options:
261
+
262
+ ```js
263
+ const schemaCompilers = {
264
+ body: new Ajv({
265
+ removeAdditional: false,
266
+ coerceTypes: false,
267
+ allErrors: true
268
+ }),
269
+ params: new Ajv({
270
+ removeAdditional: false,
271
+ coerceTypes: true,
272
+ allErrors: true
273
+ }),
274
+ querystring: new Ajv({
275
+ removeAdditional: false,
276
+ coerceTypes: true,
277
+ allErrors: true
278
+ }),
279
+ headers: new Ajv({
280
+ removeAdditional: false,
281
+ coerceTypes: true,
282
+ allErrors: true
283
+ })
284
+ }
285
+
286
+ server.setValidatorCompiler(req => {
287
+ if (!req.httpPart) {
288
+ throw new Error('Missing httpPart')
289
+ }
290
+ const compiler = schemaCompilers[req.httpPart]
291
+ if (!compiler) {
292
+ throw new Error(`Missing compiler for ${req.httpPart}`)
293
+ }
294
+ return compiler.compile(req.schema)
295
+ })
296
+ ```
297
+
258
298
  For further information see [here](https://ajv.js.org/coercion.html)
259
299
 
260
300
  <a name="ajv-plugins"></a>
package/lib/reply.js CHANGED
@@ -573,12 +573,17 @@ function handleError (reply, error, cb) {
573
573
  message: { value: error.message || '' },
574
574
  statusCode: { value: statusCode }
575
575
  }))
576
+
577
+ if (serializerFn !== false && typeof payload !== 'string') {
578
+ throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
579
+ }
576
580
  } catch (err) {
577
581
  // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
578
582
  reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
579
583
  res.statusCode = 500
580
584
  payload = serializeError({
581
585
  error: statusCodes['500'],
586
+ code: err.code,
582
587
  message: err.message,
583
588
  statusCode: 500
584
589
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.21.0",
3
+ "version": "3.21.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -322,6 +322,47 @@ test('Custom setSerializerCompiler', t => {
322
322
  })
323
323
  })
324
324
 
325
+ test('Custom setSerializerCompiler returns bad serialized output', t => {
326
+ t.plan(4)
327
+ const fastify = Fastify()
328
+
329
+ const outSchema = {
330
+ $id: 'test',
331
+ type: 'object',
332
+ whatever: 'need to be parsed by the custom serializer'
333
+ }
334
+
335
+ fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
336
+ return data => {
337
+ t.pass('returning an invalid serialization')
338
+ return { not: 'a string' }
339
+ }
340
+ })
341
+
342
+ fastify.get('/:id', {
343
+ handler (req, reply) { throw new Error('ops') },
344
+ schema: {
345
+ response: {
346
+ 500: outSchema
347
+ }
348
+ }
349
+ })
350
+
351
+ fastify.inject({
352
+ method: 'GET',
353
+ url: '/123'
354
+ }, (err, res) => {
355
+ t.error(err)
356
+ t.equal(res.statusCode, 500)
357
+ t.strictSame(res.json(), {
358
+ error: 'Internal Server Error',
359
+ code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
360
+ message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
361
+ statusCode: 500
362
+ })
363
+ })
364
+ })
365
+
325
366
  test('Custom serializer per route', async t => {
326
367
  const fastify = Fastify()
327
368
 
@@ -5,6 +5,32 @@ const Fastify = require('..')
5
5
 
6
6
  const AJV = require('ajv')
7
7
 
8
+ const customSchemaCompilers = {
9
+ body: new AJV({
10
+ coerceTypes: false
11
+ }),
12
+ params: new AJV({
13
+ coerceTypes: true
14
+ }),
15
+ querystring: new AJV({
16
+ coerceTypes: true
17
+ })
18
+ }
19
+
20
+ const customValidatorCompiler = req => {
21
+ if (!req.httpPart) {
22
+ throw new Error('Missing httpPart')
23
+ }
24
+
25
+ const compiler = customSchemaCompilers[req.httpPart]
26
+
27
+ if (!compiler) {
28
+ throw new Error(`Missing compiler for ${req.httpPart}`)
29
+ }
30
+
31
+ return compiler.compile(req.schema)
32
+ }
33
+
8
34
  const schemaA = {
9
35
  $id: 'urn:schema:foo',
10
36
  type: 'object',
@@ -846,3 +872,77 @@ test('Custom AJV settings - pt2', t => {
846
872
  t.equal(res.statusCode, 400)
847
873
  })
848
874
  })
875
+
876
+ test('Custom AJV settings on different parameters - pt1', t => {
877
+ t.plan(2)
878
+ const fastify = Fastify()
879
+
880
+ fastify.setValidatorCompiler(customValidatorCompiler)
881
+
882
+ fastify.post('/api/:id', {
883
+ schema: {
884
+ querystring: { id: { type: 'integer' } },
885
+ body: {
886
+ type: 'object',
887
+ properties: {
888
+ num: { type: 'number' }
889
+ },
890
+ required: ['num']
891
+ }
892
+ },
893
+ handler: (req, reply) => {
894
+ t.fail('the handler is not called because the "12" is not coerced to number')
895
+ }
896
+ })
897
+
898
+ fastify.inject({
899
+ method: 'POST',
900
+ url: '/api/42',
901
+ payload: {
902
+ num: '12'
903
+ }
904
+ }, (err, res) => {
905
+ t.error(err)
906
+ t.equal(res.statusCode, 400)
907
+ })
908
+ })
909
+
910
+ test('Custom AJV settings on different parameters - pt2', t => {
911
+ t.plan(4)
912
+ const fastify = Fastify()
913
+
914
+ fastify.setValidatorCompiler(customValidatorCompiler)
915
+
916
+ fastify.post('/api/:id', {
917
+ schema: {
918
+ params: {
919
+ type: 'object',
920
+ properties: {
921
+ id: { type: 'number' }
922
+ },
923
+ required: ['id']
924
+ },
925
+ body: {
926
+ type: 'object',
927
+ properties: {
928
+ num: { type: 'number' }
929
+ },
930
+ required: ['num']
931
+ }
932
+ },
933
+ handler: (req, reply) => {
934
+ t.same(typeof req.params.id, 'number')
935
+ t.same(typeof req.body.num, 'number')
936
+ t.same(req.params.id, 42)
937
+ t.same(req.body.num, 12)
938
+ }
939
+ })
940
+
941
+ fastify.inject({
942
+ method: 'POST',
943
+ url: '/api/42',
944
+ payload: {
945
+ num: 12
946
+ }
947
+ })
948
+ })
@@ -42,9 +42,15 @@ expectAssignable<FastifyInstance>(
42
42
  function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) {}
43
43
  server.setErrorHandler(fastifyErrorHandler)
44
44
 
45
+ async function asyncFastifyErrorHandler (this: FastifyInstance, error: FastifyError) {}
46
+ server.setErrorHandler(asyncFastifyErrorHandler)
47
+
45
48
  function nodeJSErrorHandler (error: NodeJS.ErrnoException) {}
46
49
  server.setErrorHandler(nodeJSErrorHandler)
47
50
 
51
+ function asyncNodeJSErrorHandler (error: NodeJS.ErrnoException) {}
52
+ server.setErrorHandler(asyncNodeJSErrorHandler)
53
+
48
54
  function notFoundHandler (request: FastifyRequest, reply: FastifyReply) {}
49
55
  function notFoundpreHandlerHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) { done() }
50
56
  async function notFoundpreHandlerAsyncHandler (request: FastifyRequest, reply: FastifyReply) {}
@@ -48,8 +48,8 @@ expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerRespon
48
48
  httpsServer
49
49
  .register(testPluginOpts)
50
50
  .after((_error) => { })
51
- .close(() => { })
52
51
  .ready((_error) => { })
52
+ .close(() => { })
53
53
 
54
54
  // Thenable
55
55
  expectAssignable<PromiseLike<undefined>>(httpsServer.after())
@@ -35,7 +35,8 @@
35
35
  "@typescript-eslint/no-empty-function": "off",
36
36
  "@typescript-eslint/explicit-function-return-type": "off",
37
37
  "@typescript-eslint/no-unused-vars": "off",
38
- "@typescript-eslint/no-non-null-assertion": "off"
38
+ "@typescript-eslint/no-non-null-assertion": "off",
39
+ "@typescript-eslint/no-misused-promises": ["error"]
39
40
  },
40
41
  "globals": {
41
42
  "NodeJS": "readonly"
@@ -37,8 +37,8 @@ export interface FastifyInstance<
37
37
  after(): FastifyInstance<RawServer, RawRequest, RawReply, Logger> & PromiseLike<undefined>;
38
38
  after(afterListener: (err: Error) => void): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
39
39
 
40
- close(): FastifyInstance<RawServer, RawRequest, RawReply, Logger> & PromiseLike<undefined>;
41
- close(closeListener: () => void): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
40
+ close(): Promise<undefined>;
41
+ close(closeListener: () => void): undefined;
42
42
 
43
43
  // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to
44
44
  decorate<T>(property: string | symbol,
@@ -357,7 +357,7 @@ export interface FastifyInstance<
357
357
  * Set a function that will be called whenever an error happens
358
358
  */
359
359
  setErrorHandler<TError extends Error = FastifyError, RouteGeneric extends RouteGenericInterface = RouteGenericInterface>(
360
- handler: (this: FastifyInstance<RawServer, RawRequest, RawReply, Logger>, error: TError, request: FastifyRequest<RouteGeneric, RawServer, RawRequest>, reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric>) => void
360
+ handler: (this: FastifyInstance<RawServer, RawRequest, RawReply, Logger>, error: TError, request: FastifyRequest<RouteGeneric, RawServer, RawRequest>, reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric>) => void | Promise<void>
361
361
  ): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
362
362
 
363
363
  /**