fastify 4.18.0 → 4.19.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/docs/Guides/Ecosystem.md +5 -0
- package/docs/Guides/Serverless.md +43 -1
- package/docs/Guides/Testing.md +135 -2
- package/docs/Guides/Write-Plugin.md +3 -1
- package/docs/Reference/Principles.md +78 -0
- package/docs/Reference/Request.md +2 -0
- package/docs/Reference/Routes.md +5 -0
- package/docs/Reference/Server.md +5 -2
- package/docs/Reference/Type-Providers.md +9 -2
- package/docs/Reference/TypeScript.md +21 -6
- package/fastify.d.ts +1 -1
- package/fastify.js +23 -8
- package/lib/contentTypeParser.js +1 -1
- package/lib/reply.js +2 -2
- package/lib/request.js +18 -8
- package/lib/route.js +0 -1
- package/lib/schema-controller.js +6 -6
- package/lib/schemas.js +2 -2
- package/lib/server.js +56 -30
- package/lib/symbols.js +1 -0
- package/package.json +9 -8
- package/test/buffer.test.js +51 -0
- package/test/close-pipelining.test.js +4 -1
- package/test/close.test.js +148 -6
- package/test/custom-http-server.test.js +13 -2
- package/test/genReqId.test.js +42 -0
- package/test/header-overflow.test.js +65 -0
- package/test/https/custom-https-server.test.js +2 -2
- package/test/inject.test.js +30 -0
- package/test/internals/reply.test.js +2 -2
- package/test/internals/request.test.js +60 -0
- package/test/router-options.test.js +221 -0
- package/test/types/fastify.test-d.ts +7 -1
- package/test/types/instance.test-d.ts +70 -0
- package/test/types/reply.test-d.ts +37 -4
- package/test/types/request.test-d.ts +1 -0
- package/test/types/route.test-d.ts +79 -3
- package/test/url-rewriting.test.js +35 -0
- package/test/versioned-routes.test.js +1 -1
- package/types/context.d.ts +2 -1
- package/types/instance.d.ts +24 -20
- package/types/reply.d.ts +11 -6
- package/types/request.d.ts +1 -0
- package/types/route.d.ts +5 -0
- package/types/utils.d.ts +24 -0
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -248,6 +248,8 @@ section.
|
|
|
248
248
|
development servers that require Babel transformations of JavaScript sources.
|
|
249
249
|
- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
|
|
250
250
|
generator & checker.
|
|
251
|
+
- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3)
|
|
252
|
+
Plugin for better-sqlite3.
|
|
251
253
|
- [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your
|
|
252
254
|
routes to the console, so you definitely know which endpoints are available.
|
|
253
255
|
- [`fastify-bookshelf`](https://github.com/butlerx/fastify-bookshelfjs) Fastify
|
|
@@ -258,6 +260,9 @@ section.
|
|
|
258
260
|
to add [bree](https://github.com/breejs/bree) support.
|
|
259
261
|
- [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin
|
|
260
262
|
to add support for [Bugsnag](https://www.bugsnag.com/) error reporting.
|
|
263
|
+
- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman)
|
|
264
|
+
Small and efficient cache provider for Node.JS with In-memory, File, Redis
|
|
265
|
+
and MongoDB engines for Fastify
|
|
261
266
|
- [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support
|
|
262
267
|
for Fastify.
|
|
263
268
|
- [`fastify-casbin-rest`](https://github.com/nearform/fastify-casbin-rest)
|
|
@@ -28,6 +28,7 @@ snippet of code.
|
|
|
28
28
|
- [Google Cloud Functions](#google-cloud-functions)
|
|
29
29
|
- [Google Cloud Run](#google-cloud-run)
|
|
30
30
|
- [Netlify Lambda](#netlify-lambda)
|
|
31
|
+
- [Platformatic Cloud](#platformatic-cloud)
|
|
31
32
|
- [Vercel](#vercel)
|
|
32
33
|
|
|
33
34
|
## AWS
|
|
@@ -453,6 +454,47 @@ Add this command to your `package.json` *scripts*
|
|
|
453
454
|
|
|
454
455
|
Then it should work fine
|
|
455
456
|
|
|
457
|
+
## Platformatic Cloud
|
|
458
|
+
|
|
459
|
+
[Platformatic](https://platformatic.dev) provides zero-configuration deployment
|
|
460
|
+
for Node.js applications.
|
|
461
|
+
To use it now, you should wrap your existing Fastify application inside a
|
|
462
|
+
[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction),
|
|
463
|
+
by running the following:
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
npm create platformatic@latest -- service
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The wizard would ask you to fill in a few answers:
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
? Where would you like to create your project? .
|
|
474
|
+
? Do you want to run npm install? yes
|
|
475
|
+
? Do you want to use TypeScript? no
|
|
476
|
+
? What port do you want to use? 3042
|
|
477
|
+
[13:04:14] INFO: Configuration file platformatic.service.json successfully created.
|
|
478
|
+
[13:04:14] INFO: Environment file .env successfully created.
|
|
479
|
+
[13:04:14] INFO: Plugins folder "plugins" successfully created.
|
|
480
|
+
[13:04:14] INFO: Routes folder "routes" successfully created.
|
|
481
|
+
? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no
|
|
482
|
+
? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in
|
|
486
|
+
with your GitHub account.
|
|
487
|
+
Create your first application and a static workspace: be careful to download the
|
|
488
|
+
API key as an env file, e.g. `yourworkspace.txt`.
|
|
489
|
+
|
|
490
|
+
Then, you can easily deploy your application with the following commmand:
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
platformatic deploy --keys `yuourworkspace.txt`
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service)
|
|
497
|
+
on how to wrap Fastify application in Platformatic.
|
|
456
498
|
|
|
457
499
|
## Vercel
|
|
458
500
|
|
|
@@ -506,4 +548,4 @@ async function routes (fastify, options) {
|
|
|
506
548
|
}
|
|
507
549
|
|
|
508
550
|
export default routes;
|
|
509
|
-
```
|
|
551
|
+
```
|
package/docs/Guides/Testing.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
<h1
|
|
1
|
+
<h1 style="text-align: center;">Fastify</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Testing
|
|
4
|
+
<a id="testing"></a>
|
|
4
5
|
|
|
5
6
|
Testing is one of the most important parts of developing an application. Fastify
|
|
6
7
|
is very flexible when it comes to testing and is compatible with most testing
|
|
7
8
|
frameworks (such as [Tap](https://www.npmjs.com/package/tap), which is used in
|
|
8
9
|
the examples below).
|
|
9
10
|
|
|
11
|
+
## Application
|
|
12
|
+
|
|
10
13
|
Let's `cd` into a fresh directory called 'testing-example' and type `npm init
|
|
11
14
|
-y` in our terminal.
|
|
12
15
|
|
|
@@ -345,3 +348,133 @@ test('should ...', {only: true}, t => ...)
|
|
|
345
348
|
|
|
346
349
|
Now you should be able to step through your test file (and the rest of
|
|
347
350
|
`Fastify`) in your code editor.
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
## Plugins
|
|
355
|
+
Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init
|
|
356
|
+
-y` in our terminal.
|
|
357
|
+
|
|
358
|
+
Run `npm i fastify fastify-plugin && npm i tap -D`
|
|
359
|
+
|
|
360
|
+
**plugin/myFirstPlugin.js**:
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
const fP = require("fastify-plugin")
|
|
364
|
+
|
|
365
|
+
async function myPlugin(fastify, options) {
|
|
366
|
+
fastify.decorateRequest("helloRequest", "Hello World")
|
|
367
|
+
fastify.decorate("helloInstance", "Hello Fastify Instance")
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = fP(myPlugin)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md)
|
|
374
|
+
|
|
375
|
+
**test/myFirstPlugin.test.js**:
|
|
376
|
+
|
|
377
|
+
```js
|
|
378
|
+
const Fastify = require("fastify");
|
|
379
|
+
const tap = require("tap");
|
|
380
|
+
const myPlugin = require("../plugin/myFirstPlugin");
|
|
381
|
+
|
|
382
|
+
tap.test("Test the Plugin Route", async t => {
|
|
383
|
+
// Create a mock fastify application to test the plugin
|
|
384
|
+
const fastify = Fastify()
|
|
385
|
+
|
|
386
|
+
fastify.register(myPlugin)
|
|
387
|
+
|
|
388
|
+
// Add an endpoint of your choice
|
|
389
|
+
fastify.get("/", async (request, reply) => {
|
|
390
|
+
return ({ message: request.helloRequest })
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// Use fastify.inject to fake a HTTP Request
|
|
394
|
+
const fastifyResponse = await fastify.inject({
|
|
395
|
+
method: "GET",
|
|
396
|
+
url: "/"
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
console.log('status code: ', fastifyResponse.statusCode)
|
|
400
|
+
console.log('body: ', fastifyResponse.body)
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
Learn more about [```fastify.inject()```](#benefits-of-using-fastifyinject).
|
|
404
|
+
Run the test file in your terminal `node test/myFirstPlugin.test.js`
|
|
405
|
+
|
|
406
|
+
```sh
|
|
407
|
+
status code: 200
|
|
408
|
+
body: {"message":"Hello World"}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Now we can replace our `console.log` calls with actual tests!
|
|
412
|
+
|
|
413
|
+
In your `package.json` change the "test" script to:
|
|
414
|
+
|
|
415
|
+
`"test": "tap --reporter=list --watch"`
|
|
416
|
+
|
|
417
|
+
Create the tap test for the endpoint.
|
|
418
|
+
|
|
419
|
+
**test/myFirstPlugin.test.js**:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
const Fastify = require("fastify");
|
|
423
|
+
const tap = require("tap");
|
|
424
|
+
const myPlugin = require("../plugin/myFirstPlugin");
|
|
425
|
+
|
|
426
|
+
tap.test("Test the Plugin Route", async t => {
|
|
427
|
+
// Specifies the number of test
|
|
428
|
+
t.plan(2)
|
|
429
|
+
|
|
430
|
+
const fastify = Fastify()
|
|
431
|
+
|
|
432
|
+
fastify.register(myPlugin)
|
|
433
|
+
|
|
434
|
+
fastify.get("/", async (request, reply) => {
|
|
435
|
+
return ({ message: request.helloRequest })
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
const fastifyResponse = await fastify.inject({
|
|
439
|
+
method: "GET",
|
|
440
|
+
url: "/"
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
t.equal(fastifyResponse.statusCode, 200)
|
|
444
|
+
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
|
|
445
|
+
})
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Finally, run `npm test` in the terminal and see your test results!
|
|
449
|
+
|
|
450
|
+
Test the ```.decorate()``` and ```.decorateRequest()```.
|
|
451
|
+
|
|
452
|
+
**test/myFirstPlugin.test.js**:
|
|
453
|
+
|
|
454
|
+
```js
|
|
455
|
+
const Fastify = require("fastify");
|
|
456
|
+
const tap = require("tap");
|
|
457
|
+
const myPlugin = require("../plugin/myFirstPlugin");
|
|
458
|
+
|
|
459
|
+
tap.test("Test the Plugin Route", async t => {
|
|
460
|
+
t.plan(5)
|
|
461
|
+
const fastify = Fastify()
|
|
462
|
+
|
|
463
|
+
fastify.register(myPlugin)
|
|
464
|
+
|
|
465
|
+
fastify.get("/", async (request, reply) => {
|
|
466
|
+
// Testing the fastify decorators
|
|
467
|
+
t.not(request.helloRequest, null)
|
|
468
|
+
t.ok(request.helloRequest, "Hello World")
|
|
469
|
+
t.ok(fastify.helloInstance, "Hello Fastify Instance")
|
|
470
|
+
return ({ message: request.helloRequest })
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
const fastifyResponse = await fastify.inject({
|
|
474
|
+
method: "GET",
|
|
475
|
+
url: "/"
|
|
476
|
+
})
|
|
477
|
+
t.equal(fastifyResponse.statusCode, 200)
|
|
478
|
+
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
|
|
479
|
+
})
|
|
480
|
+
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<h1
|
|
1
|
+
<h1 style="text-align: center;">Fastify</h1>
|
|
2
2
|
|
|
3
3
|
# How to write a good plugin
|
|
4
4
|
First, thank you for deciding to write a plugin for Fastify. Fastify is a
|
|
@@ -63,6 +63,8 @@ among different versions of its dependencies.
|
|
|
63
63
|
We do not enforce any testing library. We use [`tap`](https://www.node-tap.org/)
|
|
64
64
|
since it offers out-of-the-box parallel testing and code coverage, but it is up
|
|
65
65
|
to you to choose your library of preference.
|
|
66
|
+
We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to
|
|
67
|
+
learn about how to test your plugins.
|
|
66
68
|
|
|
67
69
|
## Code Linter
|
|
68
70
|
It is not mandatory, but we highly recommend you use a code linter in your
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Technical Principles
|
|
2
|
+
|
|
3
|
+
Every decision in the Fastify framework and its official plugins is guided by
|
|
4
|
+
the following technical principles:
|
|
5
|
+
|
|
6
|
+
1. “Zero” overhead in production
|
|
7
|
+
2. “Good” developer experience
|
|
8
|
+
3. Works great for small & big projects alike
|
|
9
|
+
4. Easy to migrate to microservices (or even serverless) and back
|
|
10
|
+
5. Security & data validation
|
|
11
|
+
6. If something could be a plugin, it likely should be
|
|
12
|
+
7. Easily testable
|
|
13
|
+
8. Do not monkeypatch core
|
|
14
|
+
9. Semantic versioning & Long Term Support
|
|
15
|
+
10. Specification adherence
|
|
16
|
+
|
|
17
|
+
## "Zero" Overhead in Production
|
|
18
|
+
|
|
19
|
+
Fastify aims to implement its features by adding as minimal overhead to your
|
|
20
|
+
application as possible.
|
|
21
|
+
This is usually delivered by implementing fast algorithms and data structures,
|
|
22
|
+
as well as JavaScript-specific features.
|
|
23
|
+
|
|
24
|
+
Given that JavaScript does not offer zero-overhead data structures, this principle
|
|
25
|
+
is at odds with providing a great developer experience and providing more features,
|
|
26
|
+
as usually those cost some overhead.
|
|
27
|
+
|
|
28
|
+
## "Good" Developer Experience
|
|
29
|
+
|
|
30
|
+
Fastify aims to provide the best developer experience at the performance point
|
|
31
|
+
it is operating.
|
|
32
|
+
It provides a great out-of-the-box experience that is flexible enough to be
|
|
33
|
+
adapted to a variety of situations.
|
|
34
|
+
|
|
35
|
+
As an example, this means that binary addons are forbidden because most JavaScript
|
|
36
|
+
developers would not
|
|
37
|
+
have access to a compiler.
|
|
38
|
+
|
|
39
|
+
## Works great for small and big projects alike
|
|
40
|
+
|
|
41
|
+
We recognize that most applications start small and become more complex over time.
|
|
42
|
+
Fastify aims to grow with
|
|
43
|
+
the complexity of your application, providing advanced features to structure
|
|
44
|
+
your codebase.
|
|
45
|
+
|
|
46
|
+
## Easy to migrate to microservices (or even serverless) and back
|
|
47
|
+
|
|
48
|
+
How you deploy your routes should not matter. The framework should "just work".
|
|
49
|
+
|
|
50
|
+
## Security and Data Validation
|
|
51
|
+
|
|
52
|
+
Your web framework is the first point of contact with untrusted data, and it
|
|
53
|
+
needs to act as the first line of defense for your system.
|
|
54
|
+
|
|
55
|
+
## If something could be a plugin, it likely should
|
|
56
|
+
|
|
57
|
+
We recognize that there are an infinite amount of use cases for an HTTP framework
|
|
58
|
+
for Node.js. Catering to them in a single module would make the codebase unmaintainable.
|
|
59
|
+
Therefore we provide hooks and options to allow you to customize the framework
|
|
60
|
+
as you please.
|
|
61
|
+
|
|
62
|
+
## Easily testable
|
|
63
|
+
|
|
64
|
+
Testing Fastify applications should be a first-class concern.
|
|
65
|
+
|
|
66
|
+
## Do not monkeypatch core
|
|
67
|
+
|
|
68
|
+
Moonkeypatch Node.js APIs or installing globals that alter the behavior of the
|
|
69
|
+
runtime makes building modular applications harder, and limit the use cases of Fastify.
|
|
70
|
+
Other frameworks do this and we do not.
|
|
71
|
+
|
|
72
|
+
## Semantic Versioning and Long Term Support
|
|
73
|
+
|
|
74
|
+
We provide a clear Long Term Support strategy so developers can know when to upgrade.
|
|
75
|
+
|
|
76
|
+
## Specification adherence
|
|
77
|
+
|
|
78
|
+
In doubt, we chose the strict behavior as defined by the relevant Specifications.
|
|
@@ -27,6 +27,8 @@ Request is a core Fastify object containing the following fields:
|
|
|
27
27
|
- `protocol` - the protocol of the incoming request (`https` or `http`)
|
|
28
28
|
- `method` - the method of the incoming request
|
|
29
29
|
- `url` - the URL of the incoming request
|
|
30
|
+
- `originalUrl` - similar to `url`, this allows you to access the
|
|
31
|
+
original `url` in case of internal re-routing
|
|
30
32
|
- `routerMethod` - the method defined for the router that is handling the
|
|
31
33
|
request
|
|
32
34
|
- `routerPath` - the path pattern defined for the router that is handling the
|
package/docs/Reference/Routes.md
CHANGED
|
@@ -112,6 +112,11 @@ fastify.route(options)
|
|
|
112
112
|
* `config`: object used to store custom configuration.
|
|
113
113
|
* `version`: a [semver](https://semver.org/) compatible string that defined the
|
|
114
114
|
version of the endpoint. [Example](#version-constraints).
|
|
115
|
+
* `constraints`: defines route restrictions based on request properties or
|
|
116
|
+
values, enabling customized matching using
|
|
117
|
+
[find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes
|
|
118
|
+
built-in `version` and `host` constraints, with support for custom constraint
|
|
119
|
+
strategies.
|
|
115
120
|
* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a
|
|
116
121
|
route with a prefix.
|
|
117
122
|
* `both` (default): Will register both `/prefix` and `/prefix/`.
|
package/docs/Reference/Server.md
CHANGED
|
@@ -527,8 +527,8 @@ Defines the label used for the request identifier when logging the request.
|
|
|
527
527
|
### `genReqId`
|
|
528
528
|
<a id="factory-gen-request-id"></a>
|
|
529
529
|
|
|
530
|
-
Function for generating the request-id. It will receive the incoming
|
|
531
|
-
a parameter. This function is expected to be error-free.
|
|
530
|
+
Function for generating the request-id. It will receive the _raw_ incoming
|
|
531
|
+
request as a parameter. This function is expected to be error-free.
|
|
532
532
|
|
|
533
533
|
+ Default: `value of 'request-id' header if provided or monotonically increasing
|
|
534
534
|
integers`
|
|
@@ -856,6 +856,9 @@ is an instance-wide configuration.
|
|
|
856
856
|
[server](https://nodejs.org/api/http.html#http_class_http_server) object as
|
|
857
857
|
returned by the [**`Fastify factory function`**](#factory).
|
|
858
858
|
|
|
859
|
+
>__Warning__: If utilized improperly, certain Fastify features could be disrupted.
|
|
860
|
+
>It is recommended to only use it for attaching listeners.
|
|
861
|
+
|
|
859
862
|
#### after
|
|
860
863
|
<a id="after"></a>
|
|
861
864
|
|
|
@@ -11,15 +11,16 @@ keep associated types for each schema defined in your project.
|
|
|
11
11
|
|
|
12
12
|
Type Providers are offered as additional packages you will need to install into
|
|
13
13
|
your project. Each provider uses a different inference library under the hood;
|
|
14
|
-
allowing you to select the library most appropriate for your needs. Type
|
|
14
|
+
allowing you to select the library most appropriate for your needs. Official Type
|
|
15
15
|
Provider packages follow a `@fastify/type-provider-{provider-name}` naming
|
|
16
|
-
convention.
|
|
16
|
+
convention, and there are several community ones available as well.
|
|
17
17
|
|
|
18
18
|
The following inference packages are supported:
|
|
19
19
|
|
|
20
20
|
- `json-schema-to-ts` -
|
|
21
21
|
[github](https://github.com/ThomasAribart/json-schema-to-ts)
|
|
22
22
|
- `typebox` - [github](https://github.com/sinclairzx81/typebox)
|
|
23
|
+
- `zod` - [github](https://github.com/colinhacks/zod)
|
|
23
24
|
|
|
24
25
|
### Json Schema to Ts
|
|
25
26
|
|
|
@@ -91,6 +92,12 @@ See also the [TypeBox
|
|
|
91
92
|
documentation](https://github.com/sinclairzx81/typebox#validation) on how to set
|
|
92
93
|
up AJV to work with TypeBox.
|
|
93
94
|
|
|
95
|
+
### Zod
|
|
96
|
+
|
|
97
|
+
See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod)
|
|
98
|
+
for Zod type provider instructions.
|
|
99
|
+
|
|
100
|
+
|
|
94
101
|
### Scoped Type-Provider
|
|
95
102
|
|
|
96
103
|
The provider types don't propagate globally. In encapsulated usage, one can
|
|
@@ -111,7 +111,7 @@ generic types for route schemas and the dynamic properties located on the
|
|
|
111
111
|
route-level `request` object.
|
|
112
112
|
|
|
113
113
|
1. If you did not complete the previous example, follow steps 1-4 to get set up.
|
|
114
|
-
2. Inside `index.ts`, define
|
|
114
|
+
2. Inside `index.ts`, define three interfaces `IQuerystring`,`IHeaders` and `IReply`:
|
|
115
115
|
```typescript
|
|
116
116
|
interface IQuerystring {
|
|
117
117
|
username: string;
|
|
@@ -121,8 +121,14 @@ route-level `request` object.
|
|
|
121
121
|
interface IHeaders {
|
|
122
122
|
'h-Custom': string;
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
interface IReply {
|
|
126
|
+
200: { success: boolean };
|
|
127
|
+
302: { url: string };
|
|
128
|
+
'4xx': { error: string };
|
|
129
|
+
}
|
|
124
130
|
```
|
|
125
|
-
3. Using the
|
|
131
|
+
3. Using the three interfaces, define a new API route and pass them as generics.
|
|
126
132
|
The shorthand route methods (i.e. `.get`) accept a generic object
|
|
127
133
|
`RouteGenericInterface` containing five named properties: `Body`,
|
|
128
134
|
`Querystring`, `Params`, `Headers` and `Reply`. The interfaces `Body`,
|
|
@@ -132,12 +138,20 @@ route-level `request` object.
|
|
|
132
138
|
```typescript
|
|
133
139
|
server.get<{
|
|
134
140
|
Querystring: IQuerystring,
|
|
135
|
-
Headers: IHeaders
|
|
141
|
+
Headers: IHeaders,
|
|
142
|
+
Reply: IReply
|
|
136
143
|
}>('/auth', async (request, reply) => {
|
|
137
144
|
const { username, password } = request.query
|
|
138
145
|
const customerHeader = request.headers['h-Custom']
|
|
139
146
|
// do something with request data
|
|
140
147
|
|
|
148
|
+
// chaining .statusCode/.code calls with .send allows type narrowing. For example:
|
|
149
|
+
// this works
|
|
150
|
+
reply.code(200).send({ success: true });
|
|
151
|
+
// but this gives a type error
|
|
152
|
+
reply.code(200).send('uh-oh');
|
|
153
|
+
// it even works for wildcards
|
|
154
|
+
reply.code(404).send({ error: 'Not found' });
|
|
141
155
|
return `logged in!`
|
|
142
156
|
})
|
|
143
157
|
```
|
|
@@ -154,7 +168,8 @@ route-level `request` object.
|
|
|
154
168
|
```typescript
|
|
155
169
|
server.get<{
|
|
156
170
|
Querystring: IQuerystring,
|
|
157
|
-
Headers: IHeaders
|
|
171
|
+
Headers: IHeaders,
|
|
172
|
+
Reply: IReply
|
|
158
173
|
}>('/auth', {
|
|
159
174
|
preValidation: (request, reply, done) => {
|
|
160
175
|
const { username, password } = request.query
|
|
@@ -172,7 +187,7 @@ route-level `request` object.
|
|
|
172
187
|
admin"}`
|
|
173
188
|
|
|
174
189
|
🎉 Good work, now you can define interfaces for each route and have strictly
|
|
175
|
-
typed request and reply instances. Other parts of the Fastify type system rely
|
|
190
|
+
typed request and reply instances. Other parts of the Fastify type system rely
|
|
176
191
|
on generic properties. Make sure to reference the detailed type system
|
|
177
192
|
documentation below to learn more about what is available.
|
|
178
193
|
|
|
@@ -493,7 +508,7 @@ Fastify Plugin in a TypeScript Project.
|
|
|
493
508
|
`"compilerOptions"` object.
|
|
494
509
|
```json
|
|
495
510
|
{
|
|
496
|
-
"
|
|
511
|
+
"compilerOptions": {
|
|
497
512
|
"declaration": true
|
|
498
513
|
}
|
|
499
514
|
}
|
package/fastify.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ declare namespace fastify {
|
|
|
109
109
|
requestIdHeader?: string | false,
|
|
110
110
|
requestIdLogLabel?: string;
|
|
111
111
|
jsonShorthand?: boolean;
|
|
112
|
-
genReqId?:
|
|
112
|
+
genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
|
|
113
113
|
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
|
|
114
114
|
querystringParser?: (str: string) => { [key: string]: unknown },
|
|
115
115
|
/**
|
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '4.
|
|
3
|
+
const VERSION = '4.19.1'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('http')
|
|
@@ -426,10 +426,13 @@ function fastify (options) {
|
|
|
426
426
|
router.closeRoutes()
|
|
427
427
|
|
|
428
428
|
hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
429
|
+
// No new TCP connections are accepted.
|
|
430
|
+
// We must call close on the server even if we are not listening
|
|
431
|
+
// otherwise memory will be leaked.
|
|
432
|
+
// https://github.com/nodejs/node/issues/48604
|
|
433
|
+
instance.server.close(done)
|
|
432
434
|
|
|
435
|
+
if (fastify[kState].listening) {
|
|
433
436
|
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
434
437
|
if (forceCloseConnections === 'idle') {
|
|
435
438
|
// Not needed in Node 19
|
|
@@ -653,6 +656,11 @@ function fastify (options) {
|
|
|
653
656
|
errorStatus = http.STATUS_CODES[errorCode]
|
|
654
657
|
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
|
655
658
|
errorLabel = 'timeout'
|
|
659
|
+
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
|
660
|
+
errorCode = '431'
|
|
661
|
+
errorStatus = http.STATUS_CODES[errorCode]
|
|
662
|
+
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
|
663
|
+
errorLabel = 'header_overflow'
|
|
656
664
|
} else {
|
|
657
665
|
errorCode = '400'
|
|
658
666
|
errorStatus = http.STATUS_CODES[errorCode]
|
|
@@ -692,10 +700,13 @@ function fastify (options) {
|
|
|
692
700
|
const id = genReqId(req)
|
|
693
701
|
const childLogger = logger.child({ reqId: id })
|
|
694
702
|
|
|
695
|
-
childLogger.info({ req }, 'incoming request')
|
|
696
|
-
|
|
697
703
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
698
704
|
const reply = new Reply(res, request, childLogger)
|
|
705
|
+
|
|
706
|
+
if (disableRequestLogging === false) {
|
|
707
|
+
childLogger.info({ req: request }, 'incoming request')
|
|
708
|
+
}
|
|
709
|
+
|
|
699
710
|
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
700
711
|
}
|
|
701
712
|
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
|
|
@@ -714,10 +725,13 @@ function fastify (options) {
|
|
|
714
725
|
const id = genReqId(req)
|
|
715
726
|
const childLogger = logger.child({ reqId: id })
|
|
716
727
|
|
|
717
|
-
childLogger.info({ req }, 'incoming request')
|
|
718
|
-
|
|
719
728
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
720
729
|
const reply = new Reply(res, request, childLogger)
|
|
730
|
+
|
|
731
|
+
if (disableRequestLogging === false) {
|
|
732
|
+
childLogger.info({ req: request }, 'incoming request')
|
|
733
|
+
}
|
|
734
|
+
|
|
721
735
|
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
722
736
|
}
|
|
723
737
|
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
|
|
@@ -793,6 +807,7 @@ function fastify (options) {
|
|
|
793
807
|
// only call isAsyncConstraint once
|
|
794
808
|
if (isAsync === undefined) isAsync = router.isAsyncConstraint()
|
|
795
809
|
if (rewriteUrl) {
|
|
810
|
+
req.originalUrl = req.url
|
|
796
811
|
const url = rewriteUrl.call(fastify, req)
|
|
797
812
|
if (typeof url === 'string') {
|
|
798
813
|
req.url = url
|
package/lib/contentTypeParser.js
CHANGED
|
@@ -281,7 +281,7 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
|
|
|
281
281
|
return defaultJsonParser
|
|
282
282
|
|
|
283
283
|
function defaultJsonParser (req, body, done) {
|
|
284
|
-
if (body === '' || body == null) {
|
|
284
|
+
if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) {
|
|
285
285
|
return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
|
|
286
286
|
}
|
|
287
287
|
let json
|
package/lib/reply.js
CHANGED
|
@@ -357,7 +357,7 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
|
|
|
357
357
|
})
|
|
358
358
|
|
|
359
359
|
// We create a WeakMap to compile the schema only once
|
|
360
|
-
// Its done
|
|
360
|
+
// Its done lazily to avoid add overhead by creating the WeakMap
|
|
361
361
|
// if it is not used
|
|
362
362
|
// TODO: Explore a central cache for all the schemas shared across
|
|
363
363
|
// encapsulated contexts
|
|
@@ -859,7 +859,7 @@ function notFound (reply) {
|
|
|
859
859
|
* @param {object} context the request context
|
|
860
860
|
* @param {object} data the JSON payload to serialize
|
|
861
861
|
* @param {number} statusCode the http status code
|
|
862
|
-
* @param {string} contentType the reply content type
|
|
862
|
+
* @param {string} [contentType] the reply content type
|
|
863
863
|
* @returns {string} the serialized payload
|
|
864
864
|
*/
|
|
865
865
|
function serialize (context, data, statusCode, contentType) {
|
package/lib/request.js
CHANGED
|
@@ -13,7 +13,8 @@ const {
|
|
|
13
13
|
kOptions,
|
|
14
14
|
kRequestCacheValidateFns,
|
|
15
15
|
kRouteContext,
|
|
16
|
-
kPublicRouteContext
|
|
16
|
+
kPublicRouteContext,
|
|
17
|
+
kRequestOriginalUrl
|
|
17
18
|
} = require('./symbols')
|
|
18
19
|
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
|
|
19
20
|
|
|
@@ -149,6 +150,15 @@ Object.defineProperties(Request.prototype, {
|
|
|
149
150
|
return this.raw.url
|
|
150
151
|
}
|
|
151
152
|
},
|
|
153
|
+
originalUrl: {
|
|
154
|
+
get () {
|
|
155
|
+
/* istanbul ignore else */
|
|
156
|
+
if (!this[kRequestOriginalUrl]) {
|
|
157
|
+
this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url
|
|
158
|
+
}
|
|
159
|
+
return this[kRequestOriginalUrl]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
152
162
|
method: {
|
|
153
163
|
get () {
|
|
154
164
|
return this.raw.method
|
|
@@ -162,7 +172,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
162
172
|
},
|
|
163
173
|
routerPath: {
|
|
164
174
|
get () {
|
|
165
|
-
return this[kRouteContext].config
|
|
175
|
+
return this[kRouteContext].config?.url
|
|
166
176
|
}
|
|
167
177
|
},
|
|
168
178
|
routeOptions: {
|
|
@@ -172,8 +182,8 @@ Object.defineProperties(Request.prototype, {
|
|
|
172
182
|
const serverLimit = context.server.initialConfig.bodyLimit
|
|
173
183
|
const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
|
|
174
184
|
const options = {
|
|
175
|
-
method: context.config
|
|
176
|
-
url: context.config
|
|
185
|
+
method: context.config?.method,
|
|
186
|
+
url: context.config?.url,
|
|
177
187
|
bodyLimit: (routeLimit || serverLimit),
|
|
178
188
|
attachValidation: context.attachValidation,
|
|
179
189
|
logLevel: context.logLevel,
|
|
@@ -186,12 +196,12 @@ Object.defineProperties(Request.prototype, {
|
|
|
186
196
|
},
|
|
187
197
|
routerMethod: {
|
|
188
198
|
get () {
|
|
189
|
-
return this[kRouteContext].config
|
|
199
|
+
return this[kRouteContext].config?.method
|
|
190
200
|
}
|
|
191
201
|
},
|
|
192
202
|
routeConfig: {
|
|
193
203
|
get () {
|
|
194
|
-
return this[kRouteContext][kPublicRouteContext]
|
|
204
|
+
return this[kRouteContext][kPublicRouteContext]?.config
|
|
195
205
|
}
|
|
196
206
|
},
|
|
197
207
|
routeSchema: {
|
|
@@ -201,7 +211,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
201
211
|
},
|
|
202
212
|
is404: {
|
|
203
213
|
get () {
|
|
204
|
-
return this[kRouteContext].config
|
|
214
|
+
return this[kRouteContext].config?.url === undefined
|
|
205
215
|
}
|
|
206
216
|
},
|
|
207
217
|
connection: {
|
|
@@ -283,7 +293,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
283
293
|
})
|
|
284
294
|
|
|
285
295
|
// We create a WeakMap to compile the schema only once
|
|
286
|
-
// Its done
|
|
296
|
+
// Its done lazily to avoid add overhead by creating the WeakMap
|
|
287
297
|
// if it is not used
|
|
288
298
|
// TODO: Explore a central cache for all the schemas shared across
|
|
289
299
|
// encapsulated contexts
|
package/lib/route.js
CHANGED
|
@@ -468,7 +468,6 @@ function buildRouting (options) {
|
|
|
468
468
|
|
|
469
469
|
const request = new context.Request(id, params, req, query, childLogger, context)
|
|
470
470
|
const reply = new context.Reply(res, request, childLogger)
|
|
471
|
-
|
|
472
471
|
if (disableRequestLogging === false) {
|
|
473
472
|
childLogger.info({ req: request }, 'incoming request')
|
|
474
473
|
}
|