fastify 6.0.0-alpha.4 → 6.0.0-alpha.6

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 (63) hide show
  1. package/README.md +1 -0
  2. package/SECURITY.md +6 -0
  3. package/build/build-validation.js +2 -0
  4. package/docs/Guides/Ecosystem.md +12 -53
  5. package/docs/Guides/Migration-Guide-V5.md +2 -2
  6. package/docs/Guides/Serverless.md +33 -10
  7. package/docs/Reference/Errors.md +13 -2
  8. package/docs/Reference/Hooks.md +13 -0
  9. package/docs/Reference/Reply.md +787 -681
  10. package/docs/Reference/Request.md +308 -104
  11. package/docs/Reference/Routes.md +6 -2
  12. package/docs/Reference/Server.md +41 -4
  13. package/docs/Reference/Validation-and-Serialization.md +48 -1
  14. package/docs/Reference/Warnings.md +2 -0
  15. package/fastify.d.ts +1 -0
  16. package/fastify.js +35 -1
  17. package/lib/config-validator.js +102 -73
  18. package/lib/content-type-parser.js +35 -14
  19. package/lib/content-type.js +4 -4
  20. package/lib/context.js +2 -1
  21. package/lib/decorate.js +4 -3
  22. package/lib/error-handler.js +21 -4
  23. package/lib/error-status.js +4 -2
  24. package/lib/errors.js +15 -14
  25. package/lib/handle-request.js +7 -20
  26. package/lib/reply.js +18 -13
  27. package/lib/request.js +12 -7
  28. package/lib/route.js +40 -7
  29. package/lib/schemas.js +44 -32
  30. package/lib/validation.js +67 -10
  31. package/lib/warnings.js +10 -1
  32. package/package.json +8 -3
  33. package/test/child-logger-factory.test.js +25 -0
  34. package/test/close-disposable.test.js +154 -0
  35. package/test/constrained-routes.test.js +4 -2
  36. package/test/content-parser.test.js +99 -0
  37. package/test/decorator.test.js +29 -0
  38. package/test/diagnostics-channel/error-status.test.js +35 -2
  39. package/test/handler-timeout.test.js +224 -0
  40. package/test/internals/all.test.js +5 -0
  41. package/test/internals/errors.test.js +19 -20
  42. package/test/internals/initial-config.test.js +2 -0
  43. package/test/internals/reply-serialize.test.js +20 -0
  44. package/test/internals/reply.test.js +30 -1
  45. package/test/internals/request.test.js +50 -0
  46. package/test/internals/server.test.js +6 -4
  47. package/test/reply-error.test.js +82 -0
  48. package/test/reply-media-type.test.js +57 -0
  49. package/test/reply-trailers.test.js +159 -1
  50. package/test/rfc-10008.test.js +6 -9
  51. package/test/router-compile.test.js +241 -0
  52. package/test/schema-feature.test.js +1 -1
  53. package/test/schema-serialization.test.js +120 -7
  54. package/test/schema-validation.test.js +251 -1
  55. package/test/types/errors.tst.ts +0 -1
  56. package/test/types/fastify.tst.ts +9 -0
  57. package/test/types/instance.tst.ts +1 -1
  58. package/test/types/route.tst.ts +37 -0
  59. package/test/types/using.tst.ts +20 -0
  60. package/test/validation-error-handling.test.js +21 -1
  61. package/types/errors.d.ts +0 -1
  62. package/types/instance.d.ts +9 -5
  63. package/types/route.d.ts +4 -2
@@ -1,45 +1,52 @@
1
1
  <h1 align="center">Fastify</h1>
2
2
 
3
3
  ## Reply
4
- - [Reply](#reply)
5
- - [Introduction](#introduction)
6
- - [.code(statusCode)](#codestatuscode)
7
- - [.elapsedTime](#elapsedtime)
8
- - [.statusCode](#statuscode)
9
- - [.mediaType](#mediatype)
10
- - [.server](#server)
11
- - [.header(key, value)](#headerkey-value)
12
- - [.headers(object)](#headersobject)
13
- - [.getHeader(key)](#getheaderkey)
14
- - [.getHeaders()](#getheaders)
15
- - [.removeHeader(key)](#removeheaderkey)
16
- - [.hasHeader(key)](#hasheaderkey)
17
- - [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback)
18
- - [.trailer(key, function)](#trailerkey-function)
19
- - [.hasTrailer(key)](#hastrailerkey)
20
- - [.removeTrailer(key)](#removetrailerkey)
21
- - [.redirect(dest, [code ,])](#redirectdest--code)
22
- - [.callNotFound()](#callnotfound)
23
- - [.type(contentType)](#typecontenttype)
24
- - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
25
- - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
26
- - [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus)
27
- - [.serializer(func)](#serializerfunc)
28
- - [.raw](#raw)
29
- - [.sent](#sent)
30
- - [.hijack()](#hijack)
31
- - [.send(data)](#senddata)
32
- - [Objects](#objects)
33
- - [Strings](#strings)
34
- - [Streams](#streams)
35
- - [Buffers](#buffers)
36
- - [TypedArrays](#typedarrays)
37
- - [ReadableStream](#readablestream)
38
- - [Response](#response)
39
- - [Errors](#errors)
40
- - [Type of the final payload](#type-of-the-final-payload)
41
- - [Async-Await and Promises](#async-await-and-promises)
42
- - [.then(fulfilled, rejected)](#thenfulfilled-rejected)
4
+ - [Introduction](#introduction)
5
+ - [Reply](#reply-object)
6
+ - [Properties](#properties)
7
+ - [.statusCode](#statuscode)
8
+ - [.sent](#sent)
9
+ - [.request](#request)
10
+ - [.log](#log)
11
+ - [.raw](#raw)
12
+ - [.server](#server)
13
+ - [.routeOptions](#routeoptions)
14
+ - [.mediaType](#mediatype)
15
+ - [.elapsedTime](#elapsedtime)
16
+ - [Methods](#methods)
17
+ - [.send(data)](#senddata)
18
+ - [Objects](#objects)
19
+ - [Strings](#strings)
20
+ - [Buffers](#buffers)
21
+ - [TypedArrays](#typedarrays)
22
+ - [Streams](#streams)
23
+ - [ReadableStream](#readablestream)
24
+ - [Response](#response)
25
+ - [Errors](#errors)
26
+ - [Async-Await and Promises](#async-await-and-promises)
27
+ - [Type of the final payload](#type-of-the-final-payload)
28
+ - [.code(statusCode)](#codestatuscode)
29
+ - [.header(key, value)](#headerkey-value)
30
+ - [.headers(object)](#headersobject)
31
+ - [.type(contentType)](#typecontenttype)
32
+ - [.redirect(dest, [code])](#redirectdest-code)
33
+ - [.getHeader(key)](#getheaderkey)
34
+ - [.getHeaders()](#getheaders)
35
+ - [.hasHeader(key)](#hasheaderkey)
36
+ - [.removeHeader(key)](#removeheaderkey)
37
+ - [.callNotFound()](#callnotfound)
38
+ - [.hijack()](#hijack)
39
+ - [.serializer(func)](#serializerfunc)
40
+ - [.serialize(payload)](#serializepayload)
41
+ - [.trailer(key, function)](#trailerkey-function)
42
+ - [.hasTrailer(key)](#hastrailerkey)
43
+ - [.removeTrailer(key)](#removetrailerkey)
44
+ - [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback)
45
+ - [.then(fulfilled, rejected)](#thenfulfilled-rejected)
46
+ - [.getDecorator(name)](#getdecoratorname)
47
+ - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus-contenttype)
48
+ - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus-contenttype)
49
+ - [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus-contenttype)
43
50
 
44
51
  ### Introduction
45
52
  <a id="introduction"></a>
@@ -66,7 +73,7 @@ since the request was received by Fastify.
66
73
  - `.hasTrailer(key)` - Determine if a trailer has been set.
67
74
  - `.removeTrailer(key)` - Remove the value of a previously set trailer.
68
75
  - `.type(value)` - Sets the header `Content-Type`.
69
- - `.redirect(dest, [code,])` - Redirect to the specified URL, the status code is
76
+ - `.redirect(dest, [code])` - Redirect to the specified URL, the status code is
70
77
  optional (defaults to `302`).
71
78
  - `.callNotFound()` - Invokes the custom not found handler.
72
79
  - `.serialize(payload)` - Serializes the specified payload using the default
@@ -95,6 +102,7 @@ since the request was received by Fastify.
95
102
  from Node core.
96
103
  - `.log` - The logger instance of the incoming request.
97
104
  - `.request` - The incoming request.
105
+ - `.getDecorator(name)` - Returns the value of a reply decorator.
98
106
 
99
107
  ```js
100
108
  fastify.get('/', options, function (request, reply) {
@@ -106,22 +114,13 @@ fastify.get('/', options, function (request, reply) {
106
114
  })
107
115
  ```
108
116
 
109
- ### .code(statusCode)
110
- <a id="code"></a>
111
-
112
- If not set via `reply.code`, the resulting `statusCode` will be `200`.
113
-
114
- ### .elapsedTime
115
- <a id="elapsedTime"></a>
116
-
117
- Invokes the custom response time getter to calculate the amount of time passed
118
- since the request was received by Fastify.
117
+ ### Reply
118
+ <a id="reply-object"></a>
119
119
 
120
- ```js
121
- const milliseconds = reply.elapsedTime
122
- ```
120
+ #### Properties
121
+ <a id="properties"></a>
123
122
 
124
- ### .statusCode
123
+ ##### .statusCode
125
124
  <a id="statusCode"></a>
126
125
 
127
126
  This property reads and sets the HTTP status code. It is an alias for
@@ -131,20 +130,69 @@ if (reply.statusCode >= 299) {
131
130
  reply.statusCode = 500
132
131
  }
133
132
  ```
134
- ### .mediaType
135
- <a id="mediatype"></a>
136
133
 
137
- Returns the media type extracted from `Content-Type` header. When `Content-Type`
138
- header is missing, it will return `undefined`.
134
+ ##### .sent
135
+ <a id="sent"></a>
136
+
137
+ As the name suggests, `.sent` is a property to indicate if a response has been
138
+ sent via `reply.send()`. It will also be `true` in case `reply.hijack()` was
139
+ used.
140
+
141
+ In case a route handler is defined as an async function or it returns a promise,
142
+ it is possible to call `reply.hijack()` to indicate that the automatic
143
+ invocation of `reply.send()` once the handler promise resolve should be skipped.
144
+ By calling `reply.hijack()`, an application claims full responsibility for the
145
+ low-level request and response. Moreover, hooks will not be invoked.
146
+
147
+ ##### .request
148
+ <a id="request"></a>
149
+
150
+ The [`Request`](./Request.md) object of the incoming request this reply belongs
151
+ to.
139
152
 
140
153
  ```js
141
- if (reply.mediaType === 'image/gif') {
142
- const versionBuffer = new Uint8Array(reply.payload, 3, 3)
143
- reply.header('x-gif-version', new TextDecoder().decode(versionBuffer))
144
- }
154
+ fastify.addHook('onSend', async function (request, reply, payload) {
155
+ reply.header('x-request-url', reply.request.url)
156
+ })
145
157
  ```
146
158
 
147
- ### .server
159
+ ##### .log
160
+ <a id="log"></a>
161
+
162
+ The logger instance of the incoming request. It is the same instance as
163
+ `request.log`, so every log line includes the request id. See
164
+ [Logging](./Logging.md) for more information.
165
+
166
+ ```js
167
+ fastify.get('/', function (request, reply) {
168
+ reply.log.info('sending the response')
169
+ reply.send({ hello: 'world' })
170
+ })
171
+ ```
172
+
173
+ ##### .raw
174
+ <a id="raw"></a>
175
+
176
+ This is the
177
+ [`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
178
+ from Node core. Whilst you are using the Fastify `Reply` object, the use of
179
+ `Reply.raw` functions is at your own risk as you are skipping all the Fastify
180
+ logic of handling the HTTP response. e.g.:
181
+
182
+ ```js
183
+ app.get('/cookie-2', (req, reply) => {
184
+ reply.setCookie('session', 'value', { secure: false }) // this will not be used
185
+
186
+ // in this case we are using only the nodejs http server response object
187
+ reply.raw.writeHead(200, { 'Content-Type': 'text/plain' })
188
+ reply.raw.write('ok')
189
+ reply.raw.end()
190
+ })
191
+ ```
192
+ Another example of the misuse of `Reply.raw` is explained in
193
+ [Reply](#getheaders).
194
+
195
+ ##### .server
148
196
  <a id="server"></a>
149
197
 
150
198
  The Fastify server instance, scoped to the current [encapsulation
@@ -160,486 +208,548 @@ fastify.get('/', async function (req, rep) {
160
208
  })
161
209
  ```
162
210
 
163
- ### .header(key, value)
164
- <a id="header"></a>
165
-
166
- Sets a response header. If the value is omitted or undefined, it is coerced to
167
- `''`.
168
-
169
- > ℹ️ Note:
170
- > The header's value must be properly encoded using
171
- > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
172
- > or similar modules such as
173
- > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters
174
- > will result in a 500 `TypeError` response.
175
-
176
- For more information, see
177
- [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_response_setheader_name_value).
178
-
179
- - ### set-cookie
180
- <a id="set-cookie"></a>
181
-
182
- - When sending different values as a cookie with `set-cookie` as the key,
183
- every value will be sent as a cookie instead of replacing the previous
184
- value.
185
-
186
- ```js
187
- reply.header('set-cookie', 'foo');
188
- reply.header('set-cookie', 'bar');
189
- ```
190
- - The browser will only consider the latest reference of a key for the
191
- `set-cookie` header. This is done to avoid parsing the `set-cookie` header
192
- when added to a reply and speeds up the serialization of the reply.
193
-
194
- - To reset the `set-cookie` header, you need to make an explicit call to
195
- `reply.removeHeader('set-cookie')`, read more about `.removeHeader(key)`
196
- [here](#removeheaderkey).
197
-
211
+ ##### .routeOptions
212
+ <a id="routeoptions"></a>
198
213
 
214
+ A read-only reference to the route options of the incoming request. It is the
215
+ same object as [`request.routeOptions`](./Request.md).
199
216
 
200
- ### .headers(object)
201
- <a id="headers"></a>
202
-
203
- Sets all the keys of the object as response headers.
204
- [`.header`](#headerkey-value) will be called under the hood.
205
217
  ```js
206
- reply.headers({
207
- 'x-foo': 'foo',
208
- 'x-bar': 'bar'
218
+ fastify.get('/', { config: { foo: 'bar' } }, function (request, reply) {
219
+ reply.send(reply.routeOptions.config.foo) // bar
209
220
  })
210
221
  ```
211
222
 
212
- ### .getHeader(key)
213
- <a id="getHeader"></a>
223
+ ##### .mediaType
224
+ <a id="mediatype"></a>
225
+
226
+ Returns the media type extracted from `Content-Type` header. When `Content-Type`
227
+ header is missing, it will return `undefined`.
214
228
 
215
- Retrieves the value of a previously set header.
216
229
  ```js
217
- reply.header('x-foo', 'foo') // setHeader: key, value
218
- reply.getHeader('x-foo') // 'foo'
230
+ if (reply.mediaType === 'image/gif') {
231
+ const versionBuffer = new Uint8Array(reply.payload, 3, 3)
232
+ reply.header('x-gif-version', new TextDecoder().decode(versionBuffer))
233
+ }
219
234
  ```
220
235
 
221
- ### .getHeaders()
222
- <a id="getHeaders"></a>
236
+ ##### .elapsedTime
237
+ <a id="elapsedTime"></a>
223
238
 
224
- Gets a shallow copy of all current response headers, including those set via the
225
- raw `http.ServerResponse`. Note that headers set via Fastify take precedence
226
- over those set via `http.ServerResponse`.
239
+ Invokes the custom response time getter to calculate the amount of time passed
240
+ since the request was received by Fastify.
227
241
 
228
242
  ```js
229
- reply.header('x-foo', 'foo')
230
- reply.header('x-bar', 'bar')
231
- reply.raw.setHeader('x-foo', 'foo2')
232
- reply.getHeaders() // { 'x-foo': 'foo', 'x-bar': 'bar' }
243
+ const milliseconds = reply.elapsedTime
233
244
  ```
234
245
 
235
- ### .removeHeader(key)
236
- <a id="removeHeader"></a>
246
+ #### Methods
247
+ <a id="methods"></a>
237
248
 
238
- Remove the value of a previously set header.
239
- ```js
240
- reply.header('x-foo', 'foo')
241
- reply.removeHeader('x-foo')
242
- reply.getHeader('x-foo') // undefined
243
- ```
249
+ ##### .send(data)
250
+ <a id="send"></a>
244
251
 
245
- ### .hasHeader(key)
246
- <a id="hasHeader"></a>
252
+ As the name suggests, `.send()` is the function that sends the payload to the
253
+ end user.
247
254
 
248
- Returns a boolean indicating if the specified header has been set.
255
+ ###### Objects
256
+ <a id="send-object"></a>
249
257
 
250
- ### .writeEarlyHints(hints, callback)
251
- <a id="writeEarlyHints"></a>
258
+ As noted above, if you are sending JSON objects, `send` will serialize the
259
+ object with
260
+ [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) if you
261
+ set an output schema, otherwise, `JSON.stringify()` will be used.
262
+ ```js
263
+ fastify.get('/json', options, function (request, reply) {
264
+ reply.send({ hello: 'world' })
265
+ })
266
+ ```
252
267
 
253
- Sends early hints to the client. Early hints allow the client to
254
- start processing resources before the final response is sent.
255
- This can improve performance by allowing the client to preload
256
- or preconnect to resources while the server is still generating the response.
268
+ ###### Strings
269
+ <a id="send-string"></a>
257
270
 
258
- The hints parameter is an object containing the early hint key-value pairs.
271
+ If you pass a string to `send` without a `Content-Type`, it will be sent as
272
+ `text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a
273
+ string to `send`, it will be serialized with the custom serializer if one is
274
+ set, otherwise, it will be sent unmodified.
259
275
 
260
- Example:
276
+ > ℹ️ Note:
277
+ > Even when the `Content-Type` header is set to `application/json`,
278
+ > strings are sent unmodified by default. To serialize a string as JSON, you
279
+ > must set a custom serializer:
261
280
 
262
281
  ```js
263
- reply.writeEarlyHints({
264
- Link: '</styles.css>; rel=preload; as=style'
265
- });
282
+ fastify.get('/json-string', async function (request, reply) {
283
+ reply
284
+ .type('application/json; charset=utf-8')
285
+ .serializer(JSON.stringify)
286
+ .send('Hello') // Returns "Hello" (JSON-encoded string)
287
+ })
288
+ ```
289
+ ```js
290
+ fastify.get('/json', options, function (request, reply) {
291
+ reply.send('plain string')
292
+ })
266
293
  ```
267
294
 
268
- The optional callback parameter is a function that will be called
269
- once the hint is sent or if an error occurs.
270
-
271
- ### .trailer(key, function)
272
- <a id="trailer"></a>
273
-
274
- Sets a response trailer. Trailer is usually used when you need a header that
275
- requires heavy resources to be sent after the `data`, for example,
276
- `Server-Timing` and `Etag`. It can ensure the client receives the response data
277
- as soon as possible.
295
+ ###### Buffers
296
+ <a id="send-buffers"></a>
278
297
 
279
- > ℹ️ Note:
280
- > For HTTP/1 responses, the header `Transfer-Encoding: chunked` will be added
281
- > once you use a trailer. HTTP/2 does not use chunked transfer encoding and
282
- > sends trailers using its native trailing headers support.
298
+ If you are sending a buffer and you have not set a `'Content-Type'` header,
299
+ *send* will set it to `'application/octet-stream'`.
283
300
 
284
- > ℹ️ Note:
285
- > Any error passed to `done` callback will be ignored. If you are interested
286
- > in the error, you can turn on `debug` level logging.
301
+ As noted above, Buffers are considered to be pre-serialized, so they will be
302
+ sent unmodified without response validation.
287
303
 
288
304
  ```js
289
- reply.trailer('server-timing', async function () {
290
- return 'db;dur=53, app;dur=47.2'
291
- })
305
+ const fs = require('node:fs')
292
306
 
293
- const { createHash } = require('node:crypto')
294
- // trailer function also receive two argument
295
- // @param {object} reply fastify reply
296
- // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
297
- // @param {function} done callback to set trailer value
298
- reply.trailer('content-md5', function(reply, payload, done) {
299
- const hash = createHash('md5')
300
- hash.update(payload)
301
- done(null, hash.digest('hex'))
307
+ fastify.get('/streams', function (request, reply) {
308
+ fs.readFile('some-file', (err, fileBuffer) => {
309
+ reply.send(err || fileBuffer)
310
+ })
302
311
  })
312
+ ```
303
313
 
304
- // when you prefer async-await
305
- reply.trailer('content-md5', async function(reply, payload) {
306
- const hash = createHash('md5')
307
- hash.update(payload)
308
- return hash.digest('hex')
314
+ When using async-await you will need to return or await the reply object:
315
+ ```js
316
+ const fs = require('node:fs')
317
+
318
+ fastify.get('/streams', async function (request, reply) {
319
+ fs.readFile('some-file', (err, fileBuffer) => {
320
+ reply.send(err || fileBuffer)
321
+ })
322
+ return reply
309
323
  })
310
324
  ```
311
325
 
312
- ### .hasTrailer(key)
313
- <a id="hasTrailer"></a>
326
+ ###### TypedArrays
327
+ <a id="send-typedarrays"></a>
314
328
 
315
- Returns a boolean indicating if the specified trailer has been set.
329
+ `send` manages TypedArray like a Buffer, and sets the `'Content-Type'`
330
+ header to `'application/octet-stream'` if not already set.
316
331
 
317
- ### .removeTrailer(key)
318
- <a id="removeTrailer"></a>
332
+ As noted above, TypedArray/Buffers are considered to be pre-serialized, so they
333
+ will be sent unmodified without response validation.
319
334
 
320
- Remove the value of a previously set trailer.
321
335
  ```js
322
- reply.trailer('server-timing', async function () {
323
- return 'db;dur=53, app;dur=47.2'
336
+ const fs = require('node:fs')
337
+
338
+ fastify.get('/streams', function (request, reply) {
339
+ const typedArray = new Uint16Array(10)
340
+ reply.send(typedArray)
324
341
  })
325
- reply.removeTrailer('server-timing')
326
- reply.hasTrailer('server-timing') // false
327
342
  ```
328
343
 
344
+ ###### Streams
345
+ <a id="send-streams"></a>
329
346
 
330
- ### .redirect(dest, [code ,])
331
- <a id="redirect"></a>
347
+ If you are sending a stream and you have not set a `'Content-Type'` header,
348
+ *send* will set it to `'application/octet-stream'`.
332
349
 
333
- Redirects a request to the specified URL, the status code is optional, default
334
- to `302` (if status code is not already set by calling `code`).
350
+ As noted above, streams are considered to be pre-serialized, so they will be
351
+ sent unmodified without response validation.
335
352
 
336
- > ℹ️ Note:
337
- > The input URL must be properly encoded using
338
- > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
339
- > or similar modules such as
340
- > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will
341
- > result in a 500 `TypeError` response.
353
+ When sending streams over HTTP/2, Fastify does not change the chunks emitted by
354
+ the stream. If a stream can emit very large chunks, split them in your
355
+ application code, for example by using `fs.createReadStream()` or a transform
356
+ stream that emits smaller chunks.
342
357
 
343
- Example (no `reply.code()` call) sets status code to `302` and redirects to
344
- `/home`
345
- ```js
346
- reply.redirect('/home')
347
- ```
358
+ See special note about error handling for streams in
359
+ [`setErrorHandler`](./Server.md#seterrorhandler).
348
360
 
349
- Example (no `reply.code()` call) sets status code to `303` and redirects to
350
- `/home`
351
361
  ```js
352
- reply.redirect('/home', 303)
353
- ```
362
+ const fs = require('node:fs')
354
363
 
355
- Example (`reply.code()` call) sets status code to `303` and redirects to `/home`
356
- ```js
357
- reply.code(303).redirect('/home')
364
+ fastify.get('/streams', function (request, reply) {
365
+ const stream = fs.createReadStream('some-file', 'utf8')
366
+ reply.header('Content-Type', 'application/octet-stream')
367
+ reply.send(stream)
368
+ })
358
369
  ```
359
-
360
- Example (`reply.code()` call) sets status code to `302` and redirects to `/home`
370
+ When using async-await you will need to return or await the reply object:
361
371
  ```js
362
- reply.code(303).redirect('/home', 302)
363
- ```
364
-
365
- ### .callNotFound()
366
- <a id="call-not-found"></a>
367
-
368
- Invokes the custom not found handler. Note that it will only call `preHandler`
369
- hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler).
372
+ const fs = require('node:fs')
370
373
 
371
- ```js
372
- reply.callNotFound()
374
+ fastify.get('/streams', async function (request, reply) {
375
+ const stream = fs.createReadStream('some-file', 'utf8')
376
+ reply.header('Content-Type', 'application/octet-stream')
377
+ return reply.send(stream)
378
+ })
373
379
  ```
374
380
 
375
- ### .type(contentType)
376
- <a id="type"></a>
381
+ ###### ReadableStream
382
+ <a id="send-readablestream"></a>
377
383
 
378
- Sets the content type for the response. This is a shortcut for
379
- `reply.header('Content-Type', 'the/type')`.
384
+ `ReadableStream` will be treated as a node stream mentioned above,
385
+ the content is considered to be pre-serialized, so they will be
386
+ sent unmodified without response validation.
380
387
 
381
388
  ```js
382
- reply.type('text/html')
389
+ const fs = require('node:fs')
390
+ const { ReadableStream } = require('node:stream/web')
391
+
392
+ fastify.get('/streams', function (request, reply) {
393
+ const stream = fs.createReadStream('some-file')
394
+ reply.header('Content-Type', 'application/octet-stream')
395
+ reply.send(ReadableStream.from(stream))
396
+ })
383
397
  ```
384
- If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
385
- `utf-8` will be used as the charset by default. For other content types, the
386
- charset must be set explicitly.
387
398
 
388
- ### .getSerializationFunction(schema | httpStatus, [contentType])
389
- <a id="getserializationfunction"></a>
399
+ ###### Response
400
+ <a id="send-response"></a>
390
401
 
391
- By calling this function using a provided `schema` or `httpStatus`,
392
- and the optional `contentType`, it will return a `serialization` function
393
- that can be used to serialize diverse inputs. It returns `undefined` if no
394
- serialization function was found using either of the provided inputs.
402
+ `Response` allows to manage the reply payload, status code and
403
+ headers in one place. The payload provided inside `Response` is
404
+ considered to be pre-serialized, so they will be sent unmodified
405
+ without response validation.
395
406
 
396
- This heavily depends of the `schema#responses` attached to the route, or
397
- the serialization functions compiled by using `compileSerializationSchema`.
407
+ Please be aware when using `Response`, the status code and headers
408
+ will not directly reflect to `reply.statusCode` and `reply.getHeaders()`.
409
+ Such behavior is based on `Response` only allow `readonly` status
410
+ code and headers. The data is not allow to be bi-direction editing,
411
+ and may confuse when checking the `payload` in `onSend` hooks.
398
412
 
399
413
  ```js
400
- const serialize = reply
401
- .getSerializationFunction({
402
- type: 'object',
403
- properties: {
404
- foo: {
405
- type: 'string'
406
- }
407
- }
408
- })
409
- serialize({ foo: 'bar' }) // '{"foo":"bar"}'
410
-
411
- // or
412
-
413
- const serialize = reply
414
- .getSerializationFunction(200)
415
- serialize({ foo: 'bar' }) // '{"foo":"bar"}'
416
-
417
- // or
414
+ const fs = require('node:fs')
415
+ const { ReadableStream } = require('node:stream/web')
418
416
 
419
- const serialize = reply
420
- .getSerializationFunction(200, 'application/json')
421
- serialize({ foo: 'bar' }) // '{"foo":"bar"}'
417
+ fastify.get('/streams', function (request, reply) {
418
+ const stream = fs.createReadStream('some-file')
419
+ const readableStream = ReadableStream.from(stream)
420
+ const response = new Response(readableStream, {
421
+ status: 200,
422
+ headers: { 'content-type': 'application/octet-stream' }
423
+ })
424
+ reply.send(response)
425
+ })
422
426
  ```
423
427
 
424
- See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
425
- for more information on how to compile serialization schemas.
426
-
427
- ### .compileSerializationSchema(schema, [httpStatus], [contentType])
428
- <a id="compileserializationschema"></a>
429
-
430
- This function will compile a serialization schema and
431
- return a function that can be used to serialize data.
432
- The function returned (a.k.a. _serialization function_) returned is compiled
433
- by using the provided `SerializerCompiler`. Also this is cached by using
434
- a `WeakMap` for reducing compilation calls.
435
428
 
436
- The optional parameters `httpStatus` and `contentType`, if provided,
437
- are forwarded directly to the `SerializerCompiler`, so it can be used
438
- to compile the serialization function if a custom `SerializerCompiler` is used.
429
+ ###### Errors
430
+ <a id="errors"></a>
439
431
 
440
- This heavily depends of the `schema#responses` attached to the route, or
441
- the serialization functions compiled by using `compileSerializationSchema`.
432
+ If you pass to *send* an object that is an instance of *Error*, Fastify will
433
+ automatically create an error structured as the following:
442
434
 
443
435
  ```js
444
- const serialize = reply
445
- .compileSerializationSchema({
446
- type: 'object',
447
- properties: {
448
- foo: {
449
- type: 'string'
450
- }
451
- }
452
- })
453
- serialize({ foo: 'bar' }) // '{"foo":"bar"}'
436
+ {
437
+ error: String // the HTTP error message
438
+ code: String // the Fastify error code
439
+ message: String // the user error message
440
+ statusCode: Number // the HTTP status code
441
+ }
442
+ ```
454
443
 
455
- // or
444
+ You can add custom properties to the Error object, such as `headers`, that will
445
+ be used to enhance the HTTP response.
456
446
 
457
- const serialize = reply
458
- .compileSerializationSchema({
459
- type: 'object',
460
- properties: {
461
- foo: {
462
- type: 'string'
463
- }
464
- }
465
- }, 200)
466
- serialize({ foo: 'bar' }) // '{"foo":"bar"}'
447
+ > ℹ️ Note:
448
+ > If you are passing an error to `send` and the statusCode is less than
449
+ > 400, Fastify will automatically set it at 500.
467
450
 
468
- // or
451
+ Tip: you can simplify errors by using the
452
+ [`http-errors`](https://www.npmjs.com/package/http-errors) module or
453
+ [`@fastify/sensible`](https://github.com/fastify/fastify-sensible) plugin to
454
+ generate errors:
469
455
 
470
- const serialize = reply
471
- .compileSerializationSchema({
472
- '3xx': {
473
- content: {
474
- 'application/json': {
475
- schema: {
476
- name: { type: 'string' },
477
- phone: { type: 'number' }
478
- }
479
- }
480
- }
481
- }
482
- }, '3xx', 'application/json')
483
- serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
456
+ ```js
457
+ fastify.get('/', function (request, reply) {
458
+ reply.send(httpErrors.Gone())
459
+ })
484
460
  ```
485
461
 
486
- Note that you should be careful when using this function, as it will cache
487
- the compiled serialization functions based on the schema provided. If the
488
- schemas provided is mutated or changed, the serialization functions will not
489
- detect that the schema has been altered and for instance it will reuse the
490
- previously compiled serialization function based on the reference of the schema
491
- previously provided.
462
+ To customize the JSON error output you can do it by:
492
463
 
493
- If there's a need to change the properties of a schema, always opt to create
494
- a totally new object, otherwise the implementation won't benefit from the cache
495
- mechanism.
464
+ - setting a response JSON schema for the status code you need
465
+ - add the additional properties to the `Error` instance
466
+
467
+ Notice that if the returned status code is not in the response schema list, the
468
+ default behavior will be applied.
496
469
 
497
- :Using the following schema as example:
498
470
  ```js
499
- const schema1 = {
500
- type: 'object',
501
- properties: {
502
- foo: {
503
- type: 'string'
471
+ fastify.get('/', {
472
+ schema: {
473
+ response: {
474
+ 501: {
475
+ type: 'object',
476
+ properties: {
477
+ statusCode: { type: 'number' },
478
+ code: { type: 'string' },
479
+ error: { type: 'string' },
480
+ message: { type: 'string' },
481
+ time: { type: 'string' }
482
+ }
483
+ }
504
484
  }
505
485
  }
506
- }
486
+ }, function (request, reply) {
487
+ const error = new Error('This endpoint has not been implemented')
488
+ error.time = 'it will be implemented in two weeks'
489
+ reply.code(501).send(error)
490
+ })
507
491
  ```
508
492
 
509
- *Not*
510
- ```js
511
- const serialize = reply.compileSerializationSchema(schema1)
493
+ If you want to customize error handling, check out
494
+ [`setErrorHandler`](./Server.md#seterrorhandler) API.
512
495
 
513
- // Later on...
514
- schema1.properties.foo.type = 'integer'
515
- const newSerialize = reply.compileSerializationSchema(schema1)
496
+ > ℹ️ Note:
497
+ > You are responsible for logging when customizing the error handler.
516
498
 
517
- console.log(newSerialize === serialize) // true
518
- ```
499
+ API:
519
500
 
520
- *Instead*
521
501
  ```js
522
- const serialize = reply.compileSerializationSchema(schema1)
502
+ fastify.setErrorHandler(function (error, request, reply) {
503
+ request.log.warn(error)
504
+ const statusCode = error.statusCode >= 400 ? error.statusCode : 500
505
+ reply
506
+ .code(statusCode)
507
+ .type('text/plain')
508
+ .send(statusCode >= 500 ? 'Internal server error' : error.message)
509
+ })
510
+ ```
523
511
 
524
- // Later on...
525
- const newSchema = Object.assign({}, schema1)
526
- newSchema.properties.foo.type = 'integer'
512
+ Beware that calling `reply.send(error)` in your custom error handler will send
513
+ the error to the default error handler.
514
+ Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle)
515
+ for more information.
527
516
 
528
- const newSerialize = reply.compileSerializationSchema(newSchema)
517
+ The not found errors generated by the router will use the
518
+ [`setNotFoundHandler`](./Server.md#setnotfoundhandler)
529
519
 
530
- console.log(newSerialize === serialize) // false
520
+ API:
521
+
522
+ ```js
523
+ fastify.setNotFoundHandler(function (request, reply) {
524
+ reply
525
+ .code(404)
526
+ .type('text/plain')
527
+ .send('a custom not found')
528
+ })
531
529
  ```
532
530
 
533
- ### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
534
- <a id="serializeinput"></a>
531
+ ###### Async-Await and Promises
532
+ <a id="async-await-promise"></a>
535
533
 
536
- This function will serialize the input data based on the provided schema
537
- or HTTP status code. If both are provided the `httpStatus` will take precedence.
534
+ Fastify natively handles promises and supports async-await.
538
535
 
539
- If there is not a serialization function for a given `schema` a new serialization
540
- function will be compiled, forwarding the `httpStatus` and `contentType` if provided.
536
+ *Note that in the following examples we are not using reply.send.*
537
+ ```js
538
+ const { promisify } = require('node:util')
539
+ const delay = promisify(setTimeout)
540
+
541
+ fastify.get('/promises', options, function (request, reply) {
542
+ return delay(200).then(() => { return { hello: 'world' }})
543
+ })
544
+
545
+ fastify.get('/async-await', options, async function (request, reply) {
546
+ await delay(200)
547
+ return { hello: 'world' }
548
+ })
549
+ ```
550
+
551
+ Rejected promises default to a `500` HTTP status code. Reject the promise, or
552
+ `throw` in an `async function`, with an object that has `statusCode` (or
553
+ `status`) and `message` properties to modify the reply.
541
554
 
542
555
  ```js
543
- reply
544
- .serializeInput({ foo: 'bar'}, {
545
- type: 'object',
546
- properties: {
547
- foo: {
548
- type: 'string'
549
- }
550
- }
551
- }) // '{"foo":"bar"}'
556
+ fastify.get('/teapot', async function (request, reply) {
557
+ const err = new Error()
558
+ err.statusCode = 418
559
+ err.message = 'short and stout'
560
+ throw err
561
+ })
552
562
 
553
- // or
563
+ fastify.get('/botnet', async function (request, reply) {
564
+ throw { statusCode: 418, message: 'short and stout' }
565
+ // will return to the client the same json
566
+ })
567
+ ```
554
568
 
555
- reply
556
- .serializeInput({ foo: 'bar'}, {
557
- type: 'object',
558
- properties: {
559
- foo: {
560
- type: 'string'
561
- }
562
- }
563
- }, 200) // '{"foo":"bar"}'
569
+ If you want to know more please review
570
+ [Routes#async-await](./Routes.md#async-await).
564
571
 
565
- // or
572
+ ###### Type of the final payload
573
+ <a id="payload-type"></a>
566
574
 
567
- reply
568
- .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
575
+ The type of the sent payload (after serialization and going through any
576
+ [`onSend` hooks](./Hooks.md#onsend)) must be one of the following types,
577
+ otherwise, an error will be thrown:
569
578
 
570
- // or
579
+ - `string`
580
+ - `Buffer`
581
+ - `stream`
582
+ - `undefined`
583
+ - `null`
571
584
 
572
- reply
573
- .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
585
+ ##### .code(statusCode)
586
+ <a id="code"></a>
587
+
588
+ If not set via `reply.code`, the resulting `statusCode` will be `200`.
589
+
590
+ `.status(statusCode)` is an alias of `.code(statusCode)`.
591
+
592
+ ##### .header(key, value)
593
+ <a id="header"></a>
594
+
595
+ Sets a response header. If the value is omitted or undefined, it is coerced to
596
+ `''`.
597
+
598
+ > ℹ️ Note:
599
+ > The header's value must be properly encoded using
600
+ > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
601
+ > or similar modules such as
602
+ > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters
603
+ > will result in a 500 `TypeError` response.
604
+
605
+ For more information, see
606
+ [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_response_setheader_name_value).
607
+
608
+ - ###### set-cookie
609
+ <a id="set-cookie"></a>
610
+
611
+ - When sending different values as a cookie with `set-cookie` as the key,
612
+ every value will be sent as a cookie instead of replacing the previous
613
+ value.
614
+
615
+ ```js
616
+ reply.header('set-cookie', 'foo');
617
+ reply.header('set-cookie', 'bar');
618
+ ```
619
+ - The browser will only consider the latest reference of a key for the
620
+ `set-cookie` header. This is done to avoid parsing the `set-cookie` header
621
+ when added to a reply and speeds up the serialization of the reply.
622
+
623
+ - To reset the `set-cookie` header, you need to make an explicit call to
624
+ `reply.removeHeader('set-cookie')`, read more about `.removeHeader(key)`
625
+ [here](#removeheaderkey).
626
+
627
+ ##### .headers(object)
628
+ <a id="headers"></a>
629
+
630
+ Sets all the keys of the object as response headers.
631
+ [`.header`](#headerkey-value) will be called under the hood.
632
+ ```js
633
+ reply.headers({
634
+ 'x-foo': 'foo',
635
+ 'x-bar': 'bar'
636
+ })
574
637
  ```
575
638
 
576
- See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
577
- for more information on how to compile serialization schemas.
639
+ ##### .type(contentType)
640
+ <a id="type"></a>
578
641
 
579
- ### .serializer(func)
580
- <a id="serializer"></a>
642
+ Sets the content type for the response. This is a shortcut for
643
+ `reply.header('Content-Type', 'the/type')`.
581
644
 
582
- By default, `.send()` will JSON-serialize any value that is not one of `Buffer`,
583
- `stream`, `string`, `undefined`, or `Error`. If you need to replace the default
584
- serializer with a custom serializer for a particular request, you can do so with
585
- the `.serializer()` utility. Be aware that if you are using a custom serializer,
586
- you must set a custom `'Content-Type'` header.
645
+ ```js
646
+ reply.type('text/html')
647
+ ```
648
+ If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
649
+ `utf-8` will be used as the charset by default. For other content types, the
650
+ charset must be set explicitly.
651
+
652
+ ##### .redirect(dest, [code])
653
+ <a id="redirect"></a>
654
+
655
+ Redirects a request to the specified URL, the status code is optional, default
656
+ to `302` (if status code is not already set by calling `code`).
657
+
658
+ > ℹ️ Note:
659
+ > The input URL must be properly encoded using
660
+ > [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
661
+ > or similar modules such as
662
+ > [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will
663
+ > result in a 500 `TypeError` response.
587
664
 
665
+ > ⚠️ Security:
666
+ > Encoding alone does **not** prevent open redirects. Neither `encodeURI`
667
+ > nor `encodeurl` escapes `/`, so a caller-supplied target such as
668
+ > `//evil.com` or `/\evil.com` survives encoding and is resolved by the
669
+ > browser as a new host (a protocol-relative, authority-opening
670
+ > reference). When `dest` can be influenced by request input
671
+ > (`req.query`, `req.body`, `req.params`, cookies, or headers), validate
672
+ > that the resolved target is same-origin or on an explicit allowlist
673
+ > instead of relying on encoding:
674
+ >
675
+ > ```js
676
+ > const target = new URL(dest, 'https://your-site.example')
677
+ > if (target.origin !== 'https://your-site.example') {
678
+ > return reply.code(400).send()
679
+ > }
680
+ > ```
681
+
682
+ Example (no `reply.code()` call) sets status code to `302` and redirects to
683
+ `/home`
588
684
  ```js
589
- reply
590
- .header('Content-Type', 'application/x-protobuf')
591
- .serializer(protoBuf.serialize)
685
+ reply.redirect('/home')
592
686
  ```
593
687
 
594
- Note that you don't need to use this utility inside a `handler` because Buffers,
595
- streams, and strings (unless a serializer is set) are considered to already be
596
- serialized.
688
+ Example (no `reply.code()` call) sets status code to `303` and redirects to
689
+ `/home`
690
+ ```js
691
+ reply.redirect('/home', 303)
692
+ ```
597
693
 
694
+ Example (`reply.code()` call) sets status code to `303` and redirects to `/home`
598
695
  ```js
599
- reply
600
- .header('Content-Type', 'application/x-protobuf')
601
- .send(protoBuf.serialize(data))
696
+ reply.code(303).redirect('/home')
602
697
  ```
603
698
 
604
- See [`.send()`](#send) for more information on sending different types of
605
- values.
699
+ Example (`reply.code()` call) sets status code to `302` and redirects to `/home`
700
+ ```js
701
+ reply.code(303).redirect('/home', 302)
702
+ ```
606
703
 
607
- ### .raw
608
- <a id="raw"></a>
704
+ ##### .getHeader(key)
705
+ <a id="getHeader"></a>
609
706
 
610
- This is the
611
- [`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
612
- from Node core. Whilst you are using the Fastify `Reply` object, the use of
613
- `Reply.raw` functions is at your own risk as you are skipping all the Fastify
614
- logic of handling the HTTP response. e.g.:
707
+ Retrieves the value of a previously set header.
708
+ ```js
709
+ reply.header('x-foo', 'foo') // setHeader: key, value
710
+ reply.getHeader('x-foo') // 'foo'
711
+ ```
712
+
713
+ ##### .getHeaders()
714
+ <a id="getHeaders"></a>
715
+
716
+ Gets a shallow copy of all current response headers, including those set via the
717
+ raw `http.ServerResponse`. Note that headers set via Fastify take precedence
718
+ over those set via `http.ServerResponse`.
615
719
 
616
720
  ```js
617
- app.get('/cookie-2', (req, reply) => {
618
- reply.setCookie('session', 'value', { secure: false }) // this will not be used
721
+ reply.header('x-foo', 'foo')
722
+ reply.header('x-bar', 'bar')
723
+ reply.raw.setHeader('x-foo', 'foo2')
724
+ reply.getHeaders() // { 'x-foo': 'foo', 'x-bar': 'bar' }
725
+ ```
619
726
 
620
- // in this case we are using only the nodejs http server response object
621
- reply.raw.writeHead(200, { 'Content-Type': 'text/plain' })
622
- reply.raw.write('ok')
623
- reply.raw.end()
624
- })
727
+ ##### .hasHeader(key)
728
+ <a id="hasHeader"></a>
729
+
730
+ Returns a boolean indicating if the specified header has been set.
731
+
732
+ ##### .removeHeader(key)
733
+ <a id="removeHeader"></a>
734
+
735
+ Remove the value of a previously set header.
736
+ ```js
737
+ reply.header('x-foo', 'foo')
738
+ reply.removeHeader('x-foo')
739
+ reply.getHeader('x-foo') // undefined
625
740
  ```
626
- Another example of the misuse of `Reply.raw` is explained in
627
- [Reply](#getheaders).
628
741
 
629
- ### .sent
630
- <a id="sent"></a>
742
+ ##### .callNotFound()
743
+ <a id="call-not-found"></a>
631
744
 
632
- As the name suggests, `.sent` is a property to indicate if a response has been
633
- sent via `reply.send()`. It will also be `true` in case `reply.hijack()` was
634
- used.
745
+ Invokes the custom not found handler. Note that it will only call `preHandler`
746
+ hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler).
635
747
 
636
- In case a route handler is defined as an async function or it returns a promise,
637
- it is possible to call `reply.hijack()` to indicate that the automatic
638
- invocation of `reply.send()` once the handler promise resolve should be skipped.
639
- By calling `reply.hijack()`, an application claims full responsibility for the
640
- low-level request and response. Moreover, hooks will not be invoked.
748
+ ```js
749
+ reply.callNotFound()
750
+ ```
641
751
 
642
- ### .hijack()
752
+ ##### .hijack()
643
753
  <a name="hijack"></a>
644
754
 
645
755
  Sometimes you might need to halt the execution of the normal request lifecycle
@@ -662,357 +772,353 @@ app.get('/', (req, reply) => {
662
772
  If `reply.raw` is used to send a response back to the user, the `onResponse`
663
773
  hooks will still be executed.
664
774
 
665
- ### .send(data)
666
- <a id="send"></a>
667
-
668
- As the name suggests, `.send()` is the function that sends the payload to the
669
- end user.
775
+ ##### .serializer(func)
776
+ <a id="serializer"></a>
670
777
 
671
- #### Objects
672
- <a id="send-object"></a>
778
+ By default, `.send()` will JSON-serialize any value that is not one of `Buffer`,
779
+ `stream`, `string`, `undefined`, or `Error`. If you need to replace the default
780
+ serializer with a custom serializer for a particular request, you can do so with
781
+ the `.serializer()` utility. Be aware that if you are using a custom serializer,
782
+ you must set a custom `'Content-Type'` header.
673
783
 
674
- As noted above, if you are sending JSON objects, `send` will serialize the
675
- object with
676
- [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) if you
677
- set an output schema, otherwise, `JSON.stringify()` will be used.
678
784
  ```js
679
- fastify.get('/json', options, function (request, reply) {
680
- reply.send({ hello: 'world' })
681
- })
785
+ reply
786
+ .header('Content-Type', 'application/x-protobuf')
787
+ .serializer(protoBuf.serialize)
682
788
  ```
683
789
 
684
- #### Strings
685
- <a id="send-string"></a>
686
-
687
- If you pass a string to `send` without a `Content-Type`, it will be sent as
688
- `text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a
689
- string to `send`, it will be serialized with the custom serializer if one is
690
- set, otherwise, it will be sent unmodified.
691
-
692
- > ℹ️ Note:
693
- > Even when the `Content-Type` header is set to `application/json`,
694
- > strings are sent unmodified by default. To serialize a string as JSON, you
695
- > must set a custom serializer:
790
+ Note that you don't need to use this utility inside a `handler` because Buffers,
791
+ streams, and strings (unless a serializer is set) are considered to already be
792
+ serialized.
696
793
 
697
794
  ```js
698
- fastify.get('/json-string', async function (request, reply) {
699
- reply
700
- .type('application/json; charset=utf-8')
701
- .serializer(JSON.stringify)
702
- .send('Hello') // Returns "Hello" (JSON-encoded string)
703
- })
795
+ reply
796
+ .header('Content-Type', 'application/x-protobuf')
797
+ .send(protoBuf.serialize(data))
704
798
  ```
799
+
800
+ See [`.send()`](#send) for more information on sending different types of
801
+ values.
802
+
803
+ ##### .serialize(payload)
804
+ <a id="serialize"></a>
805
+
806
+ Serializes the specified payload using the custom serializer set via
807
+ [`.serializer()`](#serializerfunc), if any. Otherwise, it uses the default
808
+ serializer of the route, which takes into account the response schema for the
809
+ current status code. It returns the serialized payload.
810
+
705
811
  ```js
706
- fastify.get('/json', options, function (request, reply) {
707
- reply.send('plain string')
812
+ fastify.get('/', function (request, reply) {
813
+ const payload = reply.serialize({ hello: 'world' })
814
+ reply.type('application/json').send(payload)
708
815
  })
709
816
  ```
710
817
 
711
- #### Streams
712
- <a id="send-streams"></a>
713
-
714
- If you are sending a stream and you have not set a `'Content-Type'` header,
715
- *send* will set it to `'application/octet-stream'`.
818
+ ##### .trailer(key, function)
819
+ <a id="trailer"></a>
716
820
 
717
- As noted above, streams are considered to be pre-serialized, so they will be
718
- sent unmodified without response validation.
821
+ Sets a response trailer. Trailer is usually used when you need a header that
822
+ requires heavy resources to be sent after the `data`, for example,
823
+ `Server-Timing` and `Etag`. It can ensure the client receives the response data
824
+ as soon as possible.
719
825
 
720
- When sending streams over HTTP/2, Fastify does not change the chunks emitted by
721
- the stream. If a stream can emit very large chunks, split them in your
722
- application code, for example by using `fs.createReadStream()` or a transform
723
- stream that emits smaller chunks.
826
+ > ℹ️ Note:
827
+ > For HTTP/1 responses, the header `Transfer-Encoding: chunked` will be added
828
+ > once you use a trailer. HTTP/2 does not use chunked transfer encoding and
829
+ > sends trailers using its native trailing headers support.
724
830
 
725
- See special note about error handling for streams in
726
- [`setErrorHandler`](./Server.md#seterrorhandler).
831
+ > ℹ️ Note:
832
+ > Any error passed to `done` callback will be ignored. If you are interested
833
+ > in the error, you can turn on `debug` level logging.
727
834
 
728
835
  ```js
729
- const fs = require('node:fs')
836
+ reply.trailer('server-timing', async function () {
837
+ return 'db;dur=53, app;dur=47.2'
838
+ })
730
839
 
731
- fastify.get('/streams', function (request, reply) {
732
- const stream = fs.createReadStream('some-file', 'utf8')
733
- reply.header('Content-Type', 'application/octet-stream')
734
- reply.send(stream)
840
+ const { createHash } = require('node:crypto')
841
+ // trailer function also receive two argument
842
+ // @param {object} reply fastify reply
843
+ // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
844
+ // @param {function} done callback to set trailer value
845
+ reply.trailer('content-md5', function(reply, payload, done) {
846
+ const hash = createHash('md5')
847
+ hash.update(payload)
848
+ done(null, hash.digest('hex'))
735
849
  })
736
- ```
737
- When using async-await you will need to return or await the reply object:
738
- ```js
739
- const fs = require('node:fs')
740
850
 
741
- fastify.get('/streams', async function (request, reply) {
742
- const stream = fs.createReadStream('some-file', 'utf8')
743
- reply.header('Content-Type', 'application/octet-stream')
744
- return reply.send(stream)
851
+ // when you prefer async-await
852
+ reply.trailer('content-md5', async function(reply, payload) {
853
+ const hash = createHash('md5')
854
+ hash.update(payload)
855
+ return hash.digest('hex')
745
856
  })
746
857
  ```
747
858
 
748
- #### Buffers
749
- <a id="send-buffers"></a>
859
+ ##### .hasTrailer(key)
860
+ <a id="hasTrailer"></a>
750
861
 
751
- If you are sending a buffer and you have not set a `'Content-Type'` header,
752
- *send* will set it to `'application/octet-stream'`.
862
+ Returns a boolean indicating if the specified trailer has been set.
753
863
 
754
- As noted above, Buffers are considered to be pre-serialized, so they will be
755
- sent unmodified without response validation.
864
+ ##### .removeTrailer(key)
865
+ <a id="removeTrailer"></a>
756
866
 
867
+ Remove the value of a previously set trailer.
757
868
  ```js
758
- const fs = require('node:fs')
759
-
760
- fastify.get('/streams', function (request, reply) {
761
- fs.readFile('some-file', (err, fileBuffer) => {
762
- reply.send(err || fileBuffer)
763
- })
869
+ reply.trailer('server-timing', async function () {
870
+ return 'db;dur=53, app;dur=47.2'
764
871
  })
872
+ reply.removeTrailer('server-timing')
873
+ reply.hasTrailer('server-timing') // false
765
874
  ```
766
875
 
767
- When using async-await you will need to return or await the reply object:
768
- ```js
769
- const fs = require('node:fs')
770
-
771
- fastify.get('/streams', async function (request, reply) {
772
- fs.readFile('some-file', (err, fileBuffer) => {
773
- reply.send(err || fileBuffer)
774
- })
775
- return reply
776
- })
777
- ```
876
+ ##### .writeEarlyHints(hints, callback)
877
+ <a id="writeEarlyHints"></a>
778
878
 
779
- #### TypedArrays
780
- <a id="send-typedarrays"></a>
879
+ Sends early hints to the client. Early hints allow the client to
880
+ start processing resources before the final response is sent.
881
+ This can improve performance by allowing the client to preload
882
+ or preconnect to resources while the server is still generating the response.
781
883
 
782
- `send` manages TypedArray like a Buffer, and sets the `'Content-Type'`
783
- header to `'application/octet-stream'` if not already set.
884
+ The hints parameter is an object containing the early hint key-value pairs.
784
885
 
785
- As noted above, TypedArray/Buffers are considered to be pre-serialized, so they
786
- will be sent unmodified without response validation.
886
+ Example:
787
887
 
788
888
  ```js
789
- const fs = require('node:fs')
790
-
791
- fastify.get('/streams', function (request, reply) {
792
- const typedArray = new Uint16Array(10)
793
- reply.send(typedArray)
794
- })
889
+ reply.writeEarlyHints({
890
+ Link: '</styles.css>; rel=preload; as=style'
891
+ });
795
892
  ```
796
893
 
797
- #### ReadableStream
798
- <a id="send-readablestream"></a>
894
+ The optional callback parameter is a function that will be called
895
+ once the hint is sent or if an error occurs.
799
896
 
800
- `ReadableStream` will be treated as a node stream mentioned above,
801
- the content is considered to be pre-serialized, so they will be
802
- sent unmodified without response validation.
897
+ ##### .then(fulfilled, rejected)
898
+ <a id="then"></a>
803
899
 
804
- ```js
805
- const fs = require('node:fs')
806
- const { ReadableStream } = require('node:stream/web')
900
+ As the name suggests, a `Reply` object can be awaited upon, i.e. `await reply`
901
+ will wait until the reply is sent. The `await` syntax calls the `reply.then()`.
807
902
 
808
- fastify.get('/streams', function (request, reply) {
809
- const stream = fs.createReadStream('some-file')
810
- reply.header('Content-Type', 'application/octet-stream')
811
- reply.send(ReadableStream.from(stream))
812
- })
813
- ```
903
+ `reply.then(fulfilled, rejected)` accepts two parameters:
814
904
 
815
- #### Response
816
- <a id="send-response"></a>
905
+ - `fulfilled` will be called when a response has been fully sent,
906
+ - `rejected` will be called if the underlying stream had an error, e.g. the
907
+ socket has been destroyed.
817
908
 
818
- `Response` allows to manage the reply payload, status code and
819
- headers in one place. The payload provided inside `Response` is
820
- considered to be pre-serialized, so they will be sent unmodified
821
- without response validation.
909
+ For more details, see:
822
910
 
823
- Please be aware when using `Response`, the status code and headers
824
- will not directly reflect to `reply.statusCode` and `reply.getHeaders()`.
825
- Such behavior is based on `Response` only allow `readonly` status
826
- code and headers. The data is not allow to be bi-direction editing,
827
- and may confuse when checking the `payload` in `onSend` hooks.
911
+ - https://github.com/fastify/fastify/issues/1864 for the discussion about this
912
+ feature
913
+ - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
914
+ for the signature
915
+
916
+ ##### .getDecorator(name)
917
+
918
+ Returns the value of a reply decorator declared with
919
+ [`decorateReply`](./Decorators.md#decoratereplyname-value-dependencies). If the
920
+ decorator is a function, it is returned bound to the reply. It throws an
921
+ `FST_ERR_DEC_UNDECLARED` error if the decorator has not been declared. See
922
+ [`getDecorator`](./Decorators.md#getdecoratorname) for more details.
828
923
 
829
924
  ```js
830
- const fs = require('node:fs')
831
- const { ReadableStream } = require('node:stream/web')
925
+ fastify.decorateReply('helper', function () {
926
+ return this.statusCode
927
+ })
832
928
 
833
- fastify.get('/streams', function (request, reply) {
834
- const stream = fs.createReadStream('some-file')
835
- const readableStream = ReadableStream.from(stream)
836
- const response = new Response(readableStream, {
837
- status: 200,
838
- headers: { 'content-type': 'application/octet-stream' }
839
- })
840
- reply.send(response)
929
+ fastify.get('/', function (request, reply) {
930
+ const helper = reply.getDecorator('helper')
931
+ reply.code(201).send({ statusCode: helper() }) // { statusCode: 201 }
841
932
  })
842
933
  ```
843
934
 
935
+ ##### .getSerializationFunction(schema | httpStatus, [contentType])
936
+ <a id="getserializationfunction"></a>
844
937
 
845
- #### Errors
846
- <a id="errors"></a>
938
+ By calling this function using a provided `schema` or `httpStatus`,
939
+ and the optional `contentType`, it will return a `serialization` function
940
+ that can be used to serialize diverse inputs. It returns `undefined` if no
941
+ serialization function was found using either of the provided inputs.
847
942
 
848
- If you pass to *send* an object that is an instance of *Error*, Fastify will
849
- automatically create an error structured as the following:
943
+ This heavily depends of the `schema#responses` attached to the route, or
944
+ the serialization functions compiled by using `compileSerializationSchema`.
850
945
 
851
946
  ```js
852
- {
853
- error: String // the HTTP error message
854
- code: String // the Fastify error code
855
- message: String // the user error message
856
- statusCode: Number // the HTTP status code
857
- }
858
- ```
947
+ const serialize = reply
948
+ .getSerializationFunction({
949
+ type: 'object',
950
+ properties: {
951
+ foo: {
952
+ type: 'string'
953
+ }
954
+ }
955
+ })
956
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
859
957
 
860
- You can add custom properties to the Error object, such as `headers`, that will
861
- be used to enhance the HTTP response.
958
+ // or
862
959
 
863
- > ℹ️ Note:
864
- > If you are passing an error to `send` and the statusCode is less than
865
- > 400, Fastify will automatically set it at 500.
960
+ const serialize = reply
961
+ .getSerializationFunction(200)
962
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
866
963
 
867
- Tip: you can simplify errors by using the
868
- [`http-errors`](https://www.npmjs.com/package/http-errors) module or
869
- [`@fastify/sensible`](https://github.com/fastify/fastify-sensible) plugin to
870
- generate errors:
964
+ // or
871
965
 
872
- ```js
873
- fastify.get('/', function (request, reply) {
874
- reply.send(httpErrors.Gone())
875
- })
966
+ const serialize = reply
967
+ .getSerializationFunction(200, 'application/json')
968
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
876
969
  ```
877
970
 
878
- To customize the JSON error output you can do it by:
971
+ See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
972
+ for more information on how to compile serialization schemas.
879
973
 
880
- - setting a response JSON schema for the status code you need
881
- - add the additional properties to the `Error` instance
974
+ ##### .compileSerializationSchema(schema, [httpStatus], [contentType])
975
+ <a id="compileserializationschema"></a>
882
976
 
883
- Notice that if the returned status code is not in the response schema list, the
884
- default behavior will be applied.
977
+ This function will compile a serialization schema and
978
+ return a function that can be used to serialize data.
979
+ The function returned (a.k.a. _serialization function_) returned is compiled
980
+ by using the provided `SerializerCompiler`. Also this is cached by using
981
+ a `WeakMap` for reducing compilation calls.
982
+
983
+ The optional parameters `httpStatus` and `contentType`, if provided,
984
+ are forwarded directly to the `SerializerCompiler`, so it can be used
985
+ to compile the serialization function if a custom `SerializerCompiler` is used.
986
+
987
+ This heavily depends of the `schema#responses` attached to the route, or
988
+ the serialization functions compiled by using `compileSerializationSchema`.
885
989
 
886
990
  ```js
887
- fastify.get('/', {
888
- schema: {
889
- response: {
890
- 501: {
891
- type: 'object',
892
- properties: {
893
- statusCode: { type: 'number' },
894
- code: { type: 'string' },
895
- error: { type: 'string' },
896
- message: { type: 'string' },
897
- time: { type: 'string' }
898
- }
899
- }
900
- }
901
- }
902
- }, function (request, reply) {
903
- const error = new Error('This endpoint has not been implemented')
904
- error.time = 'it will be implemented in two weeks'
905
- reply.code(501).send(error)
906
- })
907
- ```
991
+ const serialize = reply
992
+ .compileSerializationSchema({
993
+ type: 'object',
994
+ properties: {
995
+ foo: {
996
+ type: 'string'
997
+ }
998
+ }
999
+ })
1000
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
908
1001
 
909
- If you want to customize error handling, check out
910
- [`setErrorHandler`](./Server.md#seterrorhandler) API.
1002
+ // or
911
1003
 
912
- > ℹ️ Note:
913
- > You are responsible for logging when customizing the error handler.
1004
+ const serialize = reply
1005
+ .compileSerializationSchema({
1006
+ type: 'object',
1007
+ properties: {
1008
+ foo: {
1009
+ type: 'string'
1010
+ }
1011
+ }
1012
+ }, 200)
1013
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
914
1014
 
915
- API:
1015
+ // or
916
1016
 
917
- ```js
918
- fastify.setErrorHandler(function (error, request, reply) {
919
- request.log.warn(error)
920
- const statusCode = error.statusCode >= 400 ? error.statusCode : 500
921
- reply
922
- .code(statusCode)
923
- .type('text/plain')
924
- .send(statusCode >= 500 ? 'Internal server error' : error.message)
925
- })
1017
+ const serialize = reply
1018
+ .compileSerializationSchema({
1019
+ '3xx': {
1020
+ content: {
1021
+ 'application/json': {
1022
+ schema: {
1023
+ name: { type: 'string' },
1024
+ phone: { type: 'number' }
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+ }, '3xx', 'application/json')
1030
+ serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
926
1031
  ```
927
1032
 
928
- Beware that calling `reply.send(error)` in your custom error handler will send
929
- the error to the default error handler.
930
- Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle)
931
- for more information.
932
-
933
- The not found errors generated by the router will use the
934
- [`setNotFoundHandler`](./Server.md#setnotfoundhandler)
1033
+ Note that you should be careful when using this function, as it will cache
1034
+ the compiled serialization functions based on the schema provided. If the
1035
+ schemas provided is mutated or changed, the serialization functions will not
1036
+ detect that the schema has been altered and for instance it will reuse the
1037
+ previously compiled serialization function based on the reference of the schema
1038
+ previously provided.
935
1039
 
936
- API:
1040
+ If there's a need to change the properties of a schema, always opt to create
1041
+ a totally new object, otherwise the implementation won't benefit from the cache
1042
+ mechanism.
937
1043
 
1044
+ :Using the following schema as example:
938
1045
  ```js
939
- fastify.setNotFoundHandler(function (request, reply) {
940
- reply
941
- .code(404)
942
- .type('text/plain')
943
- .send('a custom not found')
944
- })
1046
+ const schema1 = {
1047
+ type: 'object',
1048
+ properties: {
1049
+ foo: {
1050
+ type: 'string'
1051
+ }
1052
+ }
1053
+ }
945
1054
  ```
946
1055
 
947
- #### Type of the final payload
948
- <a id="payload-type"></a>
949
-
950
- The type of the sent payload (after serialization and going through any
951
- [`onSend` hooks](./Hooks.md#onsend)) must be one of the following types,
952
- otherwise, an error will be thrown:
953
-
954
- - `string`
955
- - `Buffer`
956
- - `stream`
957
- - `undefined`
958
- - `null`
1056
+ *Not*
1057
+ ```js
1058
+ const serialize = reply.compileSerializationSchema(schema1)
959
1059
 
960
- #### Async-Await and Promises
961
- <a id="async-await-promise"></a>
1060
+ // Later on...
1061
+ schema1.properties.foo.type = 'integer'
1062
+ const newSerialize = reply.compileSerializationSchema(schema1)
962
1063
 
963
- Fastify natively handles promises and supports async-await.
1064
+ console.log(newSerialize === serialize) // true
1065
+ ```
964
1066
 
965
- *Note that in the following examples we are not using reply.send.*
1067
+ *Instead*
966
1068
  ```js
967
- const { promisify } = require('node:util')
968
- const delay = promisify(setTimeout)
1069
+ const serialize = reply.compileSerializationSchema(schema1)
969
1070
 
970
- fastify.get('/promises', options, function (request, reply) {
971
- return delay(200).then(() => { return { hello: 'world' }})
972
- })
1071
+ // Later on...
1072
+ const newSchema = Object.assign({}, schema1)
1073
+ newSchema.properties.foo.type = 'integer'
973
1074
 
974
- fastify.get('/async-await', options, async function (request, reply) {
975
- await delay(200)
976
- return { hello: 'world' }
977
- })
1075
+ const newSerialize = reply.compileSerializationSchema(newSchema)
1076
+
1077
+ console.log(newSerialize === serialize) // false
978
1078
  ```
979
1079
 
980
- Rejected promises default to a `500` HTTP status code. Reject the promise, or
981
- `throw` in an `async function`, with an object that has `statusCode` (or
982
- `status`) and `message` properties to modify the reply.
1080
+ ##### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
1081
+ <a id="serializeinput"></a>
983
1082
 
984
- ```js
985
- fastify.get('/teapot', async function (request, reply) {
986
- const err = new Error()
987
- err.statusCode = 418
988
- err.message = 'short and stout'
989
- throw err
990
- })
1083
+ This function will serialize the input data based on the provided schema
1084
+ or HTTP status code. If both are provided the `httpStatus` will take precedence.
991
1085
 
992
- fastify.get('/botnet', async function (request, reply) {
993
- throw { statusCode: 418, message: 'short and stout' }
994
- // will return to the client the same json
995
- })
996
- ```
1086
+ If there is not a serialization function for a given `schema` a new serialization
1087
+ function will be compiled, forwarding the `httpStatus` and `contentType` if provided.
997
1088
 
998
- If you want to know more please review
999
- [Routes#async-await](./Routes.md#async-await).
1089
+ ```js
1090
+ reply
1091
+ .serializeInput({ foo: 'bar'}, {
1092
+ type: 'object',
1093
+ properties: {
1094
+ foo: {
1095
+ type: 'string'
1096
+ }
1097
+ }
1098
+ }) // '{"foo":"bar"}'
1000
1099
 
1001
- ### .then(fulfilled, rejected)
1002
- <a id="then"></a>
1100
+ // or
1003
1101
 
1004
- As the name suggests, a `Reply` object can be awaited upon, i.e. `await reply`
1005
- will wait until the reply is sent. The `await` syntax calls the `reply.then()`.
1102
+ reply
1103
+ .serializeInput({ foo: 'bar'}, {
1104
+ type: 'object',
1105
+ properties: {
1106
+ foo: {
1107
+ type: 'string'
1108
+ }
1109
+ }
1110
+ }, 200) // '{"foo":"bar"}'
1006
1111
 
1007
- `reply.then(fulfilled, rejected)` accepts two parameters:
1112
+ // or
1008
1113
 
1009
- - `fulfilled` will be called when a response has been fully sent,
1010
- - `rejected` will be called if the underlying stream had an error, e.g. the
1011
- socket has been destroyed.
1114
+ reply
1115
+ .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
1012
1116
 
1013
- For more details, see:
1117
+ // or
1014
1118
 
1015
- - https://github.com/fastify/fastify/issues/1864 for the discussion about this
1016
- feature
1017
- - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
1018
- for the signature
1119
+ reply
1120
+ .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
1121
+ ```
1122
+
1123
+ See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
1124
+ for more information on how to compile serialization schemas.