Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

3
backend/node_modules/fastify/.borp.yaml generated vendored Normal file
View File

@@ -0,0 +1,3 @@
files:
- 'test/**/*.test.js'
- 'test/**/*.test.mjs'

View File

@@ -1,8 +0,0 @@
{
"exclude": [
"lib/configValidator.js",
"lib/error-serializer.js",
"build/build-error-serializer.js",
"test/*"
]
}

View File

@@ -1,4 +0,0 @@
{
"root": true,
"extends": "standard"
}

View File

@@ -7,7 +7,7 @@ config:
MD013:
line_length: 80
code_block_line_length: 120
headers: false
headings: false
tables: false
strict: false
stern: false

11
backend/node_modules/fastify/.taprc generated vendored
View File

@@ -1,11 +0,0 @@
ts: false
jsx: false
flow: false
# the coverage is performed by c8
check-coverage: false
coverage: false
node-arg: --allow-natives-syntax
files:
- 'test/**/*.test.js'
- 'test/**/*.test.mjs'

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016-2024 The Fastify Team
Copyright (c) 2016-present The Fastify Team
The Fastify team members are listed at https://github.com/fastify/fastify#team
and in the README file.

View File

@@ -1,6 +1,6 @@
# Fastify Charter
The Fastify project aims to build a fast and low overhead web framework for
The Fastify project aims to build a fast and low-overhead web framework for
Node.js.
@@ -20,7 +20,7 @@ experience with the least overhead and a plugin architecture.
### 1.1: In-scope
+ Develop a web framework for Node.js with a focus on developer experience,
performance and extensibility.
performance, and extensibility.
+ Plugin Architecture
+ Support web protocols
+ Official plugins for common user requirements
@@ -43,7 +43,7 @@ experience with the least overhead and a plugin architecture.
+ Support versions of Node.js at EOL (end of life) stage
+ Support serverless architecture
+ Contributions that violates the [Code of Conduct](CODE_OF_CONDUCT.md)
+ Contributions that violate the [Code of Conduct](CODE_OF_CONDUCT.md)
## Section 2: Relationship with OpenJS Foundation CPC.
@@ -58,10 +58,10 @@ the Board of Directors (the "Board").
This Fastify Charter reflects a carefully constructed balanced role for the
Collaborators and the CPC in the governance of the OpenJS Foundation. The
charter amendment process is for the Fastify Collaborators to propose change
using simple majority of the full Fastify Organization, the proposed changes
using a majority of the full Fastify Organization, the proposed changes
being subject to review and approval by the CPC. The CPC may additionally make
amendments to the Collaborators charter at any time, though the CPC will not
interfere with day-to-day discussions, votes or meetings of the Fastify
interfere with day-to-day discussions, votes, or meetings of the Fastify
Organization.
@@ -92,7 +92,7 @@ Section Intentionally Left Blank
Fastify's features can be discussed in GitHub issues and/or projects. Consensus
on a discussion is reached when there is no objection by any collaborators.
Whenever there is not consensus, Lead Maintainers will have final say on the
When there is no consensus, Lead Maintainers will have the final say on the
topic.
**Voting, and/or Elections**
@@ -112,7 +112,7 @@ Section Intentionally Left Blank
Foundation Board.
+ *Collaborators*: contribute code and other artifacts, have the right to commit
to the code base and release plugins projects. Collaborators follow the
to the code base, and release plugin projects. Collaborators follow the
[CONTRIBUTING](CONTRIBUTING.md) guidelines to manage the project. A
Collaborator could be encumbered by other Fastify Collaborators and never by
the CPC or OpenJS Foundation Board.

View File

@@ -9,12 +9,12 @@
<div align="center">
[![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg)](https://github.com/fastify/fastify/actions/workflows/ci.yml)
[![CI](https://github.com/fastify/fastify/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/ci.yml)
[![Package Manager
CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml)
CI](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml)
[![Web
SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
site](https://github.com/fastify/fastify/actions/workflows/website.yml/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585)
</div>
@@ -29,16 +29,16 @@ downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.np
Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/fastify/fastify/blob/main/SECURITY.md)
[![Discord](https://img.shields.io/discord/725613461949906985)](https://discord.gg/fastify)
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=blue)](https://gitpod.io/#https://github.com/fastify/fastify)
![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fastify)
[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fastify)](https://github.com/sponsors/fastify#sponsors)
</div>
<br />
An efficient server implies a lower cost of the infrastructure, a better
responsiveness under load and happy users. How can you efficiently handle the
An efficient server implies a lower cost of the infrastructure, better
responsiveness under load, and happy users. How can you efficiently handle the
resources of your server, knowing that you are serving the highest number of
requests as possible, without sacrificing security validations and handy
requests possible, without sacrificing security validations and handy
development?
Enter Fastify. Fastify is a web framework highly focused on providing the best
@@ -46,17 +46,14 @@ developer experience with the least overhead and a powerful plugin architecture.
It is inspired by Hapi and Express and as far as we know, it is one of the
fastest web frameworks in town.
The `main` branch refers to the Fastify `v4` release. Check out the
[`v3.x` branch](https://github.com/fastify/fastify/tree/3.x) for `v3`.
The `main` branch refers to the Fastify `v5` release.
Check out the [`4.x` branch](https://github.com/fastify/fastify/tree/4.x) for `v4`.
### Table of Contents
- [Quick start](#quick-start)
- [Install](#install)
- [Example](#example)
- [Fastify v1.x and v2.x](#fastify-v1x-and-v2x)
- [Core features](#core-features)
- [Benchmarks](#benchmarks)
- [Documentation](#documentation)
@@ -109,14 +106,9 @@ generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli).
To install Fastify in an existing project as a dependency:
Install with npm:
```sh
npm i fastify
```
Install with yarn:
```sh
yarn add fastify
```
### Example
@@ -146,7 +138,7 @@ fastify.listen({ port: 3000 }, (err, address) => {
})
```
with async-await:
With async-await:
```js
// ESM
@@ -173,13 +165,14 @@ fastify.listen({ port: 3000 }, (err, address) => {
Do you want to know more? Head to the <a
href="./docs/Guides/Getting-Started.md"><code><b>Getting Started</b></code></a>.
If you learn best by reading code, explore the official [demo](https://github.com/fastify/demo).
> ## Note
> `.listen` binds to the local host, `localhost`, interface by default
> (`127.0.0.1` or `::1`, depending on the operating system configuration). If
> you are running Fastify in a container (Docker,
> [GCP](https://cloud.google.com/), etc.), you may need to bind to `0.0.0.0`. Be
> careful when deciding to listen on all interfaces; it comes with inherent
> careful when listening on all interfaces; it comes with inherent
> [security
> risks](https://web.archive.org/web/20170711105010/https://snyk.io/blog/mongodb-hack-and-secure-defaults/).
> See [the documentation](./docs/Reference/Server.md#listen) for more
@@ -190,16 +183,16 @@ href="./docs/Guides/Getting-Started.md"><code><b>Getting Started</b></code></a>.
- **Highly performant:** as far as we know, Fastify is one of the fastest web
frameworks in town, depending on the code complexity we can serve up to 76+
thousand requests per second.
- **Extensible:** Fastify is fully extensible via its hooks, plugins and
- **Extensible:** Fastify is fully extensible via its hooks, plugins, and
decorators.
- **Schema based:** even if it is not mandatory we recommend to use [JSON
- **Schema-based:** even if it is not mandatory we recommend using [JSON
Schema](https://json-schema.org/) to validate your routes and serialize your
outputs, internally Fastify compiles the schema in a highly performant
outputs. Internally Fastify compiles the schema in a highly performant
function.
- **Logging:** logs are extremely important but are costly; we chose the best
logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)!
- **Developer friendly:** the framework is built to be very expressive and help
the developer in their daily use, without sacrificing performance and
developers in their daily use without sacrificing performance and
security.
### Benchmarks
@@ -219,10 +212,10 @@ second average
| - | | | |
| `http.Server` | 16.14.2 | &#10007; | 74,513 |
Benchmarks taken using https://github.com/fastify/benchmarks. This is a
synthetic, "hello world" benchmark that aims to evaluate the framework overhead.
These benchmarks taken using https://github.com/fastify/benchmarks. This is a
synthetic "hello world" benchmark that aims to evaluate the framework overhead.
The overhead that each framework has on your application depends on your
application, you should __always__ benchmark if performance matters to you.
application. You should __always__ benchmark if performance matters to you.
## Documentation
* [__`Getting Started`__](./docs/Guides/Getting-Started.md)
@@ -252,13 +245,11 @@ application, you should __always__ benchmark if performance matters to you.
* [__`Serverless`__](./docs/Guides/Serverless.md)
* [__`Recommendations`__](./docs/Guides/Recommendations.md)
中文文档[地址](https://github.com/fastify/docs-chinese/blob/HEAD/README.md)
## Ecosystem
- [Core](./docs/Guides/Ecosystem.md#core) - Core plugins maintained by the
_Fastify_ [team](#team).
- [Community](./docs/Guides/Ecosystem.md#community) - Community supported
- [Community](./docs/Guides/Ecosystem.md#community) - Community-supported
plugins.
- [Live Examples](https://github.com/fastify/example) - Multirepo with a broad
set of real working examples.
@@ -269,9 +260,17 @@ application, you should __always__ benchmark if performance matters to you.
Please visit [Fastify help](https://github.com/fastify/help) to view prior
support issues and to ask new support questions.
Version 3 of Fastify and lower are EOL and will not receive any security or bug
fixes.
Fastify's partner, HeroDevs, provides commercial security fixes for all
unsupported versions at [https://herodevs.com/support/fastify-nes][hd-link].
Fastify's supported version matrix is available in the
[Long Term Support][lts-link] documentation.
## Contributing
Whether reporting bugs, discussing improvements and new ideas or writing code,
Whether reporting bugs, discussing improvements and new ideas, or writing code,
we welcome contributions from anyone and everyone. Please read the [CONTRIBUTING](./CONTRIBUTING.md)
guidelines before submitting pull requests.
@@ -282,98 +281,103 @@ listed in alphabetical order.
**Lead Maintainers:**
* [__Matteo Collina__](https://github.com/mcollina),
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
<https://x.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
<https://x.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__KaKa Ng__](https://github.com/climba03003),
<https://www.npmjs.com/~climba03003>
* [__Manuel Spigolon__](https://github.com/eomm),
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
<https://x.com/manueomm>, <https://www.npmjs.com/~eomm>
* [__James Sumners__](https://github.com/jsumners),
<https://twitter.com/jsumners79>, <https://www.npmjs.com/~jsumners>
<https://x.com/jsumners79>, <https://www.npmjs.com/~jsumners>
### Fastify Core team
* [__Tommaso Allevi__](https://github.com/allevo),
<https://twitter.com/allevitommaso>, <https://www.npmjs.com/~allevo>
* [__Harry Brundage__](https://github.com/airhorns/),
<https://twitter.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
* [__David Mark Clements__](https://github.com/davidmarkclements),
<https://twitter.com/davidmarkclem>,
<https://www.npmjs.com/~davidmarkclements>
* [__Matteo Collina__](https://github.com/mcollina),
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Dustin Deus__](https://github.com/StarpTech),
<https://twitter.com/dustindeus>, <https://www.npmjs.com/~starptech>
* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
<https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
* [__Denis Fäcke__](https://github.com/SerayaEryn),
<https://twitter.com/serayaeryn>, <https://www.npmjs.com/~serayaeryn>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
<https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Luciano Mammino__](https://github.com/lmammino),
<https://twitter.com/loige>, <https://www.npmjs.com/~lmammino>
* [__Luis Orbaiceta__](https://github.com/luisorbaiceta),
<https://twitter.com/luisorbai>, <https://www.npmjs.com/~luisorbaiceta>
* [__Maksim Sinik__](https://github.com/fox1t),
<https://twitter.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
* [__Manuel Spigolon__](https://github.com/eomm),
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
* [__James Sumners__](https://github.com/jsumners),
<https://twitter.com/jsumners79>, <https://www.npmjs.com/~jsumners>
* [__Aras Abbasi__](https://github.com/uzlopak),
<https://www.npmjs.com/~uzlopak>
* [__Harry Brundage__](https://github.com/airhorns/),
<https://x.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
* [__Matteo Collina__](https://github.com/mcollina),
<https://x.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
<https://www.npmjs.com/~gurgunday>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://x.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://x.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Luciano Mammino__](https://github.com/lmammino),
<https://x.com/loige>, <https://www.npmjs.com/~lmammino>
* [__Jean Michelet__](https://github.com/jean-michelet),
<https://www.npmjs.com/~jean-michelet>
* [__KaKa Ng__](https://github.com/climba03003),
<https://www.npmjs.com/~climba03003>
* [__Luis Orbaiceta__](https://github.com/luisorbaiceta),
<https://x.com/luisorbai>, <https://www.npmjs.com/~luisorbaiceta>
* [__Maksim Sinik__](https://github.com/fox1t),
<https://x.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
* [__Manuel Spigolon__](https://github.com/eomm),
<https://x.com/manueomm>, <https://www.npmjs.com/~eomm>
* [__James Sumners__](https://github.com/jsumners),
<https://x.com/jsumners79>, <https://www.npmjs.com/~jsumners>
### Fastify Plugins team
* [__Matteo Collina__](https://github.com/mcollina),
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Harry Brundage__](https://github.com/airhorns/),
<https://twitter.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
<https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Salman Mitha__](https://github.com/salmanm),
<https://www.npmjs.com/~salmanm>
* [__Maksim Sinik__](https://github.com/fox1t),
<https://twitter.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
* [__Frazer Smith__](https://github.com/Fdawgs), <https://www.npmjs.com/~fdawgs>
* [__Manuel Spigolon__](https://github.com/eomm),
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
<https://x.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
* [__Simone Busoli__](https://github.com/simoneb),
<https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
<https://x.com/simonebu>, <https://www.npmjs.com/~simoneb>
* [__Dan Castillo__](https://github.com/dancastillo),
<https://www.npmjs.com/~dancastillo>
* [__Matteo Collina__](https://github.com/mcollina),
<https://x.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
<https://www.npmjs.com/~gurgunday>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://x.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://x.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Jean Michelet__](https://github.com/jean-michelet),
<https://www.npmjs.com/~jean-michelet>
* [__KaKa Ng__](https://github.com/climba03003),
<https://www.npmjs.com/~climba03003>
* [__Maksim Sinik__](https://github.com/fox1t),
<https://x.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
* [__Frazer Smith__](https://github.com/Fdawgs), <https://www.npmjs.com/~fdawgs>
* [__Manuel Spigolon__](https://github.com/eomm),
<https://x.com/manueomm>, <https://www.npmjs.com/~eomm>
### Great Contributors
Great contributors on a specific area in the Fastify ecosystem will be invited
to join this group by Lead Maintainers.
### Emeritus Contributors
Great contributors to a specific area of the Fastify ecosystem will be invited
to join this group by Lead Maintainers when they decide to step down from the
active contributor's group.
* [__dalisoft__](https://github.com/dalisoft), <https://twitter.com/dalisoft>,
<https://www.npmjs.com/~dalisoft>
* [__Luciano Mammino__](https://github.com/lmammino),
<https://twitter.com/loige>, <https://www.npmjs.com/~lmammino>
* [__Evan Shortiss__](https://github.com/evanshortiss),
<https://twitter.com/evanshortiss>, <https://www.npmjs.com/~evanshortiss>
**Past Collaborators**
* [__Çağatay Çalı__](https://github.com/cagataycali),
<https://twitter.com/cagataycali>, <https://www.npmjs.com/~cagataycali>
* [__Trivikram Kamat__](https://github.com/trivikr),
<https://twitter.com/trivikram>, <https://www.npmjs.com/~trivikr>
* [__Cemre Mengu__](https://github.com/cemremengu),
<https://twitter.com/cemremengu>, <https://www.npmjs.com/~cemremengu>
* [__Nathan Woltman__](https://github.com/nwoltman),
<https://twitter.com/NathanWoltman>, <https://www.npmjs.com/~nwoltman>
* [__Tommaso Allevi__](https://github.com/allevo),
<https://x.com/allevitommaso>, <https://www.npmjs.com/~allevo>
* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
<https://twitter.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
<https://x.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
* [__Çağatay Çalı__](https://github.com/cagataycali),
<https://x.com/cagataycali>, <https://www.npmjs.com/~cagataycali>
* [__David Mark Clements__](https://github.com/davidmarkclements),
<https://x.com/davidmarkclem>,
<https://www.npmjs.com/~davidmarkclements>
* [__dalisoft__](https://github.com/dalisoft), <https://x.com/dalisoft>,
<https://www.npmjs.com/~dalisoft>
* [__Dustin Deus__](https://github.com/StarpTech),
<https://x.com/dustindeus>, <https://www.npmjs.com/~starptech>
* [__Denis Fäcke__](https://github.com/SerayaEryn),
<https://x.com/serayaeryn>, <https://www.npmjs.com/~serayaeryn>
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
<https://x.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
* [__Trivikram Kamat__](https://github.com/trivikr),
<https://x.com/trivikram>, <https://www.npmjs.com/~trivikr>
* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
<https://x.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
* [__Cemre Mengu__](https://github.com/cemremengu),
<https://x.com/cemremengu>, <https://www.npmjs.com/~cemremengu>
* [__Salman Mitha__](https://github.com/salmanm),
<https://www.npmjs.com/~salmanm>
* [__Nathan Woltman__](https://github.com/nwoltman),
<https://x.com/NathanWoltman>, <https://www.npmjs.com/~nwoltman>
## Hosted by
@@ -381,7 +385,7 @@ to join this group by Lead Maintainers.
src="https://github.com/openjs-foundation/artwork/blob/main/openjs_foundation/openjs_foundation-logo-horizontal-color.png?raw=true"
width="250px;"/>](https://openjsf.org/projects)
We are a [At-Large
We are an [At-Large
Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#at-large-projects)
in the [OpenJS Foundation](https://openjsf.org/).
@@ -391,7 +395,7 @@ Support this project by becoming a [SPONSOR](./SPONSORS.md)!
Fastify has an [Open Collective](https://opencollective.com/fastify)
page where we accept and manage financial contributions.
## Acknowledgements
## Acknowledgments
This project is kindly sponsored by:
- [NearForm](https://nearform.com)
@@ -400,8 +404,8 @@ This project is kindly sponsored by:
Past Sponsors:
- [LetzDoIt](https://www.letzdoitapp.com/)
This list includes all companies that support one or more of the team members
in the maintenance of this project.
This list includes all companies that support one or more team members
in maintaining this project.
## License
@@ -413,3 +417,6 @@ dependencies:
- ISC
- BSD-3-Clause
- BSD-2-Clause
[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=github_readme
[lts-link]: https://fastify.dev/docs/latest/Reference/LTS/

View File

@@ -1,4 +1,210 @@
# Security Policy
Please see Fastify's [organization-wide security policy
](https://github.com/fastify/.github/blob/main/SECURITY.md).
This document describes the management of vulnerabilities for the Fastify
project and its official plugins.
## Threat Model
Fastify's threat model extends the
[Node.js threat model](https://github.com/nodejs/node/blob/main/SECURITY.md#the-nodejs-threat-model).
**Trusted:** Application code (plugins, handlers, hooks, schemas), configuration,
and the runtime environment.
**Untrusted:** All network input (HTTP headers, body, query strings, URL
parameters).
### Examples of Vulnerabilities
- Parsing flaws that bypass validation or security controls
- DoS through malformed input to Fastify's core
- Bypasses of built-in protections (prototype poisoning, schema validation)
### Examples of Non-Vulnerabilities
The following are **not** considered vulnerabilities in Fastify:
- **Application code vulnerabilities**: XSS, SQL injection, or other flaws in
user-written route handlers, hooks, or plugins
- **Malicious application code**: Issues caused by intentionally malicious
plugins or handlers (application code is trusted)
- **Validation schema issues**: Weak or incorrect schemas provided by developers
(schemas are trusted)
- **ReDoS in user patterns**: Regular expression DoS in user-provided regex
patterns for routes or validation
- **Missing security features**: Lack of rate limiting, authentication, or
authorization (these are application-level concerns)
- **Configuration mistakes**: Security issues arising from developer
misconfiguration (configuration is trusted)
- **Third-party dependencies**: Vulnerabilities in npm packages used by the
application (not Fastify core dependencies)
- **Resource exhaustion from handlers**: DoS caused by expensive operations in
user route handlers
- **Information disclosure by design**: Exposing error details or stack traces
explicitly enabled via configuration options
## Reporting vulnerabilities
Individuals who find potential vulnerabilities in Fastify are invited to
complete a vulnerability report via the dedicated pages:
1. [HackerOne](https://hackerone.com/fastify)
2. [GitHub Security Advisory](https://github.com/fastify/fastify/security/advisories/new)
### Strict measures when reporting vulnerabilities
It is of the utmost importance that you read carefully and follow these
guidelines to ensure the ecosystem as a whole isn't disrupted due to improperly
reported vulnerabilities:
* Avoid creating new "informative" reports. Only create new
reports on a vulnerability if you are absolutely sure this should be
tagged as an actual vulnerability. Third-party vendors and individuals are
tracking any new vulnerabilities reported in HackerOne or GitHub and will flag
them as such for their customers (think about snyk, npm audit, ...).
* Security reports should never be created and triaged by the same person. If
you are creating a report for a vulnerability that you found, or on
behalf of someone else, there should always be a 2nd Security Team member who
triages it. If in doubt, invite more Fastify Collaborators to help triage the
validity of the report. In any case, the report should follow the same process
as outlined below of inviting the maintainers to review and accept the
vulnerability.
* ***Do not*** attempt to show CI/CD vulnerabilities by creating new pull
requests to any of the Fastify organization's repositories. Doing so will
result in a [content report][cr] to GitHub as an unsolicited exploit.
The proper way to provide such reports is by creating a new repository,
configured in the same manner as the repository you would like to submit
a report about, and with a pull request to your own repository showing
the proof of concept.
[cr]: https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request
### Vulnerabilities found outside this process
⚠ The Fastify project does not support any reporting outside the process mentioned
in this document.
## Handling vulnerability reports
When a potential vulnerability is reported, the following actions are taken:
### Triage
**Delay:** 4 business days
Within 4 business days, a member of the security team provides a first answer to
the individual who submitted the potential vulnerability. The possible responses
can be:
* **Acceptance**: what was reported is considered as a new vulnerability
* **Rejection**: what was reported is not considered as a new vulnerability
* **Need more information**: the security team needs more information in order to
evaluate what was reported.
Triaging should include updating issue fields:
* Asset - set/create the module affected by the report
* Severity - TBD, currently left empty
Reference: [HackerOne: Submitting
Reports](https://docs.hackerone.com/hackers/submitting-reports.html)
### Correction follow-up
**Delay:** 90 days
When a vulnerability is confirmed, a member of the security team volunteers to
follow up on this report.
With the help of the individual who reported the vulnerability, they contact the
maintainers of the vulnerable package to make them aware of the vulnerability.
The maintainers can be invited as participants to the reported issue.
With the package maintainer, they define a release date for the publication of
the vulnerability. Ideally, this release date should not happen before the
package has been patched.
The report's vulnerable versions upper limit should be set to:
* `*` if there is no fixed version available by the time of publishing the
report.
* the last vulnerable version. For example: `<=1.2.3` if a fix exists in `1.2.4`
### Publication
**Delay:** 90 days
Within 90 days after the triage date, the vulnerability must be made public.
**Severity**: Vulnerability severity is assessed using [CVSS
v.3](https://www.first.org/cvss/user-guide). More information can be found on
[HackerOne documentation](https://docs.hackerone.com/hackers/severity.html)
If the package maintainer is actively developing a patch, an additional delay
can be added with the approval of the security team and the individual who
reported the vulnerability.
At this point, a CVE should be requested through the selected platform through
the UI, which should include the Report ID and a summary.
Within HackerOne, this is handled through a "public disclosure request".
Reference: [HackerOne:
Disclosure](https://docs.hackerone.com/hackers/disclosure.html)
### Secondary Contact
If you do not receive an acknowledgment of your report within 6 business days,
or if you cannot find a private security contact for the project, you may
contact the OpenJS Foundation CNA at `security@lists.openjsf.org` for
assistance.
The CNA can help ensure your report is properly acknowledged, assist with
coordinating disclosure timelines, and assign CVEs when necessary. This is a
support mechanism to ensure security reports are handled appropriately across
all OpenJS Foundation projects.
## The Fastify Security team
The core team is responsible for the management of the security program and
this policy and process.
Members of this team are expected to keep all information that they have
privileged access to by being on the team completely private to the team. This
includes agreeing to not notify anyone outside the team of issues that have not
yet been disclosed publicly, including the existence of issues, expectations of
upcoming releases, and patching of any issues other than in the process of their
work as a member of the Fastify Core team.
### Members
* [__Matteo Collina__](https://github.com/mcollina),
<https://x.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
* [__Tomas Della Vedova__](https://github.com/delvedor),
<https://x.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__KaKa Ng__](https://github.com/climba03003)
* [__James Sumners__](https://github.com/jsumners),
<https://x.com/jsumners79>, <https://www.npmjs.com/~jsumners>
## OpenSSF CII Best Practices
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585)
There are three “tiers”: passing, silver, and gold.
### Passing
We meet 100% of the “passing” criteria.
### Silver
We meet 87% of the "silver" criteria. The gaps are as follows:
- we do not have a DCO or a CLA process for contributions.
- we do not currently document "the architecture (aka high-level design)"
for our project.
### Gold
We meet 70% of the “gold” criteria. The gaps are as follows:
- we do not yet have the “silver” badge; see all the gaps above.
- We do not include a copyright or license statement in each source file.
Efforts are underway to change this archaic practice into a
suggestion instead of a hard requirement.
- There are a few unanswered questions around cryptography that are
waiting for clarification.

View File

@@ -9,13 +9,15 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)!
## Tier 4
_Be the first!_
- [SerpApi](http://serpapi.com/)
## Tier 3
- [Mercedes-Benz Group](https://github.com/mercedes-benz)
- [Val Town, Inc.](https://opencollective.com/valtown)
- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship)
- [Lambdatest](https://www.lambdatest.com/)
## Tier 2

View File

@@ -18,10 +18,12 @@ const code = FJS({
const file = path.join(__dirname, '..', 'lib', 'error-serializer.js')
const moduleCode = `// This file is autogenerated by build/build-error-serializer.js, do not edit
/* istanbul ignore file */
/* c8 ignore start */
${code}
/* c8 ignore stop */
`
/* c8 ignore start */
if (require.main === module) {
fs.writeFileSync(file, moduleCode)
console.log(`Saved ${file} file successfully`)
@@ -30,3 +32,4 @@ if (require.main === module) {
code: moduleCode
}
}
/* c8 ignore stop */

View File

@@ -8,14 +8,15 @@ const path = require('node:path')
const factory = AjvStandaloneCompiler({
readMode: false,
storeFunction (routeOpts, schemaValidationCode) {
const moduleCode = `// This file is autogenerated by ${__filename.replace(__dirname, 'build')}, do not edit
/* istanbul ignore file */
const moduleCode = `// This file is autogenerated by build/build-validation.js, do not edit
/* c8 ignore start */
${schemaValidationCode}
module.exports.defaultInitOptions = ${JSON.stringify(defaultInitOptions)}
/* c8 ignore stop */
`
const file = path.join(__dirname, '..', 'lib', 'configValidator.js')
const file = path.join(__dirname, '..', 'lib', 'config-validator.js')
fs.writeFileSync(file, moduleCode)
console.log(`Saved ${file} file successfully`)
}
@@ -31,18 +32,25 @@ const defaultInitOptions = {
caseSensitive: true,
allowUnsafeRegex: false,
disableRequestLogging: false,
jsonShorthand: true,
ignoreTrailingSlash: false,
ignoreDuplicateSlashes: false,
maxParamLength: 100,
onProtoPoisoning: 'error',
onConstructorPoisoning: 'error',
pluginTimeout: 10000,
requestIdHeader: 'request-id',
requestIdHeader: false,
requestIdLogLabel: 'reqId',
http2SessionTimeout: 72000, // 72 seconds
exposeHeadRoutes: true,
useSemicolonDelimiter: true
useSemicolonDelimiter: false,
allowErrorHandlerOverride: true, // TODO: set to false in v6
routerOptions: {
ignoreTrailingSlash: false,
ignoreDuplicateSlashes: false,
maxParamLength: 100,
allowUnsafeRegex: false,
useSemicolonDelimiter: false
}
}
const schema = {
@@ -90,27 +98,26 @@ const schema = {
ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.ignoreTrailingSlash },
ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.ignoreDuplicateSlashes },
disableRequestLogging: {
type: 'boolean',
default: false
},
jsonShorthand: { type: 'boolean', default: defaultInitOptions.jsonShorthand },
maxParamLength: { type: 'integer', default: defaultInitOptions.maxParamLength },
onProtoPoisoning: { type: 'string', default: defaultInitOptions.onProtoPoisoning },
onConstructorPoisoning: { type: 'string', default: defaultInitOptions.onConstructorPoisoning },
pluginTimeout: { type: 'integer', default: defaultInitOptions.pluginTimeout },
requestIdHeader: { anyOf: [{ enum: [false] }, { type: 'string' }], default: defaultInitOptions.requestIdHeader },
requestIdHeader: { anyOf: [{ type: 'boolean' }, { type: 'string' }], default: defaultInitOptions.requestIdHeader },
requestIdLogLabel: { type: 'string', default: defaultInitOptions.requestIdLogLabel },
http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout },
exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes },
useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter },
// deprecated style of passing the versioning constraint
versioning: {
routerOptions: {
type: 'object',
additionalProperties: true,
required: ['storage', 'deriveVersion'],
properties: {
storage: { },
deriveVersion: { }
ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreTrailingSlash },
ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreDuplicateSlashes },
maxParamLength: { type: 'integer', default: defaultInitOptions.routerOptions.maxParamLength },
allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.routerOptions.allowUnsafeRegex },
useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.routerOptions.useSemicolonDelimiter }
}
},
constraints: {
@@ -121,9 +128,9 @@ const schema = {
additionalProperties: true,
properties: {
name: { type: 'string' },
storage: { },
validate: { },
deriveConstraint: { }
storage: {},
validate: {},
deriveConstraint: {}
}
}
}

View File

@@ -1,18 +1,18 @@
<h1 align="center">Fastify</h1>
## Benchmarking
Benchmarking is important if you want to measure how a change can affect the
performance of your application. We provide a simple way to benchmark your
Benchmarking is important if you want to measure how a change can affect your
application's performance. We provide a simple way to benchmark your
application from the point of view of a user and contributor. The setup allows
you to automate benchmarks in different branches and on different Node.js
versions.
The modules we will use:
- [Autocannon](https://github.com/mcollina/autocannon): A HTTP/1.1 benchmarking
- [Autocannon](https://github.com/mcollina/autocannon): An HTTP/1.1 benchmarking
tool written in node.
- [Branch-comparer](https://github.com/StarpTech/branch-comparer): Checkout
multiple git branches, execute scripts and log the results.
- [Concurrently](https://github.com/kimmobrunfeldt/concurrently): Run commands
multiple git branches, execute scripts, and log the results.
- [Concurrently](https://github.com/open-cli-tools/concurrently): Run commands
concurrently.
- [Npx](https://github.com/npm/npx): NPM package runner used to run scripts
against different Node.js Versions and execute local binaries. Shipped with

View File

@@ -2,17 +2,17 @@
## Database
Fastify's ecosystem provides a handful of
plugins for connecting to various database engines.
This guide covers engines that have Fastify
Fastify's ecosystem provides a handful of
plugins for connecting to various database engines.
This guide covers engines that have Fastify
plugins maintained within the Fastify organization.
> If a plugin for your database of choice does not exist
> you can still use the database as Fastify is database agnostic.
> By following the examples of the database plugins listed in this guide,
> a plugin can be written for the missing database engine.
> If a plugin for your database of choice does not exist
> you can still use the database as Fastify is database agnostic.
> By following the examples of the database plugins listed in this guide,
> a plugin can be written for the missing database engine.
> If you would like to write your own Fastify plugin
> If you would like to write your own Fastify plugin
> please take a look at the [plugins guide](./Plugins-Guide.md)
### [MySQL](https://github.com/fastify/fastify-mysql)
@@ -104,8 +104,8 @@ fastify.listen({ port: 3000 }, err => {
})
```
By default `@fastify/redis` doesn't close
the client connection when Fastify server shuts down.
By default `@fastify/redis` doesn't close
the client connection when Fastify server shuts down.
To opt-in to this behavior, register the client like so:
```javascript
@@ -126,7 +126,7 @@ fastify.register(require('@fastify/mongodb'), {
// force to close the mongodb connection when app stopped
// the default value is false
forceClose: true,
url: 'mongodb://mongo/mydb'
})
@@ -178,8 +178,8 @@ fastify.listen({ port: 3000 }, err => {
```
### Writing plugin for a database library
We could write a plugin for a database
library too (e.g. Knex, Prisma, or TypeORM).
We could write a plugin for a database
library too (e.g. Knex, Prisma, or TypeORM).
We will use [Knex](https://knexjs.org/) in our example.
```javascript
@@ -245,7 +245,7 @@ for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check
#### [Postgrator](https://www.npmjs.com/package/postgrator)
Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to
alter the database schema. Each file an migrations folder need to follow the
alter the database schema. Each file in a migrations folder needs to follow the
pattern: ` [version].[action].[optional-description].sql`.
**version:** must be an incrementing number (e.g. `001` or a timestamp).
@@ -281,7 +281,7 @@ async function migrate() {
const client = new pg.Client({
host: 'localhost',
port: 5432,
database: 'example',
database: 'example',
user: 'example',
password: 'example',
});
@@ -313,7 +313,7 @@ async function migrate() {
console.error(err)
process.exitCode = 1
}
await client.end()
}

View File

@@ -77,8 +77,8 @@ server.get('/ping', function (request, reply) {
})
server.post('/webhook', function (request, reply) {
// It's good practice to validate webhook requests really come from
// whoever you expect. This is skipped in this sample for the sake
// It's good practice to validate webhook requests come from
// who you expect. This is skipped in this sample for the sake
// of simplicity
const { magicKey } = request.body
@@ -103,7 +103,7 @@ server.get('/v1*', async function (request, reply) {
}
})
server.decorate('magicKey', null)
server.decorate('magicKey')
server.listen({ port: '1234' }, () => {
provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS)
@@ -303,7 +303,7 @@ async function setup(fastify) {
fastify.server.on('listening', doMagic)
// Set up the placeholder for the magicKey
fastify.decorate('magicKey', null)
fastify.decorate('magicKey')
// Our magic -- important to make sure errors are handled. Beware of async
// functions outside `try/catch` blocks
@@ -406,7 +406,7 @@ https://nodejs.org/api/net.html#event-listening). We use that to reach out to
our provider as soon as possible, with the `doMagic` function.
```js
fastify.decorate('magicKey', null)
fastify.decorate('magicKey')
```
The `magicKey` decoration is also part of the plugin now. We initialize it with
@@ -448,10 +448,10 @@ have the possibility of giving the customer meaningful information, like how
long they should wait before retrying the request. Going even further, by
issuing a [`503` status
code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) we're
signaling to our infrastructure components (namely load balancers) we're still
not ready to take incoming requests and they should redirect traffic to other
instances, if available, besides in how long we estimate that will be solved.
All of that in a few simple lines!
signaling to our infrastructure components (namely load balancers) that we're
still not ready to take incoming requests and they should redirect traffic to
other instances, if available. Additionally, we are providing a `Retry-After`
header with the time in milliseconds the client should wait before retrying.
It's noteworthy that we didn't use the `fastify-plugin` wrapper in the `delay`
factory. That's because we wanted the `onRequest` hook to only be set within
@@ -524,14 +524,17 @@ Retry-After: 5000
}
```
Then we attempt a new request (`req-2`), which was a `GET /ping`. As expected,
Then we attempted a new request (`req-2`), which was a `GET /ping`. As expected,
since that was not one of the requests we asked our plugin to filter, it
succeeded. That could also be used as means of informing an interested party
whether or not we were ready to serve requests (although `/ping` is more
commonly associated with *liveness* checks and that would be the responsibility
of a *readiness* check -- the curious reader can get more info on these terms
[here](https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes))
with the `ready` field. Below is the response for that request:
succeeded. That could also be used as a means of informing an interested party
whether or not we were ready to serve requests with the `ready` field. Although
`/ping` is more commonly associated with *liveness* checks and that would be
the responsibility of a *readiness* check. The curious reader can get more info
on these terms in the article
["Kubernetes best practices: Setting up health checks with readiness and liveness probes"](
https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes).
Below is the response to that request:
```sh
HTTP/1.1 200 OK
@@ -547,7 +550,7 @@ Keep-Alive: timeout=5
}
```
After that there were more interesting log messages:
After that, there were more interesting log messages:
<!-- markdownlint-disable -->
```sh

View File

@@ -4,30 +4,30 @@
## Introduction
Fastify provides request events to trigger at certain points in a request's
lifecycle. However, there isn't a built-in mechanism to
detect unintentional client disconnection scenarios such as when the client's
Fastify provides request events to trigger at certain points in a request's
lifecycle. However, there isn't a built-in mechanism to
detect unintentional client disconnection scenarios such as when the client's
internet connection is interrupted. This guide covers methods to detect if
and when a client intentionally aborts a request.
Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a
client aborts a request. This works in the same way as the standard Node HTTP
module, which triggers the `clientError` event when there is a bad request or
exceedingly large header data. When a client aborts a request, there is no
Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a
client aborts a request. This works in the same way as the standard Node HTTP
module, which triggers the `clientError` event when there is a bad request or
exceedingly large header data. When a client aborts a request, there is no
error on the socket and the `clientErrorHandler` will not be triggered.
## Solution
### Overview
The proposed solution is a possible way of detecting when a client
intentionally aborts a request, such as when a browser is closed or the HTTP
request is aborted from your client application. If there is an error in your
application code that results in the server crashing, you may require
The proposed solution is a possible way of detecting when a client
intentionally aborts a request, such as when a browser is closed or the HTTP
request is aborted from your client application. If there is an error in your
application code that results in the server crashing, you may require
additional logic to avoid a false abort detection.
The goal here is to detect when a client intentionally aborts a connection
so your application logic can proceed accordingly. This can be useful for
The goal here is to detect when a client intentionally aborts a connection
so your application logic can proceed accordingly. This can be useful for
logging purposes or halting business logic.
### Hands-on
@@ -78,10 +78,10 @@ const start = async () => {
start()
```
Our code is setting up a Fastify server which includes the following
Our code is setting up a Fastify server which includes the following
functionality:
- Accepting requests at http://localhost:3000, with a 3 second delayed response
- Accepting requests at `http://localhost:3000`, with a 3 second delayed response
of `{ ok: true }`.
- An onRequest hook that triggers when every request is received.
- Logic that triggers in the hook when the request is closed.
@@ -108,7 +108,7 @@ app.get('/', async (request, reply) => {
})
```
At any point in your business logic, you can check if the request has been
At any point in your business logic, you can check if the request has been
aborted and perform alternative actions.
```js
@@ -122,14 +122,14 @@ app.get('/', async (request, reply) => {
})
```
A benefit to adding this in your application code is that you can log Fastify
details such as the reqId, which may be unavailable in lower-level code that
A benefit to adding this in your application code is that you can log Fastify
details such as the reqId, which may be unavailable in lower-level code that
only has access to the raw request information.
### Testing
To test this functionality you can use an app like Postman and cancel your
request within 3 seconds. Alternatively, you can use Node to send an HTTP
To test this functionality you can use an app like Postman and cancel your
request within 3 seconds. Alternatively, you can use Node to send an HTTP
request with logic to abort the request before 3 seconds. Example:
```js
@@ -151,7 +151,7 @@ setTimeout(() => {
}, 1000);
```
With either approach, you should see the Fastify log appear at the moment the
With either approach, you should see the Fastify log appear at the moment the
request is aborted.
## Conclusion
@@ -160,13 +160,13 @@ Specifics of the implementation will vary from one problem to another, but the
main goal of this guide was to show a very specific use case of an issue that
could be solved within Fastify's ecosystem.
You can listen to the request close event and determine if the request was
aborted or if it was successfully delivered. You can implement this solution
You can listen to the request close event and determine if the request was
aborted or if it was successfully delivered. You can implement this solution
in an onRequest hook or directly in an individual route.
This approach will not trigger in the event of internet disruption, and such
detection would require additional business logic. If you have flawed backend
application logic that results in a server crash, then you could trigger a
false detection. The `clientErrorHandler`, either by default or with custom
logic, is not intended to handle this scenario and will not trigger when the
This approach will not trigger in the event of internet disruption, and such
detection would require additional business logic. If you have flawed backend
application logic that results in a server crash, then you could trigger a
false detection. The `clientErrorHandler`, either by default or with custom
logic, is not intended to handle this scenario and will not trigger when the
client aborts a request.

View File

@@ -12,8 +12,6 @@ section.
[accepts](https://www.npmjs.com/package/accepts) in your request object.
- [`@fastify/accepts-serializer`](https://github.com/fastify/fastify-accepts-serializer)
to serialize to output according to the `Accept` header.
- [`@fastify/any-schema`](https://github.com/fastify/any-schema-you-like) Save
multiple schemas and decide which one to use to serialize the payload.
- [`@fastify/auth`](https://github.com/fastify/fastify-auth) Run multiple auth
functions in Fastify.
- [`@fastify/autoload`](https://github.com/fastify/fastify-autoload) Require all
@@ -42,11 +40,6 @@ section.
plugin for adding
[CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection to
Fastify.
- [`@fastify/diagnostics-channel`](https://github.com/fastify/fastify-diagnostics-channel)
Plugin to deal with `diagnostics_channel` on Fastify
- [`@fastify/early-hints`](https://github.com/fastify/fastify-early-hints) Plugin
to add HTTP 103 feature based on [RFC
8297](https://datatracker.ietf.org/doc/html/rfc8297).
- [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch)
Plugin to share the same ES client.
- [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check
@@ -91,6 +84,8 @@ section.
[`simple-oauth2`](https://github.com/lelylan/simple-oauth2).
- [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats
Fastify's logs into a nice one-line message.
- [`@fastify/otel`](https://github.com/fastify/otel) OpenTelemetry
instrumentation library.
- [`@fastify/passport`](https://github.com/fastify/fastify-passport) Use Passport
strategies to authenticate requests and protect route.
- [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify
@@ -124,8 +119,8 @@ section.
HTTP errors and assertions, but also more request and reply methods.
- [`@fastify/session`](https://github.com/fastify/session) a session plugin for
Fastify.
- [`@fastify/soap-client`](https://github.com/fastify/fastify-soap-client) a SOAP
client plugin for Fastify.
- [`@fastify/sse`](https://github.com/fastify/sse) Plugin for Server-Sent Events
(SSE) support in Fastify.
- [`@fastify/static`](https://github.com/fastify/fastify-static) Plugin for
serving static files as fast as possible.
- [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for
@@ -159,8 +154,22 @@ section.
#### [Community](#community)
> Note:
> Fastify community plugins are part of the broader community efforts,
> and we are thankful for these contributions. However, they are not
> maintained by the Fastify team.
> Use them at your own discretion.
> If you find malicious code, please
> [open an issue](https://github.com/fastify/fastify/issues/new/choose) or
> submit a PR to remove the plugin from the list.
- [`@aaroncadillac/crudify-mongo`](https://github.com/aaroncadillac/crudify-mongo)
A simple way to add a crud in your fastify project.
- [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs)
Alternate Fastify and Next.js integration.
- [`@attaryz/fastify-devtools`](https://github.com/attaryz/fastify-devtools)
Development tools plugin for Fastify with live request dashboard, replay
capabilities, and metrics tracking.
- [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache)
A plugin to help with caching API responses using AWS DynamoDB.
- [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify)
@@ -173,13 +182,12 @@ section.
close the server gracefully on `SIGINT` and `SIGTERM` signals.
- [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides
easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes.
- [`@ethicdevs/fastify-custom-session`](https://github.com/EthicDevs/fastify-custom-session)
A plugin lets you use session and decide only where to load/save from/to. Has
great TypeScript support + built-in adapters for common ORMs/databases (Firebase,
Prisma Client, Postgres (wip), InMemory) and you can easily make your own adapter!
- [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server)
A plugin to easily create git server and make one/many Git repositories available
for clone/fetch/push through the standard `git` (over http) commands.
- [`@exortek/fastify-mongo-sanitize`](https://github.com/ExorTek/fastify-mongo-sanitize)
A Fastify plugin that protects against No(n)SQL injection by sanitizing data.
- [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify)
Fastify plugin for Remix.
- [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id)
Fastify Request ID Plugin
- [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner)
@@ -191,14 +199,16 @@ section.
Run REST APIs and other web applications using your existing Node.js
application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda,
Huawei and many other clouds.
- [`@hey-api/openapi-ts`](https://heyapi.dev/openapi-ts/plugins/fastify)
The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more.
- [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics)
Minimalistic and opinionated plugin that collects usage/process metrics and
dispatches to [statsd](https://github.com/statsd/statsd).
- [`@immobiliarelabs/fastify-sentry`](https://github.com/immobiliare/fastify-sentry)
Sentry errors handler that just works! Install, add your DSN and you're good
to go!
A plugin to implement [Lyra](https://github.com/nearform/lyra) search engine
on Fastify
- [`@inaiat/fastify-papr`](https://github.com/inaiat/fastify-papr)
A plugin to integrate [Papr](https://github.com/plexinc/papr),
the MongoDB ORM for TypeScript & MongoDB, with Fastify.
- [`@jerome1337/fastify-enforce-routes-pattern`](https://github.com/Jerome1337/fastify-enforce-routes-pattern)
A Fastify plugin that enforces naming pattern for routes path.
- [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma)
A plugin for accessing an instantiated PrismaClient on your server.
- [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit)
@@ -213,13 +223,15 @@ section.
Fast sodium-based crypto for @mgcrea/fastify-session
- [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact)
A custom compact pino-base prettifier
- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/packages/fastify-api-reference)
- [`@pybot/fastify-autoload`](https://github.com/kunal097/fastify-autoload)
Plugin to generate routes automatically with valid json content
- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/integrations/fastify)
Beautiful OpenAPI/Swagger API references for Fastify
- [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs)
SeaweedFS for Fastify
- [`apitally`](https://github.com/apitally/nodejs-client) Fastify plugin to
integrate with [Apitally](https://apitally.io), a simple API monitoring &
API key management solution.
- [`apitally`](https://github.com/apitally/apitally-js) Fastify plugin to
integrate with [Apitally](https://apitally.io/fastify), an API analytics,
logging and monitoring tool.
- [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for
Kubernetes Liveness and Readiness Probes.
- [`aws-xray-sdk-fastify`](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify)
@@ -229,6 +241,9 @@ section.
request IDs into your logs.
- [`electron-server`](https://github.com/anonrig/electron-server) A plugin for
using Fastify without the need of consuming a port on Electron apps.
- [`elements-fastify`](https://github.com/rohitsoni007/elements-fastify) Fastify
Plugin for Stoplight Elements API Documentation using
openapi swagger json yml.
- [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for
waterline. Decorates Fastify with waterline models.
- [`fastify-204`](https://github.com/Shiva127/fastify-204) Fastify plugin that
@@ -252,7 +267,7 @@ section.
plugin to authenticate HTTP requests based on API key and signature
- [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify
Plugin for interacting with Appwrite server.
- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin
- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin
to access Fastify instance, logger, request and reply from Node.js [Async
Local Storage](https://nodejs.org/api/async_context.html#class-asynclocalstorage).
- [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify
@@ -280,7 +295,7 @@ section.
development servers that require Babel transformations of JavaScript sources.
- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
generator & checker.
- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3)
- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3)
Plugin for better-sqlite3.
- [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your
routes to the console, so you definitely know which endpoints are available.
@@ -292,7 +307,7 @@ section.
to add [bree](https://github.com/breejs/bree) support.
- [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin
to add support for [Bugsnag](https://www.bugsnag.com/) error reporting.
- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman)
- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman)
Small and efficient cache provider for Node.js with In-memory, File, Redis
and MongoDB engines for Fastify
- [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support
@@ -347,7 +362,7 @@ section.
- [`fastify-event-bus`](https://github.com/Shiva127/fastify-event-bus) Event bus
support for Fastify. Built upon [js-event-bus](https://github.com/bcerati/js-event-bus).
- [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify
plugin for instantiating and encapsulating the
plugin for instantiating and encapsulating the
[Evervault](https://evervault.com/) client.
- [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of
your decorators across all the encapsulated contexts.
@@ -356,7 +371,7 @@ section.
- [`fastify-feature-flags`](https://gitlab.com/m03geek/fastify-feature-flags)
Fastify feature flags plugin with multiple providers support (e.g. env,
[config](https://lorenwest.github.io/node-config/),
[unleash](https://unleash.github.io/)).
[unleash](https://github.com/Unleash/unleash)).
- [`fastify-file-routes`](https://github.com/spa5k/fastify-file-routes) Get
Next.js based file system routing into fastify.
- [`fastify-file-upload`](https://github.com/huangang/fastify-file-upload)
@@ -447,12 +462,16 @@ section.
middlewares into Fastify plugins
- [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify
Kubernetes client plugin.
- [`fastify-kysely`](https://github.com/alenap93/fastify-kysely) Fastify
plugin for supporting Kysely type-safe query builder.
- [`fastify-language-parser`](https://github.com/lependu/fastify-language-parser)
Fastify plugin to parse request language.
- [`fastify-lcache`](https://github.com/denbon05/fastify-lcache)
Lightweight cache plugin
- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes)
A simple plugin for Fastify to list all available routes.
- [`fastify-lm`](https://github.com/galiprandi/fastify-lm#readme)
Use OpenAI, Claude, Google, Deepseek, and others LMs with one Fastify plugin.
- [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from
a directory and inject the Fastify instance in each file.
- [`fastify-log-controller`](https://github.com/Eomm/fastify-log-controller/)
@@ -493,6 +512,8 @@ middlewares into Fastify plugins
[MS Graph Change Notifications webhooks](https://learn.microsoft.com/it-it/graph/change-notifications-delivery-webhooks?tabs=http).
- [`fastify-multer`](https://github.com/fox1t/fastify-multer) Multer is a plugin
for handling multipart/form-data, which is primarily used for uploading files.
- [`fastify-multilingual`](https://github.com/gbrugger/fastify-multilingual) Unobtrusively
decorates fastify request with Polyglot.js for i18n.
- [`fastify-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share
[NATS](https://nats.io) client across Fastify.
- [`fastify-next-auth`](https://github.com/wobsoriano/fastify-next-auth)
@@ -501,10 +522,7 @@ middlewares into Fastify plugins
Add `additionalProperties: false` by default to your JSON Schemas.
- [`fastify-no-icon`](https://github.com/jsumners/fastify-no-icon) Plugin to
eliminate thrown errors for `/favicon.ico` requests.
- [`fastify-normalize-request-reply`](https://github.com/ericrglass/fastify-normalize-request-reply)
Plugin to normalize the request and reply to the Express version 4.x request
and response, which allows use of middleware, like swagger-stats, that was
originally written for Express.
- [`fastify-now`](https://github.com/yonathan06/fastify-now) Structure your
endpoints in a folder and load them dynamically with Fastify.
- [`fastify-nuxtjs`](https://github.com/gomah/fastify-nuxtjs) Vue server-side
@@ -539,12 +557,17 @@ middlewares into Fastify plugins
OSM plugin to run overpass queries by OpenStreetMap.
- [`fastify-override`](https://github.com/matthyk/fastify-override)
Fastify plugin to override decorators, plugins and hooks for testing purposes
- [`fastify-passkit-webservice`](https://github.com/alexandercerutti/fastify-passkit-webservice)
A set of Fastify plugins to integrate Apple Wallet Web Service specification
- [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo)
Fastify plugin for memoize responses by expressive settings.
- [`fastify-permissions`](https://github.com/pckrishnadas88/fastify-permissions)
Route-level permission middleware for Fastify supports
custom permission checks.
- [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to
handle i18n using
- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin
to handle i18n using
[node-polyglot](https://www.npmjs.com/package/node-polyglot).
- [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile)
Plugin to integrate [PostGraphile](https://www.graphile.org/postgraphile/) in
@@ -600,6 +623,9 @@ middlewares into Fastify plugins
Fastify Rob-Config integration.
- [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group)
Convenient grouping and inheritance of routes.
- [`fastify-route-preset`](https://github.com/inyourtime/fastify-route-preset)
A Fastify plugin that enables you to create route configurations that can be
applied to multiple routes.
- [`fastify-s3-buckets`](https://github.com/kibertoad/fastify-s3-buckets)
Ensure the existence of defined S3 buckets on the application startup.
- [`fastify-schema-constraint`](https://github.com/Eomm/fastify-schema-constraint)
@@ -615,6 +641,8 @@ middlewares into Fastify plugins
- [`fastify-server-session`](https://github.com/jsumners/fastify-server-session)
A session plugin with support for arbitrary backing caches via
`fastify-caching`.
- [`fastify-ses-mailer`](https://github.com/KaranHotwani/fastify-ses-mailer) A
Fastify plugin for sending emails via AWS SES using AWS SDK v3.
- [`fastify-shared-schema`](https://github.com/Adibla/fastify-shared-schema) Plugin
for sharing schemas between different routes.
- [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik
@@ -656,7 +684,7 @@ middlewares into Fastify plugins
- [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema)
Fastify
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
for [@effect/schema](https://github.com/effect-ts/schema).
for [@effect/schema](https://github.com/Effect-TS/effect).
- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
Fastify
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
@@ -718,6 +746,7 @@ middlewares into Fastify plugins
- [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
and updated Typeorm plugin for use with Fastify.
#### [Community Tools](#community-tools)
- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
@@ -726,9 +755,13 @@ middlewares into Fastify plugins
generator by directory structure.
- [`fastify-flux`](https://github.com/Jnig/fastify-flux) Tool for building
Fastify APIs using decorators and convert Typescript interface to JSON Schema.
- [`jeasx`](https://www.jeasx.dev)
A flexible server-rendering framework built on Fastify
that leverages asynchronous JSX to simplify web development.
- [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to
generate JSON Schema from TypeScript interfaces.
- [`vite-plugin-fastify`](https://github.com/Vanilla-IceCream/vite-plugin-fastify)
Fastify plugin for Vite with Hot-module Replacement.
- [`vite-plugin-fastify-routes`](https://github.com/Vanilla-IceCream/vite-plugin-fastify-routes)
File-based routing for Fastify applications using Vite.

View File

@@ -55,7 +55,7 @@ fastify.post('/the/url', { schema }, handler)
### Reuse
With `fluent-json-schema` you can manipulate your schemas more easily and
With `fluent-json-schema`, you can manipulate your schemas more easily and
programmatically and then reuse them thanks to the `addSchema()` method. You can
refer to the schema in two different manners that are detailed in the
[Validation and
@@ -122,5 +122,5 @@ const schema = { body: bodyJsonSchema }
fastify.post('/the/url', { schema }, handler)
```
NB You can mix up the `$ref-way` and the `replace-way` when using
`fastify.addSchema`.
> Note: You can mix up the `$ref-way` and the `replace-way`
> when using `fastify.addSchema`.

View File

@@ -106,7 +106,7 @@ of your code.
Fastify offers an easy platform that helps to solve all of the problems outlined
above, and more!
> ## Note
> **Note**
> The above examples, and subsequent examples in this document, default to
> listening *only* on the localhost `127.0.0.1` interface. To listen on all
> available IPv4 interfaces the example should be modified to listen on
@@ -128,6 +128,9 @@ above, and more!
>
> When deploying to a Docker (or another type of) container using `0.0.0.0` or
> `::` would be the easiest method for exposing the application.
>
> Note that when using `0.0.0.0`, the address provided in the callback argument
> above will be the first address the wildcard refers to.
### Your first plugin
<a id="first-plugin"></a>
@@ -143,7 +146,7 @@ declaration](../Reference/Routes.md) docs).
```js
// ESM
import Fastify from 'fastify'
import firstRoute from './our-first-route'
import firstRoute from './our-first-route.js'
/**
* @type {import('fastify').FastifyInstance} Instance of Fastify
*/
@@ -232,8 +235,8 @@ npm i fastify-plugin @fastify/mongodb
```js
// ESM
import Fastify from 'fastify'
import dbConnector from './our-db-connector'
import firstRoute from './our-first-route'
import dbConnector from './our-db-connector.js'
import firstRoute from './our-first-route.js'
/**
* @type {import('fastify').FastifyInstance} Instance of Fastify
@@ -417,7 +420,7 @@ In this way, you will always have access to all of the properties declared in
the current scope.
As discussed previously, Fastify offers a solid encapsulation model, to help you
build your application as single and independent services. If you want to
build your application as independent services. If you want to
register a plugin only for a subset of routes, you just have to replicate the
above structure.
```
@@ -450,8 +453,6 @@ Data validation is extremely important and a core concept of the framework.
To validate incoming requests, Fastify uses [JSON
Schema](https://json-schema.org/).
(JTD schemas are loosely supported, but `jsonShorthand` must be disabled first)
Let's look at an example demonstrating validation for routes:
```js
/**
@@ -554,15 +555,16 @@ an amazing [ecosystem](./Ecosystem.md)!
<a id="test-server"></a>
Fastify does not offer a testing framework, but we do recommend a way to write
your tests that use the features and architecture of Fastify.
your tests that uses the features and architecture of Fastify.
Read the [testing](./Testing.md) documentation to learn more!
### Run your server from CLI
<a id="cli"></a>
Fastify also has CLI integration thanks to
[fastify-cli](https://github.com/fastify/fastify-cli).
Fastify also has CLI integration via
[fastify-cli](https://github.com/fastify/fastify-cli),
a separate tool for scaffolding and managing Fastify projects.
First, install `fastify-cli`:

View File

@@ -15,11 +15,11 @@ This table of contents is in alphabetical order.
met in your application. This guide focuses on solving the problem using
[`Hooks`](../Reference/Hooks.md), [`Decorators`](../Reference/Decorators.md),
and [`Plugins`](../Reference/Plugins.md).
+ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A
+ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A
practical guide on detecting if and when a client aborts a request.
+ [Ecosystem](./Ecosystem.md): Lists all core plugins and many known community
plugins.
+ [Fluent Schema](./Fluent-Schema.md): Shows how writing JSON Schema can be
+ [Fluent Schema](./Fluent-Schema.md): Shows how JSON Schema can be
written with a fluent API and used in Fastify.
+ [Getting Started](./Getting-Started.md): Introduction tutorial for Fastify.
This is where beginners should start.

View File

@@ -9,13 +9,13 @@ work after upgrading.
## Codemods
### Fastify v4 Codemods
To help with the upgrade, weve worked with the team at
To help with the upgrade, weve worked with the team at
[Codemod](https://github.com/codemod-com/codemod) to
publish codemods that will automatically update your code to many of
publish codemods that will automatically update your code to many of
the new APIs and patterns in Fastify v4.
Run the following
[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to
Run the following
[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to
automatically update your code to Fastify v4:
```
@@ -30,7 +30,7 @@ This will run the following codemods:
- [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls)
Each of these codemods automates the changes listed in the v4 migration guide.
For a complete list of available Fastify codemods and further details,
For a complete list of available Fastify codemods and further details,
see [Codemod Registry](https://go.codemod.com/fastify).
@@ -52,14 +52,14 @@ fastify.register(async fastify => {
console.log(err.message) // 'kaboom'
throw new Error('caught')
})
fastify.get('/encapsulated', async () => {
throw new Error('kaboom')
})
})
fastify.setErrorHandler(async err => {
console.log(err.message) // 'caught'
console.log(err.message) // 'caught'
throw new Error('wrapped')
})
@@ -67,10 +67,10 @@ const res = await fastify.inject('/encapsulated')
console.log(res.json().message) // 'wrapped'
```
>The root error handler is Fastifys generic error handler.
>This error handler will use the headers and status code in the Error object,
>The root error handler is Fastifys generic error handler.
>This error handler will use the headers and status code in the Error object,
>if they exist. **The headers and status code will not be automatically set if
>a custom error handler is provided**.
>a custom error handler is provided**.
### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506))
@@ -109,7 +109,8 @@ argument from your router handler.
### `exposeHeadRoutes` true by default
Starting with v4, every `GET` route will create a sibling `HEAD` route.
You can revert this behavior by setting `exposeHeadRoutes: false` in the server options.
You can revert this behavior by setting `exposeHeadRoutes: false` in the server
options.
### Synchronous route definitions ([#2954](https://github.com/fastify/fastify/pull/2954))
@@ -210,7 +211,7 @@ fastify.get('/posts/:id?', (request, reply) => {
The [variadic signature](https://en.wikipedia.org/wiki/Variadic_function) of the
`fastify.listen()` method is now deprecated.
Prior to this release, the following invocations of this method were valid:
Before this release, the following invocations of this method were valid:
- `fastify.listen(8000)`
- `fastify.listen(8000, 127.0.0.1)`
@@ -242,7 +243,7 @@ As such, schemas like below will need to be changed from:
properties: {
api_key: { type: 'string' },
image: { type: ['object', 'array'] }
}
}
}
```

View File

@@ -0,0 +1,727 @@
# V5 Migration Guide
This guide is intended to help with migration from Fastify v4 to v5.
Before migrating to v5, please ensure that you have fixed all deprecation
warnings from v4. All v4 deprecations have been removed and will no longer
work after upgrading.
## Long Term Support Cycle
Fastify v5 will only support Node.js v20+. If you are using an older version of
Node.js, you will need to upgrade to a newer version to use Fastify v5.
Fastify v4 is still supported until June 30, 2025. If you are unable to upgrade,
you should consider buying an end-of-life support plan from HeroDevs.
### Why Node.js v20?
Fastify v5 will only support Node.js v20+ because it has significant differences
compared to v18, such as
better support for `node:test`. This allows us to provide a better developer
experience and streamline maintenance.
Node.js v18 will exit Long Term Support on April 30, 2025, so you should be planning
to upgrade to v20 anyway.
## Breaking Changes
### Full JSON Schema is now required for `querystring`, `params` and `body` and response schemas
Starting with v5, Fastify will require a full JSON schema for the `querystring`,
`params` and `body` schema. Note that the `jsonShortHand` option has been
removed as well.
If the default JSON Schema validator is used, you will need
to provide a full JSON schema for the
`querystring`, `params`, `body`, and `response` schemas,
including the `type` property.
```js
// v4
fastify.get('/route', {
schema: {
querystring: {
name: { type: 'string' }
}
}
}, (req, reply) => {
reply.send({ hello: req.query.name });
});
```
```js
// v5
fastify.get('/route', {
schema: {
querystring: {
type: 'object',
properties: {
name: { type: 'string' }
},
required: ['name']
}
}
}, (req, reply) => {
reply.send({ hello: req.query.name });
});
```
See [#5586](https://github.com/fastify/fastify/pull/5586) for more details
Note that it's still possible to override the JSON Schema validator to
use a different format, such as Zod. This change simplifies that as well.
This change helps with integration of other tools, such as
[`@fastify/swagger`](https://github.com/fastify/fastify-swagger).
### New logger constructor signature
In Fastify v4, Fastify accepted the options to build a pino
logger in the `logger` option, as well as a custom logger instance.
This was the source of significant confusion.
As a result, the `logger` option will not accept a custom logger anymore in v5.
To use a custom logger, you should use the `loggerInstance` option instead:
```js
// v4
const logger = require('pino')();
const fastify = require('fastify')({
logger
});
```
```js
// v5
const loggerInstance = require('pino')();
const fastify = require('fastify')({
loggerInstance
});
```
### `useSemicolonDelimiter` false by default
Starting with v5, Fastify instances will no longer default to supporting the use
of semicolon delimiters in the query string as they did in v4.
This is due to it being non-standard
behavior and not adhering to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.4).
If you still wish to use semicolons as delimiters, you can do so by
setting `useSemicolonDelimiter: true` in the server configuration.
```js
const fastify = require('fastify')({
useSemicolonDelimiter: true
});
```
### The parameters object no longer has a prototype
In v4, the `parameters` object had a prototype. This is no longer the case in v5.
This means that you can no longer access properties inherited from `Object` on
the `parameters` object, such as `toString` or `hasOwnProperty`.
```js
// v4
fastify.get('/route/:name', (req, reply) => {
console.log(req.params.hasOwnProperty('name')); // true
return { hello: req.params.name };
});
```
```js
// v5
fastify.get('/route/:name', (req, reply) => {
console.log(Object.hasOwn(req.params, 'name')); // true
return { hello: req.params.name };
});
```
This increases the security of the application by hardening against prototype
pollution attacks.
### Type Providers now differentiate between validator and serializer schemas
In v4, the type providers had the same types for both validation and serialization.
In v5, the type providers have been split into two separate types: `ValidatorSchema`
and `SerializerSchema`.
[`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
and
[`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
have already been updated: upgrade to the latest version to get the new types.
If you are using a custom type provider, you will need to modify it like
the following:
```
--- a/index.ts
+++ b/index.ts
@@ -11,7 +11,8 @@ import {
import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts'
export interface JsonSchemaToTsProvider<
Options extends FromSchemaOptions = FromSchemaDefaultOptions
> extends FastifyTypeProvider {
- output: this['input'] extends JSONSchema ? FromSchema<this['input'], Options> : unknown;
+ validator: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
+ serializer: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
}
```
### Changes to the .listen() method
The variadic argument signature of the `.listen()` method has been removed.
This means that you can no longer call `.listen()` with a variable number of arguments.
```js
// v4
fastify.listen(8000)
```
Will become:
```js
// v5
fastify.listen({ port: 8000 })
```
This was already deprecated in v4 as `FSTDEP011`, so you should have already updated
your code to use the new signature.
### Direct return of trailers has been removed
In v4, you could directly return trailers from a handler.
This is no longer possible in v5.
```js
// v4
fastify.get('/route', (req, reply) => {
reply.trailer('ETag', function (reply, payload) {
return 'custom-etag'
})
reply.send('')
});
```
```js
// v5
fastify.get('/route', (req, reply) => {
reply.trailer('ETag', async function (reply, payload) {
return 'custom-etag'
})
reply.send('')
});
```
A callback could also be used.
This was already deprecated in v4 as `FSTDEP013`,
so you should have already updated your code to use the new signature.
### Streamlined access to route definition
All deprecated properties relating to accessing the route definition have been removed
and are now accessed via `request.routeOptions`.
| Code | Description | How to solve | Discussion |
| ---- | ----------- | ------------ | ---------- |
| FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) |
| FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| FSTDEP016 | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) |
See [#5616](https://github.com/fastify/fastify/pull/5616) for more information.
### `reply.redirect()` has a new signature
The `reply.redirect()` method has a new signature:
`reply.redirect(url: string, code?: number)`.
```js
// v4
reply.redirect(301, '/new-route')
```
Change it to:
```js
// v5
reply.redirect('/new-route', 301)
```
This was already deprecated in v4 as `FSTDEP021`, so you should have already
updated your code to use the new signature.
### Modifying `reply.sent` is now forbidden
In v4, you could modify the `reply.sent` property to prevent the response from
being sent.
This is no longer possible in v5, use `reply.hijack()` instead.
```js
// v4
fastify.get('/route', (req, reply) => {
reply.sent = true;
reply.raw.end('hello');
});
```
Change it to:
```js
// v5
fastify.get('/route', (req, reply) => {
reply.hijack();
reply.raw.end('hello');
});
```
This was already deprecated in v4 as `FSTDEP010`, so you should have already
updated your code to use the new signature.
### Constraints for route versioning signature changes
We changed the signature for route versioning constraints.
The `version` and `versioning` options have been removed and you should
use the `constraints` option instead.
| Code | Description | How to solve | Discussion |
| ---- | ----------- | ------------ | ---------- |
| FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
| FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
### `HEAD` routes requires to register before `GET` when `exposeHeadRoutes: true`
We have a more strict requirement for custom `HEAD` route when
`exposeHeadRoutes: true`.
When you provides a custom `HEAD` route, you must either explicitly
set `exposeHeadRoutes` to `false`
```js
// v4
fastify.get('/route', {
}, (req, reply) => {
reply.send({ hello: 'world' });
});
fastify.head('/route', (req, reply) => {
// ...
});
```
```js
// v5
fastify.get('/route', {
exposeHeadRoutes: false
}, (req, reply) => {
reply.send({ hello: 'world' });
});
fastify.head('/route', (req, reply) => {
// ...
});
```
or place the `HEAD` route before `GET`.
```js
// v5
fastify.head('/route', (req, reply) => {
// ...
});
fastify.get('/route', {
}, (req, reply) => {
reply.send({ hello: 'world' });
});
```
This was changed in [#2700](https://github.com/fastify/fastify/pull/2700),
and the old behavior was deprecated in v4 as `FSTDEP007`.
### Removed `request.connection`
The `request.connection` property has been removed in v5.
You should use `request.socket` instead.
```js
// v4
fastify.get('/route', (req, reply) => {
console.log(req.connection.remoteAddress);
return { hello: 'world' };
});
```
```js
// v5
fastify.get('/route', (req, reply) => {
console.log(req.socket.remoteAddress);
return { hello: 'world' };
});
```
This was already deprecated in v4 as `FSTDEP05`, so you should
have already updated your code to use the new signature.
### `reply.getResponseTime()` has been removed, use `reply.elapsedTime` instead
The `reply.getResponseTime()` method has been removed in v5.
You should use `reply.elapsedTime` instead.
```js
// v4
fastify.get('/route', (req, reply) => {
console.log(reply.getResponseTime());
return { hello: 'world' };
});
```
```js
// v5
fastify.get('/route', (req, reply) => {
console.log(reply.elapsedTime);
return { hello: 'world' };
});
```
This was already deprecated in v4 as `FSTDEP20`, so you should have already
updated your code to use the new signature.
### `fastify.hasRoute()` now matches the behavior of `find-my-way`
The `fastify.hasRoute()` method now matches the behavior of `find-my-way`
and requires the route definition to be passed as it is defined in the route.
```js
// v4
fastify.get('/example/:file(^\\d+).png', function (request, reply) { })
console.log(fastify.hasRoute({
method: 'GET',
url: '/example/12345.png'
)); // true
```
```js
// v5
fastify.get('/example/:file(^\\d+).png', function (request, reply) { })
console.log(fastify.hasRoute({
method: 'GET',
url: '/example/:file(^\\d+).png'
)); // true
```
### Removal of some non-standard HTTP methods
We have removed the following HTTP methods from Fastify:
- `PROPFIND`
- `PROPPATCH`
- `MKCOL`
- `COPY`
- `MOVE`
- `LOCK`
- `UNLOCK`
- `TRACE`
- `SEARCH`
It's now possible to add them back using the `addHttpMethod` method.
```js
const fastify = Fastify()
// add a new http method on top of the default ones:
fastify.addHttpMethod('REBIND')
// add a new HTTP method that accepts a body:
fastify.addHttpMethod('REBIND', { hasBody: true })
// reads the HTTP methods list:
fastify.supportedMethods // returns a string array
```
See [#5567](https://github.com/fastify/fastify/pull/5567) for more
information.
### Removed support from reference types in decorators
Decorating Request/Reply with a reference type (`Array`, `Object`)
is now prohibited as this reference is shared amongst all requests.
```js
// v4
fastify.decorateRequest('myObject', { hello: 'world' });
```
```js
// v5
fastify.decorateRequest('myObject');
fastify.addHook('onRequest', async (req, reply) => {
req.myObject = { hello: 'world' };
});
```
or turn it into a function
```js
// v5
fastify.decorateRequest('myObject', () => ({ hello: 'world' }));
```
or as a getter
```js
// v5
fastify.decorateRequest('myObject', {
getter () {
return { hello: 'world' }
}
});
```
See [#5462](https://github.com/fastify/fastify/pull/5462) for more information.
### Remove support for DELETE with a `Content-Type: application/json` header and an empty body
In v4, Fastify allowed `DELETE` requests with a `Content-Type: application/json`
header and an empty body was accepted.
This is no longer allowed in v5.
See [#5419](https://github.com/fastify/fastify/pull/5419) for more information.
### Plugins cannot mix callback/promise API anymore
In v4, plugins could mix the callback and promise API, leading to unexpected behavior.
This is no longer allowed in v5.
```js
// v4
fastify.register(async function (instance, opts, done) {
done();
});
```
```js
// v5
fastify.register(async function (instance, opts) {
return;
});
```
or
```js
// v5
fastify.register(function (instance, opts, done) {
done();
});
```
### Requests now have `host`, `hostname`, and `port`, and `hostname` no longer includes the port number
In Fastify v4, `req.hostname` would include both the hostname and the
servers port, so locally it might have the value `localhost:1234`.
With v5, we aligned to the Node.js URL object and now include `host`, `hostname`,
and `port` properties. `req.host` has the same value as `req.hostname` did in v4,
while `req.hostname` includes the hostname _without_ a port if a port is present,
and `req.port` contains just the port number.
See [#4766](https://github.com/fastify/fastify/pull/4766)
and [#4682](https://github.com/fastify/fastify/issues/4682) for more information.
### Removes `getDefaultRoute` and `setDefaultRoute` methods
The `getDefaultRoute` and `setDefaultRoute` methods have been removed in v5.
See [#4485](https://github.com/fastify/fastify/pull/4485)
and [#4480](https://github.com/fastify/fastify/pull/4485)
for more information.
This was already deprecated in v4 as `FSTDEP014`,
so you should have already updated your code.
### `time` and `date-time` formats enforce timezone
The updated AJV compiler updates `ajv-formats` which now
enforce the use of timezone in `time` and `date-time` format.
A workaround is to use `iso-time` and `iso-date-time` formats
which support an optional timezone for backwards compatibility.
See the
[full discussion](https://github.com/fastify/fluent-json-schema/issues/267).
## New Features
### Diagnostic Channel support
Fastify v5 now supports the [Diagnostics Channel](https://nodejs.org/api/diagnostics_channel.html)
API natively
and provides a way to trace the lifecycle of a request.
```js
'use strict'
const diagnostics = require('node:diagnostics_channel')
const Fastify = require('fastify')
diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => {
console.log(msg.route.url) // '/:id'
console.log(msg.route.method) // 'GET'
})
diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => {
// msg is the same as the one emitted by the 'tracing:fastify.request.handler:start' channel
console.log(msg)
})
diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
// in case of error
})
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/:id',
handler: function (req, reply) {
return { hello: 'world' }
}
})
fastify.listen({ port: 0 }, async function () {
const result = await fetch(fastify.listeningOrigin + '/7')
t.assert.ok(result.ok)
t.assert.strictEqual(response.status, 200)
t.assert.deepStrictEqual(await result.json(), { hello: 'world' })
})
```
See the [documentation](https://github.com/fastify/fastify/blob/main/docs/Reference/Hooks.md#diagnostics-channel-hooks)
and [#5252](https://github.com/fastify/fastify/pull/5252) for additional details.
## Contributors
The complete list of contributors, across all of the core
Fastify packages, is provided below. Please consider
contributing to those that are capable of accepting sponsorships.
| Contributor | Sponsor Link | Packages |
| --- | --- | --- |
| 10xLaCroixDrinker | [❤️ sponsor](https://github.com/sponsors/10xLaCroixDrinker) | fastify-cli |
| Bram-dc | | fastify; fastify-swagger |
| BrianValente | | fastify |
| BryanAbate | | fastify-cli |
| Cadienvan | [❤️ sponsor](https://github.com/sponsors/Cadienvan) | fastify |
| Cangit | | fastify |
| Cyberlane | | fastify-elasticsearch |
| Eomm | [❤️ sponsor](https://github.com/sponsors/Eomm) | ajv-compiler; fastify; fastify-awilix; fastify-diagnostics-channel; fastify-elasticsearch; fastify-hotwire; fastify-mongodb; fastify-nextjs; fastify-swagger-ui; under-pressure |
| EstebanDalelR | [❤️ sponsor](https://github.com/sponsors/EstebanDalelR) | fastify-cli |
| Fdawgs | [❤️ sponsor](https://github.com/sponsors/Fdawgs) | aws-lambda-fastify; csrf-protection; env-schema; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-hotwire; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-mongodb; fastify-multipart; fastify-mysql; fastify-nextjs; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; middie; point-of-view; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure |
| Gehbt | | fastify-secure-session |
| Gesma94 | | fastify-routes-stats |
| H4ad | [❤️ sponsor](https://github.com/sponsors/H4ad) | aws-lambda-fastify |
| JohanManders | | fastify-secure-session |
| LiviaMedeiros | | fastify |
| Momy93 | | fastify-secure-session |
| MunifTanjim | | fastify-swagger-ui |
| Nanosync | | fastify-secure-session |
| RafaelGSS | [❤️ sponsor](https://github.com/sponsors/RafaelGSS) | fastify; under-pressure |
| Rantoledo | | fastify |
| SMNBLMRR | | fastify |
| SimoneDevkt | | fastify-cli |
| Tony133 | | fastify |
| Uzlopak | [❤️ sponsor](https://github.com/sponsors/Uzlopak) | fastify; fastify-autoload; fastify-diagnostics-channel; fastify-hotwire; fastify-nextjs; fastify-passport; fastify-plugin; fastify-rate-limit; fastify-routes; fastify-static; fastify-swagger-ui; point-of-view; under-pressure |
| Zamiell | | fastify-secure-session |
| aadito123 | | fastify |
| aaroncadillac | [❤️ sponsor](https://github.com/sponsors/aaroncadillac) | fastify |
| aarontravass | | fastify |
| acro5piano | [❤️ sponsor](https://github.com/sponsors/acro5piano) | fastify-secure-session |
| adamward459 | | fastify-cli |
| adrai | [❤️ sponsor](https://github.com/sponsors/adrai) | aws-lambda-fastify |
| alenap93 | | fastify |
| alexandrucancescu | | fastify-nextjs |
| anthonyringoet | | aws-lambda-fastify |
| arshcodemod | | fastify |
| autopulated | | point-of-view |
| barbieri | | fastify |
| beyazit | | fastify |
| big-kahuna-burger | [❤️ sponsor](https://github.com/sponsors/big-kahuna-burger) | fastify-cli; fastify-compress; fastify-helmet |
| bilalshareef | | fastify-routes |
| blue86321 | | fastify-swagger-ui |
| bodinsamuel | | fastify-rate-limit |
| busybox11 | [❤️ sponsor](https://github.com/sponsors/busybox11) | fastify |
| climba03003 | | csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cors; fastify-env; fastify-etag; fastify-flash; fastify-formbody; fastify-http-proxy; fastify-mongodb; fastify-swagger-ui; fastify-url-data; fastify-websocket; middie |
| dancastillo | [❤️ sponsor](https://github.com/sponsors/dancastillo) | fastify; fastify-basic-auth; fastify-caching; fastify-circuit-breaker; fastify-cors; fastify-helmet; fastify-passport; fastify-response-validation; fastify-routes; fastify-schedule |
| danny-andrews | | fastify-kafka |
| davidcralph | [❤️ sponsor](https://github.com/sponsors/davidcralph) | csrf-protection |
| davideroffo | | under-pressure |
| dhensby | | fastify-cli |
| dmkng | | fastify |
| domdomegg | | fastify |
| faustman | | fastify-cli |
| floridemai | | fluent-json-schema |
| fox1t | | fastify-autoload |
| giuliowaitforitdavide | | fastify |
| gunters63 | | fastify-reply-from |
| gurgunday | | fastify; fastify-circuit-breaker; fastify-cookie; fastify-multipart; fastify-mysql; fastify-rate-limit; fastify-response-validation; fastify-sensible; fastify-swagger-ui; fluent-json-schema; middie; proxy-addr; safe-regex2; secure-json-parse |
| ildella | | under-pressure |
| james-kaguru | | fastify |
| jcbain | | fastify-http-proxy |
| jdhollander | | fastify-swagger-ui |
| jean-michelet | | fastify; fastify-autoload; fastify-cli; fastify-mysql; fastify-sensible |
| johaven | | fastify-multipart |
| jordanebelanger | | fastify-plugin |
| jscheffner | | fastify |
| jsprw | | fastify-secure-session |
| jsumners | [❤️ sponsor](https://github.com/sponsors/jsumners) | ajv-compiler; avvio; csrf-protection; env-schema; fast-json-stringify; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cookie; fastify-cors; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-plugin; fastify-postgres; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; light-my-request; middie; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure |
| karankraina | | under-pressure |
| kerolloz | [❤️ sponsor](https://github.com/sponsors/kerolloz) | fastify-jwt |
| kibertoad | | fastify-rate-limit |
| kukidon-dev | | fastify-passport |
| kunal097 | | fastify |
| lamweili | | fastify-sensible |
| lemonclown | | fastify-mongodb |
| liuhanqu | | fastify |
| matthyk | | fastify-plugin |
| mch-dsk | | fastify |
| mcollina | [❤️ sponsor](https://github.com/sponsors/mcollina) | ajv-compiler; avvio; csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-compress; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; light-my-request; middie; point-of-view; proxy-addr; secure-json-parse; under-pressure |
| melroy89 | [❤️ sponsor](https://github.com/sponsors/melroy89) | under-pressure |
| metcoder95 | [❤️ sponsor](https://github.com/sponsors/metcoder95) | fastify-elasticsearch |
| mhamann | | fastify-cli |
| mihaur | | fastify-elasticsearch |
| mikesamm | | fastify |
| mikhael-abdallah | | secure-json-parse |
| miquelfire | [❤️ sponsor](https://github.com/sponsors/miquelfire) | fastify-routes |
| miraries | | fastify-swagger-ui |
| mohab-sameh | | fastify |
| monish001 | | fastify |
| moradebianchetti81 | | fastify |
| mouhannad-sh | | aws-lambda-fastify |
| multivoltage | | point-of-view |
| muya | [❤️ sponsor](https://github.com/sponsors/muya) | under-pressure |
| mweberxyz | | point-of-view |
| nflaig | | fastify |
| nickfla1 | | avvio |
| o-az | | process-warning |
| ojeytonwilliams | | csrf-protection |
| onosendi | | fastify-formbody |
| philippviereck | | fastify |
| pip77 | | fastify-mongodb |
| puskin94 | | fastify |
| remidewitte | | fastify |
| rozzilla | | fastify |
| samialdury | | fastify-cli |
| sknetl | | fastify-cors |
| sourcecodeit | | fastify |
| synapse | | env-schema |
| timursaurus | | secure-json-parse |
| tlhunter | | fastify |
| tlund101 | | fastify-rate-limit |
| ttshivers | | fastify-http-proxy |
| voxpelli | [❤️ sponsor](https://github.com/sponsors/voxpelli) | fastify |
| weixinwu | | fastify-cli |
| zetaraku | | fastify-cli |

View File

@@ -71,8 +71,8 @@ order of plugins. *How?* Glad you asked, check out
[`avvio`](https://github.com/mcollina/avvio)! Fastify starts loading the plugin
__after__ `.listen()`, `.inject()` or `.ready()` are called.
Inside a plugin you can do whatever you want, register routes, utilities (we
will see this in a moment) and do nested registers, just remember to call `done`
Inside a plugin you can do whatever you want, register routes and utilities (we
will see this in a moment), and do nested registers, just remember to call `done`
when everything is set up!
```js
module.exports = function (fastify, options, done) {
@@ -117,7 +117,7 @@ Now you can access your utility just by calling `fastify.util` whenever you need
it - even inside your test.
And here starts the magic; do you remember how just now we were talking about
encapsulation? Well, using `register` and `decorate` in conjunction enable
encapsulation? Well, using `register` and `decorate` in conjunction enables
exactly that, let me show you an example to clarify this:
```js
fastify.register((instance, opts, done) => {
@@ -137,7 +137,7 @@ Inside the second register call `instance.util` will throw an error because
`util` exists only inside the first register context.
Let's step back for a moment and dig deeper into this: every time you use the
`register` API, a new context is created which avoids the negative situations
`register` API, a new context is created that avoids the negative situations
mentioned above.
Do note that encapsulation applies to the ancestors and siblings, but not the
@@ -202,7 +202,7 @@ a utility that also needs access to the `request` and `reply` instance,
a function that is defined using the `function` keyword is needed instead
of an *arrow function expression*.
In the same way you can do this for the `request` object:
You can do the same for the `request` object:
```js
fastify.decorate('getHeader', (req, header) => {
return req.headers[header]
@@ -316,7 +316,7 @@ based on a [route config option](../Reference/Routes.md#routes-options):
```js
fastify.register((instance, opts, done) => {
instance.decorate('util', (request, key, value) => { request[key] = value })
function handler(request, reply, done) {
instance.util(request, 'timestamp', new Date())
done()
@@ -395,7 +395,7 @@ As we mentioned earlier, Fastify starts loading its plugins __after__
have been declared. This means that, even though the plugin may inject variables
to the external Fastify instance via [`decorate`](../Reference/Decorators.md),
the decorated variables will not be accessible before calling `.listen()`,
`.inject()` or `.ready()`.
`.inject()`, or `.ready()`.
In case you rely on a variable injected by a preceding plugin and want to pass
that in the `options` argument of `register`, you can do so by using a function

View File

@@ -8,7 +8,7 @@
<a id="pp"></a>
Based on the article by Eran Hammer,the issue is created by a web security bug.
It is also a perfect illustration of the efforts required to maintain
It is also a perfect illustration of the efforts required to maintain
open-source software and the limitations of existing communication channels.
But first, if we use a JavaScript framework to process incoming JSON data, take
@@ -16,7 +16,7 @@ a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javasc
in general, and the specific
[technical details](https://github.com/hapijs/hapi/issues/3916) of this issue.
This could be a critical issue so, we might need to verify your own code first.
It focuses on specific framework however, any solution that uses `JSON.parse()`
It focuses on specific framework however, any solution that uses `JSON.parse()`
to process external data is potentially at risk.
### BOOM
@@ -42,7 +42,7 @@ defect a validation library can have.
To understand this, we need to understand how JavaScript works a bit.
Every object in JavaScript can have a prototype. It is a set of methods and
properties it "inherits" from another object. I have put inherits in quotes
properties it "inherits" from another object. I have put inherits in quotes
because JavaScript isn't really an object-oriented language. It is a prototype-
based object-oriented language.

View File

@@ -212,17 +212,19 @@ server {
# server group via port 3000.
server {
# This listen directive asks NGINX to accept requests
# coming to any address, port 443, with SSL, and HTTP/2
# if possible.
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
# coming to any address, port 443, with SSL.
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
# With a server_name directive you can also ask NGINX to
# use this server block only with matching server name(s)
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# listen 443 ssl;
# listen [::]:443 ssl;
# server_name example.tld;
# Enable HTTP/2 support
http2 on;
# Your SSL/TLS certificate (chain) and secret key in the PEM format
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/private.pem;
@@ -283,7 +285,7 @@ server {
## Kubernetes
<a id="kubernetes"></a>
The `readinessProbe` uses [(by
The `readinessProbe` uses ([by
default](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes))
the pod IP as the hostname. Fastify listens on `127.0.0.1` by default. The probe
will not be able to reach the application in this case. To make it work,
@@ -305,22 +307,22 @@ readinessProbe:
## Capacity Planning For Production
<a id="capacity"></a>
In order to rightsize the production environment for your Fastify application,
it is highly recommended that you perform your own measurements against
In order to rightsize the production environment for your Fastify application,
it is highly recommended that you perform your own measurements against
different configurations of the environment, which may
use real CPU cores, virtual CPU cores (vCPU), or even fractional
vCPU cores. We will use the term vCPU throughout this
recommendation to represent any CPU type.
Tools such as [k6](https://github.com/grafana/k6)
Tools such as [k6](https://github.com/grafana/k6)
or [autocannon](https://github.com/mcollina/autocannon) can be used for
conducting the necessary performance tests.
That said, you may also consider the following as a rule of thumb:
* To have the lowest possible latency, 2 vCPU are recommended per app
instance (e.g., a k8s pod). The second vCPU will mostly be used by the
garbage collector (GC) and libuv threadpool. This will minimize the latency
* To have the lowest possible latency, 2 vCPU are recommended per app
instance (e.g., a k8s pod). The second vCPU will mostly be used by the
garbage collector (GC) and libuv threadpool. This will minimize the latency
for your users, as well as the memory usage, as the GC will be run more
frequently. Also, the main thread won't have to stop to let the GC run.
@@ -328,7 +330,7 @@ frequently. Also, the main thread won't have to stop to let the GC run.
requests per second per vCPU available), consider using a smaller amount of vCPUs
per app instance. It is totally fine to run Node.js applications with 1 vCPU.
* You may experiment with an even smaller amount of vCPU, which may provide
* You may experiment with an even smaller amount of vCPU, which may provide
even better throughput in certain use-cases. There are reports of API gateway
solutions working well with 100m-200m vCPU in Kubernetes.
@@ -345,7 +347,7 @@ would be exposing metrics endpoints on a separate port,
to prevent public access, when using a reverse proxy or an ingress
firewall is not an option.
It is perfectly fine to spin up several Fastify instances within the same
Node.js process and run them concurrently, even in high load systems.
It is perfectly fine to spin up several Fastify instances within the same
Node.js process and run them concurrently, even in high load systems.
Each Fastify instance only generates as much load as the traffic it receives,
plus the memory used for that Fastify instance.

View File

@@ -1,20 +1,20 @@
<h1 align="center">Serverless</h1>
Run serverless applications and REST APIs using your existing Fastify
application. By default, Fastify will not work on your serverless platform of
choice, you will need to make some small changes to fix this. This document
contains a small guide for the most popular serverless providers and how to use
application. You may need to make code changes to work on your
serverless platform of choice. This document contains a small guide
for the most popular serverless providers and how to use
Fastify with them.
#### Should you use Fastify in a serverless platform?
That is up to you! Keep in mind that functions as a service should always use
That is up to you! Keep in mind, functions as a service should always use
small and focused functions, but you can also run an entire web application with
them. It is important to remember that the bigger the application the slower the
initial boot will be. The best way to run Fastify applications in serverless
environments is to use platforms like Google Cloud Run, AWS Fargate, and Azure
Container Instances, where the server can handle multiple requests at the same
time and make full use of Fastify's features.
environments is to use platforms like Google Cloud Run, AWS Fargate, Azure
Container Instances, and Vercel where the server can handle multiple requests
at the same time and make full use of Fastify's features.
One of the best features of using Fastify in serverless applications is the ease
of development. In your local environment, you will always run the Fastify
@@ -25,21 +25,21 @@ snippet of code.
### Contents
- [AWS](#aws)
- [Genezio](#genezio)
- [Google Cloud Functions](#google-cloud-functions)
- [Google Firebase Functions](#google-firebase-functions)
- [Google Cloud Run](#google-cloud-run)
- [Netlify Lambda](#netlify-lambda)
- [Platformatic Cloud](#platformatic-cloud)
- [Vercel](#vercel)
## AWS
To integrate with AWS, you have two choices of library:
- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify)
- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify)
which only adds API Gateway support but has heavy optimizations for fastify.
- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter)
which is a little slower as it creates an HTTP request for each AWS event but
- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter)
which is a little slower as it creates an HTTP request for each AWS event but
has support for more AWS services such as: AWS SQS, AWS SNS and others.
So you can decide which option is best for you, but you can test both libraries.
@@ -129,6 +129,13 @@ If you need to integrate with more AWS services, take a look at
[@h4ad/serverless-adapter](https://viniciusl.com.br/serverless-adapter/docs/main/frameworks/fastify)
on Fastify to find out how to integrate.
## Genezio
[Genezio](https://genezio.com/) is a platform designed to simplify the deployment
of serverless applications to the cloud.
[Genezio has a dedicated guide for deploying a Fastify application.](https://genezio.com/docs/frameworks/fastify/)
## Google Cloud Functions
### Creation of Fastify instance
@@ -230,14 +237,13 @@ npx @google-cloud/functions-framework --target=fastifyFunction
Or add this command to your `package.json` scripts:
```json
"scripts": {
...
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
...
...
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
...
}
```
and run it with `npm run dev`.
### Deploy
```bash
gcloud functions deploy fastifyFunction \
@@ -263,7 +269,7 @@ curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
## Google Firebase Functions
Follow this guide if you want to use Fastify as the HTTP framework for
Follow this guide if you want to use Fastify as the HTTP framework for
Firebase Functions instead of the vanilla JavaScript router provided with
`onRequest(async (req, res) => {}`.
@@ -280,8 +286,8 @@ const { onRequest } = require("firebase-functions/v2/https")
### Creation of Fastify instance
Create the Fastify instance and encapsulate the returned application instance
in a function which will register routes, await the server's processing of
plugins, hooks and other settings. As follows:
in a function that will register routes, await the server's processing of
plugins, hooks, and other settings. As follows:
```js
const fastify = require("fastify")({
@@ -297,10 +303,10 @@ const fastifyApp = async (request, reply) => {
### Add Custom `contentTypeParser` to Fastify instance and define endpoints
Firebase Function's HTTP layer already parses the request
and makes a JSON payload available. It also provides access
to the raw body, unparsed, which is useful in order to calculate
request signatures to validate HTTP webhooks.
Firebase Function's HTTP layer already parses the request and makes a JSON
payload available through the property `payload.body` below. It also provides
access to the raw body, unparsed, which is useful for calculating request
signatures to validate HTTP webhooks.
Add as follows to the `registerRoutes()` function:
@@ -318,7 +324,7 @@ async function registerRoutes (fastify) {
})
// define your endpoints here...
fastify.post("/some-route-here", async (request, reply) => {}
fastify.post("/some-route-here", async (request, reply) => {})
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
@@ -326,6 +332,24 @@ async function registerRoutes (fastify) {
}
```
**Failing to add this `ContentTypeParser` may lead to the Fastify process
remaining stuck and not processing any other requests after receiving one with
the Content-Type `application/json`.**
When using Typescript, since the type of `payload` is a native `IncomingMessage`
that gets modified by Firebase, it won't be able to find the property
`payload.body`.
In order to suppress the error, you can use the following signature:
```ts
declare module 'http' {
interface IncomingMessage {
body?: unknown;
}
}
```
### Export the function using Firebase onRequest
Final step is to export the Fastify app instance to Firebase's own
@@ -369,7 +393,6 @@ firebase functions:log
- [Fastify on Firebase Functions](https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify/blob/main/package.json)
- [An article about HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy](https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy)
## Google Cloud Run
Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless
@@ -384,7 +407,7 @@ familiar with gcloud or just follow their
### Adjust Fastify server
In order for Fastify to properly listen for requests within the container, be
For Fastify to properly listen for requests within the container, be
sure to set the correct port and address:
```js
@@ -455,7 +478,7 @@ CMD [ "npm", "start" ]
To keep build artifacts out of your container (which keeps it small and improves
build times) add a `.dockerignore` file like the one below:
```.dockerignore
```dockerignore
Dockerfile
README.md
node_modules
@@ -482,12 +505,11 @@ gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
Your app will be accessible from the URL GCP provides.
## netlify-lambda
First, please perform all preparation steps related to **AWS Lambda**.
Create a folder called `functions`, then create `server.js` (and your endpoint
Create a folder called `functions`, then create `server.js` (and your endpoint
path will be `server.js`) inside the `functions` folder.
### functions/server.js
@@ -556,106 +578,27 @@ Add this command to your `package.json` *scripts*
```json
"scripts": {
...
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
...
...
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
...
}
```
Then it should work fine
## Platformatic Cloud
[Platformatic](https://platformatic.dev) provides zero-configuration deployment
for Node.js applications.
To use it now, you should wrap your existing Fastify application inside a
[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction),
by running the following:
```bash
npm create platformatic@latest -- service
```
The wizard would ask you to fill in a few answers:
```
? Where would you like to create your project? .
? Do you want to run npm install? yes
? Do you want to use TypeScript? no
? What port do you want to use? 3042
[13:04:14] INFO: Configuration file platformatic.service.json successfully created.
[13:04:14] INFO: Environment file .env successfully created.
[13:04:14] INFO: Plugins folder "plugins" successfully created.
[13:04:14] INFO: Routes folder "routes" successfully created.
? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no
? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no
```
Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in
with your GitHub account.
Create your first application and a static workspace: be careful to download the
API key as an env file, e.g. `yourworkspace.txt`.
Then, you can easily deploy your application with the following command:
```bash
platformatic deploy --keys `yourworkspace.txt`
```
Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service)
on how to wrap Fastify application in Platformatic.
Then it should work fine.
## Vercel
[Vercel](https://vercel.com) provides zero-configuration deployment for Node.js
applications. To use it now, it is as simple as configuring your `vercel.json`
file like the following:
[Vercel](https://vercel.com) fully supports deploying Fastify applications.
Additionally, with Vercel's
[Fluid compute](https://vercel.com/docs/functions/fluid-compute), you can combine
server-like concurrency with the autoscaling properties of traditional
serverless functions.
```json
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/serverless.js"
}
]
}
```
Get started with the
[Fastify template on Vercel](
https://vercel.com/templates/backend/fastify-on-vercel).
Then, write `api/serverless.js` like so:
```js
"use strict";
// Read the .env file.
import * as dotenv from "dotenv";
dotenv.config();
// Require the framework
import Fastify from "fastify";
// Instantiate Fastify with some config
const app = Fastify({
logger: true,
});
// Register your application as a normal plugin.
app.register(import("../src/app.js"));
export default async (req, res) => {
await app.ready();
app.server.emit('request', req, res);
}
```
In `src/app.js` define the plugin.
```js
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
export default routes;
```
[Fluid compute](https://vercel.com/docs/functions/fluid-compute) currently
requires an explicit opt-in. Learn more about enabling Fluid compute
[here](
https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute).

View File

@@ -83,7 +83,7 @@ Result:
Make sure you avoid copying other people's work. Keep it as original as
possible. You can learn from what they have done and reference where it is from
if you used a particular quote from their work.
if you use a particular quote from their work.
## Word Choice
@@ -217,7 +217,7 @@ Styles](https://medium.com/better-programming/string-case-styles-camel-pascal-sn
### Hyperlinks
Hyperlinks should have a clear title of what it references. Here is how your
Hyperlinks should have a clear title of what they reference. Here is how your
hyperlink should look:
```MD

View File

@@ -5,15 +5,15 @@
Testing is one of the most important parts of developing an application. Fastify
is very flexible when it comes to testing and is compatible with most testing
frameworks (such as [Tap](https://www.npmjs.com/package/tap), which is used in
the examples below).
frameworks (such as [Node Test Runner](https://nodejs.org/api/test.html),
which is used in the examples below).
## Application
Let's `cd` into a fresh directory called 'testing-example' and type `npm init
-y` in our terminal.
Run `npm i fastify && npm i tap pino-pretty -D`
Run `npm i fastify && npm i pino-pretty -D`
### Separating concerns makes testing easy
@@ -113,24 +113,25 @@ Now we can replace our `console.log` calls with actual tests!
In your `package.json` change the "test" script to:
`"test": "tap --reporter=list --watch"`
`"test": "node --test --watch"`
**app.test.js**:
```js
'use strict'
const { test } = require('tap')
const { test } = require('node:test')
const build = require('./app')
test('requests the "/" route', async t => {
t.plan(1)
const app = build()
const response = await app.inject({
method: 'GET',
url: '/'
})
t.equal(response.statusCode, 200, 'returns a status code of 200')
t.assert.strictEqual(response.statusCode, 200, 'returns a status code of 200')
})
```
@@ -214,26 +215,26 @@ module.exports = buildFastify
**test.js**
```js
const tap = require('tap')
const { test } = require('node:test')
const buildFastify = require('./app')
tap.test('GET `/` route', t => {
test('GET `/` route', t => {
t.plan(4)
const fastify = buildFastify()
// At the end of your tests it is highly recommended to call `.close()`
// to ensure that all connections to external services get closed.
t.teardown(() => fastify.close())
t.after(() => fastify.close())
fastify.inject({
method: 'GET',
url: '/'
}, (err, response) => {
t.error(err)
t.equal(response.statusCode, 200)
t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
t.same(response.json(), { hello: 'world' })
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
t.assert.deepStrictEqual(response.json(), { hello: 'world' })
})
})
```
@@ -248,11 +249,11 @@ Uses **app.js** from the previous example.
**test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici))
```js
const tap = require('tap')
const { test } = require('node:test')
const { Client } = require('undici')
const buildFastify = require('./app')
tap.test('should work with undici', async t => {
test('should work with undici', async t => {
t.plan(2)
const fastify = buildFastify()
@@ -263,63 +264,64 @@ tap.test('should work with undici', async t => {
'http://localhost:' + fastify.server.address().port, {
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10
}
}
)
t.teardown(() => {
t.after(() => {
fastify.close()
client.close()
})
const response = await client.request({ method: 'GET', path: '/' })
t.equal(await response.body.text(), '{"hello":"world"}')
t.equal(response.statusCode, 200)
t.assert.strictEqual(await response.body.text(), '{"hello":"world"}')
t.assert.strictEqual(response.statusCode, 200)
})
```
Alternatively, starting with Node.js 18,
[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch)
Alternatively, starting with Node.js 18,
[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch)
may be used without requiring any extra dependencies:
**test-listen.js**
```js
const tap = require('tap')
const { test } = require('node:test')
const buildFastify = require('./app')
tap.test('should work with fetch', async t => {
test('should work with fetch', async t => {
t.plan(3)
const fastify = buildFastify()
t.teardown(() => fastify.close())
t.after(() => fastify.close())
await fastify.listen()
const response = await fetch(
'http://localhost:' + fastify.server.address().port
)
t.equal(response.status, 200)
t.equal(
t.assert.strictEqual(response.status, 200)
t.assert.strictEqual(
response.headers.get('content-type'),
'application/json; charset=utf-8'
)
t.has(await response.json(), { hello: 'world' })
const jsonResult = await response.json()
t.assert.strictEqual(jsonResult.hello, 'world')
})
```
**test-ready.js** (testing with
[`SuperTest`](https://www.npmjs.com/package/supertest))
```js
const tap = require('tap')
const { test } = require('node:test')
const supertest = require('supertest')
const buildFastify = require('./app')
tap.test('GET `/` route', async (t) => {
test('GET `/` route', async (t) => {
const fastify = buildFastify()
t.teardown(() => fastify.close())
t.after(() => fastify.close())
await fastify.ready()
@@ -327,22 +329,21 @@ tap.test('GET `/` route', async (t) => {
.get('/')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')
t.same(response.body, { hello: 'world' })
t.assert.deepStrictEqual(response.body, { hello: 'world' })
})
```
### How to inspect tap tests
### How to inspect node tests
1. Isolate your test by passing the `{only: true}` option
```javascript
test('should ...', {only: true}, t => ...)
```
2. Run `tap` using `npx`
2. Run `node --test`
```bash
> npx tap -O -T --node-arg=--inspect-brk test/<test-file.test.js>
> node --test --test-only --inspect-brk test/<test-file.test.js>
```
- `-O` specifies to run tests with the `only` option enabled
- `-T` specifies not to timeout (while you're debugging)
- `--node-arg=--inspect-brk` will launch the node debugger
- `--test-only` specifies to run tests with the `only` option enabled
- `--inspect-brk` will launch the node debugger
3. In VS Code, create and launch a `Node.js: Attach` debug configuration. No
modification should be necessary.
@@ -352,10 +353,10 @@ Now you should be able to step through your test file (and the rest of
## Plugins
Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init
-y` in our terminal.
Let's `cd` into a fresh directory called 'testing-plugin-example' and type
`npm init -y` in our terminal.
Run `npm i fastify fastify-plugin && npm i tap -D`
Run `npm i fastify fastify-plugin`
**plugin/myFirstPlugin.js**:
@@ -376,16 +377,16 @@ A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md)
```js
const Fastify = require("fastify");
const tap = require("tap");
const { test } = require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");
tap.test("Test the Plugin Route", async t => {
test("Test the Plugin Route", async t => {
// Create a mock fastify application to test the plugin
const fastify = Fastify()
fastify.register(myPlugin)
// Add an endpoint of your choice
// Add an endpoint of your choice
fastify.get("/", async (request, reply) => {
return ({ message: request.helloRequest })
})
@@ -395,7 +396,7 @@ tap.test("Test the Plugin Route", async t => {
method: "GET",
url: "/"
})
console.log('status code: ', fastifyResponse.statusCode)
console.log('body: ', fastifyResponse.body)
})
@@ -412,18 +413,18 @@ Now we can replace our `console.log` calls with actual tests!
In your `package.json` change the "test" script to:
`"test": "tap --reporter=list --watch"`
`"test": "node --test --watch"`
Create the tap test for the endpoint.
Create the test for the endpoint.
**test/myFirstPlugin.test.js**:
```js
const Fastify = require("fastify");
const tap = require("tap");
const { test } = require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");
tap.test("Test the Plugin Route", async t => {
test("Test the Plugin Route", async t => {
// Specifies the number of test
t.plan(2)
@@ -439,9 +440,9 @@ tap.test("Test the Plugin Route", async t => {
method: "GET",
url: "/"
})
t.equal(fastifyResponse.statusCode, 200)
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
t.assert.strictEqual(fastifyResponse.statusCode, 200)
t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
})
```
@@ -453,10 +454,10 @@ Test the ```.decorate()``` and ```.decorateRequest()```.
```js
const Fastify = require("fastify");
const tap = require("tap");
const { test }= require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");
tap.test("Test the Plugin Route", async t => {
test("Test the Plugin Route", async t => {
t.plan(5)
const fastify = Fastify()
@@ -464,9 +465,9 @@ tap.test("Test the Plugin Route", async t => {
fastify.get("/", async (request, reply) => {
// Testing the fastify decorators
t.not(request.helloRequest, null)
t.ok(request.helloRequest, "Hello World")
t.ok(fastify.helloInstance, "Hello Fastify Instance")
t.assert.ifError(request.helloRequest)
t.assert.ok(request.helloRequest, "Hello World")
t.assert.ok(fastify.helloInstance, "Hello Fastify Instance")
return ({ message: request.helloRequest })
})
@@ -474,7 +475,7 @@ tap.test("Test the Plugin Route", async t => {
method: "GET",
url: "/"
})
t.equal(fastifyResponse.statusCode, 200)
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
t.assert.strictEqual(fastifyResponse.statusCode, 200)
t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
})
```
```

View File

@@ -14,7 +14,7 @@ suggestion"](https://github.com/fastify/fastify/issues?q=is%3Aissue+is%3Aopen+la
in our issue tracker!*
## Code
Fastify uses different techniques to optimize its code, many of them are
Fastify uses different techniques to optimize its code, many of which are
documented in our Guides. We highly recommend you read [the hitchhiker's guide
to plugins](./Plugins-Guide.md) to discover all the APIs you can use to build
your plugin and learn how to use them.
@@ -53,17 +53,16 @@ Always put an example file in your repository. Examples are very helpful for
users and give a very fast way to test your plugin. Your users will be grateful.
## Test
It is extremely important that a plugin is thoroughly tested to verify that is
working properly.
A plugin **must** be thoroughly tested to verify that is working properly.
A plugin without tests will not be accepted to the ecosystem list. A lack of
tests does not inspire trust nor guarantee that the code will continue to work
among different versions of its dependencies.
We do not enforce any testing library. We use [`tap`](https://www.node-tap.org/)
We do not enforce any testing library. We use [`node:test`](https://nodejs.org/api/test.html)
since it offers out-of-the-box parallel testing and code coverage, but it is up
to you to choose your library of preference.
We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to
We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to
learn about how to test your plugins.
## Code Linter

View File

@@ -10,16 +10,17 @@ Whereas exhaustive type narrowing checks normally rely on `never` to represent
an unreachable state, reduction in type provider interfaces should only be done
up to `unknown`.
The reasoning is that certain methods of `FastifyInstance` are
contravariant on `TypeProvider`, which can lead to TypeScript surfacing
assignability issues unless the custom type provider interface is
The reasoning is that certain methods of `FastifyInstance` are
contravariant on `TypeProvider`, which can lead to TypeScript surfacing
assignability issues unless the custom type provider interface is
substitutable with `FastifyTypeProviderDefault`.
For example, `FastifyTypeProviderDefault` will not be assignable to the following:
```ts
export interface NotSubstitutableTypeProvider extends FastifyTypeProvider {
// bad, nothing is assignable to `never` (except for itself)
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
}
```
@@ -27,6 +28,7 @@ Unless changed to:
```ts
export interface SubstitutableTypeProvider extends FastifyTypeProvider {
// good, anything can be assigned to `unknown`
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
}
```

View File

@@ -1,33 +1,34 @@
<h1 align="center">Fastify</h1>
## `Content-Type` Parser
Natively, Fastify only supports `'application/json'` and `'text/plain'` content
types. If the content type is not one of these, an
`FST_ERR_CTP_INVALID_MEDIA_TYPE` error will be thrown.
Other common content types are supported through the use of
[plugins](https://fastify.dev/ecosystem/).
Fastify natively supports `'application/json'` and `'text/plain'` content types
with a default charset of `utf-8`. These default parsers can be changed or
removed.
The default charset is `utf-8`. If you need to support different content types,
you can use the `addContentTypeParser` API. *The default JSON and/or plain text
parser can be changed or removed.*
Unsupported content types will throw an `FST_ERR_CTP_INVALID_MEDIA_TYPE` error.
*Note: If you decide to specify your own content type with the `Content-Type`
header, UTF-8 will not be the default. Be sure to include UTF-8 like this
`text/html; charset=utf-8`.*
To support other content types, use the `addContentTypeParser` API or an
existing [plugin](https://fastify.dev/ecosystem/).
As with the other APIs, `addContentTypeParser` is encapsulated in the scope in
which it is declared. This means that if you declare it in the root scope it
will be available everywhere, while if you declare it inside a plugin it will be
available only in that scope and its children.
As with other APIs, `addContentTypeParser` is encapsulated in the scope in which
it is declared. If declared in the root scope, it is available everywhere; if
declared in a plugin, it is available only in that scope and its children.
Fastify automatically adds the parsed request payload to the [Fastify
request](./Request.md) object which you can access with `request.body`.
request](./Request.md) object, accessible via `request.body`.
Note that for `GET` and `HEAD` requests the payload is never parsed. For
`OPTIONS` and `DELETE` requests the payload is only parsed if the content type
is given in the content-type header. If it is not given, the
[catch-all](#catch-all) parser is not executed as with `POST`, `PUT` and
`PATCH`, but the payload is simply not parsed.
Note that for `GET` and `HEAD` requests, the payload is never parsed. For
`OPTIONS` and `DELETE` requests, the payload is parsed only if a valid
`content-type` header is provided. Unlike `POST`, `PUT`, and `PATCH`, the
[catch-all](#catch-all) parser is not executed, and the payload is simply not
parsed.
> ⚠ Warning:
> When using regular expressions to detect `Content-Type`, it is important to
> ensure proper detection. For example, to match `application/*`, use
> `/^application\/([\w-]+);?/` to match the
> [essence MIME type](https://mimesniff.spec.whatwg.org/#mime-type-miscellaneous)
> only.
### Usage
```js
@@ -46,13 +47,13 @@ fastify.addContentTypeParser(['text/xml', 'application/xml'], function (request,
// Async is also supported in Node versions >= 8.0.0
fastify.addContentTypeParser('application/jsoff', async function (request, payload) {
var res = await jsoffParserAsync(payload)
const res = await jsoffParserAsync(payload)
return res
})
// Handle all content types that matches RegExp
fastify.addContentTypeParser(/^image\/.*/, function (request, payload, done) {
fastify.addContentTypeParser(/^image\/([\w-]+);?/, function (request, payload, done) {
imageParser(payload, function (err, body) {
done(err, body)
})
@@ -63,11 +64,10 @@ fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefa
```
Fastify first tries to match a content-type parser with a `string` value before
trying to find a matching `RegExp`. If you provide overlapping content types,
Fastify tries to find a matching content type by starting with the last one
passed and ending with the first one. So if you want to specify a general
content type more precisely, first specify the general content type and then the
more specific one, like in the example below.
trying to find a matching `RegExp`. For overlapping content types, it starts
with the last one configured and ends with the first (last in, first out).
To specify a general content type more precisely, first specify the general
type, then the specific one, as shown below.
```js
// Here only the second content type parser is called because its value also matches the first one
@@ -80,14 +80,34 @@ fastify.addContentTypeParser('application/vnd.custom', (request, body, done) =>
fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} )
```
Besides the `addContentTypeParser` API there are further APIs that can be used.
These are `hasContentTypeParser`, `removeContentTypeParser` and
`removeAllContentTypeParsers`.
### Using addContentTypeParser with fastify.register
When using `addContentTypeParser` with `fastify.register`, avoid `await`
when registering routes. Using `await` makes route registration asynchronous,
potentially registering routes before `addContentTypeParser` is set.
#### Correct Usage
```js
const fastify = require('fastify')();
fastify.register((fastify, opts) => {
fastify.addContentTypeParser('application/json', function (request, payload, done) {
jsonParser(payload, function (err, body) {
done(err, body)
})
})
fastify.get('/hello', async (req, res) => {});
});
```
In addition to `addContentTypeParser`, the `hasContentTypeParser`,
`removeContentTypeParser`, and `removeAllContentTypeParsers` APIs are available.
#### hasContentTypeParser
You can use the `hasContentTypeParser` API to find if a specific content type
parser already exists.
Use the `hasContentTypeParser` API to check if a specific content type parser
exists.
```js
if (!fastify.hasContentTypeParser('application/jsoff')){
@@ -101,8 +121,8 @@ if (!fastify.hasContentTypeParser('application/jsoff')){
#### removeContentTypeParser
With `removeContentTypeParser` a single or an array of content types can be
removed. The method supports `string` and `RegExp` content types.
`removeContentTypeParser` can remove a single content type or an array of
content types, supporting both `string` and `RegExp`.
```js
fastify.addContentTypeParser('text/xml', function (request, payload, done) {
@@ -116,16 +136,11 @@ fastify.removeContentTypeParser(['application/json', 'text/plain'])
```
#### removeAllContentTypeParsers
In the example from just above, it is noticeable that we need to specify each
content type that we want to remove. To solve this problem Fastify provides the
`removeAllContentTypeParsers` API. This can be used to remove all currently
existing content type parsers. In the example below we achieve the same as in
the example above except that we do not need to specify each content type to
delete. Just like `removeContentTypeParser`, this API supports encapsulation.
The API is especially useful if you want to register a [catch-all content type
parser](#catch-all) that should be executed for every content type and the
built-in parsers should be ignored as well.
The `removeAllContentTypeParsers` API removes all existing content type parsers
eliminating the need to specify each one individually. This API supports
encapsulation and is useful for registering a
[catch-all content type parser](#catch-all) that should be executed for every
content type, ignoring built-in parsers.
```js
fastify.removeAllContentTypeParsers()
@@ -137,22 +152,20 @@ fastify.addContentTypeParser('text/xml', function (request, payload, done) {
})
```
**Notice**: The old syntaxes `function(req, done)` and `async function(req)` for
the parser are still supported but they are deprecated.
> Note: `function(req, done)` and `async function(req)` are
> still supported but deprecated.
#### Body Parser
You can parse the body of a request in two ways. The first one is shown above:
you add a custom content type parser and handle the request stream. In the
second one, you should pass a `parseAs` option to the `addContentTypeParser`
API, where you declare how you want to get the body. It could be of type
`'string'` or `'buffer'`. If you use the `parseAs` option, Fastify will
internally handle the stream and perform some checks, such as the [maximum
size](./Server.md#factory-body-limit) of the body and the content length. If the
limit is exceeded the custom parser will not be invoked.
The request body can be parsed in two ways. First, add a custom content type
parser and handle the request stream. Or second, use the `parseAs` option in the
`addContentTypeParser` API, specifying `'string'` or `'buffer'`. Fastify will
handle the stream, check the [maximum size](./Server.md#factory-body-limit) of
the body, and the content length. If the limit is exceeded, the custom parser
will not be invoked.
```js
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
try {
var json = JSON.parse(body)
const json = JSON.parse(body)
done(null, json)
} catch (err) {
err.statusCode = 400
@@ -166,30 +179,27 @@ See
for an example.
##### Custom Parser Options
+ `parseAs` (string): Either `'string'` or `'buffer'` to designate how the
incoming data should be collected. Default: `'buffer'`.
+ `parseAs` (string): `'string'` or `'buffer'` to designate how the incoming
data should be collected. Default: `'buffer'`.
+ `bodyLimit` (number): The maximum payload size, in bytes, that the custom
parser will accept. Defaults to the global body limit passed to the [`Fastify
factory function`](./Server.md#bodylimit).
#### Catch-All
There are some cases where you need to catch all requests regardless of their
content type. With Fastify, you can just use the `'*'` content type.
To catch all requests regardless of content type, use the `'*'` content type:
```js
fastify.addContentTypeParser('*', function (request, payload, done) {
var data = ''
let data = ''
payload.on('data', chunk => { data += chunk })
payload.on('end', () => {
done(null, data)
})
})
```
All requests without a corresponding content type parser will be handled by
this function.
Using this, all requests that do not have a corresponding content type parser
will be handled by the specified function.
This is also useful for piping the request stream. You can define a content
parser like:
This is also useful for piping the request stream. Define a content parser like:
```js
fastify.addContentTypeParser('*', function (request, payload, done) {
@@ -197,7 +207,7 @@ fastify.addContentTypeParser('*', function (request, payload, done) {
})
```
and then access the core HTTP request directly for piping it where you want:
And then access the core HTTP request directly for piping:
```js
app.post('/hello', (request, reply) => {
@@ -225,19 +235,18 @@ fastify.route({
})
```
For piping file uploads you may want to check out [this
plugin](https://github.com/fastify/fastify-multipart).
For piping file uploads, check out
[`@fastify/multipart`](https://github.com/fastify/fastify-multipart).
If you want the content type parser to be executed on all content types and not
only on those that don't have a specific one, you should call the
`removeAllContentTypeParsers` method first.
To execute the content type parser on all content types, call
`removeAllContentTypeParsers` first.
```js
// Without this call, the request body with the content type application/json would be processed by the built-in JSON parser
fastify.removeAllContentTypeParsers()
fastify.addContentTypeParser('*', function (request, payload, done) {
var data = ''
const data = ''
payload.on('data', chunk => { data += chunk })
payload.on('end', () => {
done(null, data)

View File

@@ -2,16 +2,15 @@
## Decorators
The decorators API allows customization of the core Fastify objects, such as the
server instance itself and any request and reply objects used during the HTTP
request lifecycle. The decorators API can be used to attach any type of property
to the core objects, e.g. functions, plain objects, or native types.
The decorators API customizes core Fastify objects, such as the server instance
and any request and reply objects used during the HTTP request lifecycle. It
can attach any type of property to core objects, e.g., functions, plain
objects, or native types.
This API is *synchronous*. Attempting to define a decoration asynchronously
could result in the Fastify instance booting before the decoration completes its
initialization. To avoid this issue, and register an asynchronous decoration,
the `register` API, in combination with `fastify-plugin`, must be used instead.
To learn more, see the [Plugins](./Plugins.md) documentation.
This API is *synchronous*. Defining a decoration asynchronously could result in
the Fastify instance booting before the decoration completes. To register an
asynchronous decoration, use the `register` API with `fastify-plugin`. See the
[Plugins](./Plugins.md) documentation for more details.
Decorating core objects with this API allows the underlying JavaScript engine to
optimize the handling of server, request, and reply objects. This is
@@ -35,9 +34,9 @@ fastify.get('/', function (req, reply) {
})
```
Since the above example mutates the request object after it has already been
instantiated, the JavaScript engine must deoptimize access to the request
object. By using the decoration API this deoptimization is avoided:
The above example mutates the request object after instantiation, causing the
JavaScript engine to deoptimize access. Using the decoration API avoids this
deoptimization:
```js
// Decorate request with a 'user' property
@@ -54,17 +53,13 @@ fastify.get('/', (req, reply) => {
})
```
Note that it is important to keep the initial shape of a decorated field as
close as possible to the value intended to be set dynamically in the future.
Initialize a decorator as a `''` if the intended value is a string, and as
`null` if it will be an object or a function.
Remember this example works only with value types as reference types will be
shared amongst all requests. See [decorateRequest](#decorate-request).
See [JavaScript engine fundamentals: Shapes and Inline
Caches](https://mathiasbynens.be/notes/shapes-ics) for more information on this
topic.
Keep the initial shape of a decorated field close to its future dynamic value.
Initialize a decorator as `''` for strings and `null` for objects or functions.
This works only with value types; reference types will throw an error during
Fastify startup. See [decorateRequest](#decorate-request) and
[JavaScript engine fundamentals: Shapes
and Inline Caches](https://mathiasbynens.be/notes/shapes-ics)
for more information.
### Usage
<a id="usage"></a>
@@ -72,8 +67,7 @@ topic.
#### `decorate(name, value, [dependencies])`
<a id="decorate"></a>
This method is used to customize the Fastify [server](./Server.md)
instance.
This method customizes the Fastify [server](./Server.md) instance.
For example, to attach a new method to the server instance:
@@ -83,7 +77,7 @@ fastify.decorate('utility', function () {
})
```
As mentioned above, non-function values can be attached:
Non-function values can also be attached to the server instance:
```js
fastify.decorate('conf', {
@@ -109,7 +103,7 @@ fastify.decorate('db', new DbConnection())
fastify.get('/', async function (request, reply) {
// using return
return { hello: await this.db.query('world') }
// or
// using reply.send()
reply.send({ hello: await this.db.query('world') })
@@ -118,9 +112,9 @@ fastify.get('/', async function (request, reply) {
```
The `dependencies` parameter is an optional list of decorators that the
decorator being defined relies upon. This list is simply a list of string names
of other decorators. In the following example, the "utility" decorator depends
upon "greet" and "hi" decorators:
decorator being defined relies upon. This list contains the names of other
decorators. In the following example, the "utility" decorator depends on the
"greet" and "hi" decorators:
```js
async function greetDecorator (fastify, opts) {
@@ -155,18 +149,17 @@ fastify.listen({ port: 3000 }, (err, address) => {
})
```
Note: using an arrow function will break the binding of `this` to the
`FastifyInstance`.
Using an arrow function breaks the binding of `this` to
the `FastifyInstance`.
If a dependency is not satisfied, the `decorate` method will throw an exception.
The dependency check is performed before the server instance is booted. Thus, it
cannot occur during runtime.
If a dependency is not satisfied, the `decorate` method throws an exception.
The dependency check occurs before the server instance boots, not during
runtime.
#### `decorateReply(name, value, [dependencies])`
<a id="decorate-reply"></a>
As the name suggests, this API is used to add new methods/properties to the core
`Reply` object:
This API adds new methods/properties to the core `Reply` object:
```js
fastify.decorateReply('utility', function () {
@@ -174,28 +167,29 @@ fastify.decorateReply('utility', function () {
})
```
Note: using an arrow function will break the binding of `this` to the Fastify
Using an arrow function will break the binding of `this` to the Fastify
`Reply` instance.
Note: using `decorateReply` will emit a warning if used with a reference type:
Using `decorateReply` will throw and error if used with a reference type:
```js
// Don't do this
fastify.decorateReply('foo', { bar: 'fizz'})
```
In this example, the reference of the object is shared with all the requests:
In this example, the object reference would be shared with all requests, and
**any mutation will impact all requests, potentially creating security
vulnerabilities or memory leaks**. To achieve proper encapsulation across
requests configure a new value for each incoming request in the [`'onRequest'`
hook](./Hooks.md#onrequest). Example:
vulnerabilities or memory leaks**. Fastify blocks this.
To achieve proper encapsulation across requests configure a new value for each
incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest).
```js
const fp = require('fastify-plugin')
async function myPlugin (app) {
app.decorateRequest('foo', null)
app.decorateReply('foo')
app.addHook('onRequest', async (req, reply) => {
req.foo = { bar: 42 }
reply.foo = { bar: 42 }
})
}
@@ -207,8 +201,8 @@ See [`decorate`](#decorate) for information about the `dependencies` parameter.
#### `decorateRequest(name, value, [dependencies])`
<a id="decorate-request"></a>
As above with [`decorateReply`](#decorate-reply), this API is used add new
methods/properties to the core `Request` object:
As with [`decorateReply`](#decorate-reply), this API adds new methods/properties
to the core `Request` object:
```js
fastify.decorateRequest('utility', function () {
@@ -216,27 +210,29 @@ fastify.decorateRequest('utility', function () {
})
```
Note: using an arrow function will break the binding of `this` to the Fastify
Using an arrow function will break the binding of `this` to the Fastify
`Request` instance.
Note: using `decorateRequest` will emit a warning if used with a reference type:
Using `decorateRequest` will emit an error if used with a reference type:
```js
// Don't do this
fastify.decorateRequest('foo', { bar: 'fizz'})
```
In this example, the reference of the object is shared with all the requests:
In this example, the object reference would be shared with all requests, and
**any mutation will impact all requests, potentially creating security
vulnerabilities or memory leaks**.
vulnerabilities or memory leaks**. Fastify blocks this.
To achieve proper encapsulation across requests configure a new value for each
incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest). Example:
incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest).
Example:
```js
const fp = require('fastify-plugin')
async function myPlugin (app) {
app.decorateRequest('foo', null)
app.decorateRequest('foo')
app.addHook('onRequest', async (req, reply) => {
req.foo = { bar: 42 }
})
@@ -245,6 +241,28 @@ async function myPlugin (app) {
module.exports = fp(myPlugin)
```
The hook solution is more flexible and allows for more complex initialization
because more logic can be added to the `onRequest` hook.
Another approach is to use the getter/setter pattern, but it requires 2 decorators:
```js
fastify.decorateRequest('my_decorator_holder') // define the holder
fastify.decorateRequest('user', {
getter () {
this.my_decorator_holder ??= {} // initialize the holder
return this.my_decorator_holder
}
})
fastify.get('/', async function (req, reply) {
req.user.access = 'granted'
// other code
})
```
This ensures that the `user` property is always unique for each request.
See [`decorate`](#decorate) for information about the `dependencies` parameter.
#### `hasDecorator(name)`
@@ -279,9 +297,7 @@ fastify.hasReplyDecorator('utility')
Defining a decorator (using `decorate`, `decorateRequest`, or `decorateReply`)
with the same name more than once in the same **encapsulated** context will
throw an exception.
As an example, the following will throw:
throw an exception. For example, the following will throw:
```js
const server = require('fastify')()
@@ -332,9 +348,9 @@ server.listen({ port: 3000 })
### Getters and Setters
<a id="getters-setters"></a>
Decorators accept special "getter/setter" objects. These objects have functions
named `getter` and `setter` (though the `setter` function is optional). This
allows defining properties via decorators, for example:
Decorators accept special "getter/setter" objects with `getter` and optional
`setter` functions. This allows defining properties via decorators,
for example:
```js
fastify.decorate('foo', {
@@ -349,3 +365,70 @@ Will define the `foo` property on the Fastify instance:
```js
console.log(fastify.foo) // 'a getter'
```
#### `getDecorator(name)`
<a id="get-decorator"></a>
Used to retrieve an existing decorator from the Fastify instance, `Request`,
or `Reply`.
If the decorator is not defined, an `FST_ERR_DEC_UNDECLARED` error is thrown.
```js
// Get a decorator from the Fastify instance
const utility = fastify.getDecorator('utility')
// Get a decorator from the request object
const user = request.getDecorator('user')
// Get a decorator from the reply object
const helper = reply.getDecorator('helper')
```
The `getDecorator` method is useful for dependency validation - it can be used to
check for required decorators at registration time. If any are missing, it fails
at boot, ensuring dependencies are available during the request lifecycle.
```js
fastify.register(async function (fastify) {
// Verify the decorator exists before using it
const usersRepository = fastify.getDecorator('usersRepository')
fastify.get('/users', async function (request, reply) {
return usersRepository.findAll()
})
})
```
> Note: For TypeScript users, `getDecorator` supports generic type parameters.
> See the [TypeScript documentation](/docs/Reference/TypeScript.md) for
> advanced typing examples.
#### `setDecorator(name, value)`
<a id="set-decorator"></a>
Used to safely update the value of a `Request` decorator.
If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error is thrown.
```js
fastify.decorateRequest('user', null)
fastify.addHook('preHandler', async (req, reply) => {
// Safely set the decorator value
req.setDecorator('user', 'Bob Dylan')
})
```
The `setDecorator` method provides runtime safety by ensuring the decorator exists
before setting its value, preventing errors from typos in decorator names.
```js
fastify.decorateRequest('account', null)
fastify.addHook('preHandler', async (req, reply) => {
// This will throw FST_ERR_DEC_UNDECLARED due to typo in decorator name
req.setDecorator('acount', { id: 123 })
})
```
> Note: For TypeScript users, see the
> [TypeScript documentation](/docs/Reference/TypeScript.md) for advanced
> typing examples using `setDecorator<T>`.

View File

@@ -3,21 +3,20 @@
## Encapsulation
<a id="encapsulation"></a>
A fundamental feature of Fastify is the "encapsulation context." The
encapsulation context governs which [decorators](./Decorators.md), registered
[hooks](./Hooks.md), and [plugins](./Plugins.md) are available to
[routes](./Routes.md). A visual representation of the encapsulation context
is shown in the following figure:
A fundamental feature of Fastify is the "encapsulation context." It governs
which [decorators](./Decorators.md), registered [hooks](./Hooks.md), and
[plugins](./Plugins.md) are available to [routes](./Routes.md). A visual
representation of the encapsulation context is shown in the following figure:
![Figure 1](../resources/encapsulation_context.svg)
In the above figure, there are several entities:
In the figure above, there are several entities:
1. The _root context_
2. Three _root plugins_
3. Two _child contexts_ where each _child context_ has
3. Two _child contexts_, each with:
* Two _child plugins_
* One _grandchild context_ where each _grandchild context_ has
* One _grandchild context_, each with:
- Three _child plugins_
Every _child context_ and _grandchild context_ has access to the _root plugins_.
@@ -26,15 +25,18 @@ _child plugins_ registered within the containing _child context_, but the
containing _child context_ **does not** have access to the _child plugins_
registered within its _grandchild context_.
Given that everything in Fastify is a [plugin](./Plugins.md), except for the
Given that everything in Fastify is a [plugin](./Plugins.md) except for the
_root context_, every "context" and "plugin" in this example is a plugin
that can consist of decorators, hooks, plugins, and routes. Thus, to put
this example into concrete terms, consider a basic scenario of a REST API
server that has three routes: the first route (`/one`) requires authentication,
the second route (`/two`) does not, and the third route (`/three`) has
access to the same context as the second route. Using
[@fastify/bearer-auth][bearer] to provide the authentication, the code for this
example is as follows:
that can consist of decorators, hooks, plugins, and routes. As plugins, they
must still signal completion either by returning a Promise (e.g., using `async`
functions) or by calling the `done` function if using the callback style.
To put this
example into concrete terms, consider a basic scenario of a REST API server
with three routes: the first route (`/one`) requires authentication, the
second route (`/two`) does not, and the third route (`/three`) has access to
the same context as the second route. Using [@fastify/bearer-auth][bearer] to
provide authentication, the code for this example is as follows:
```js
'use strict'
@@ -52,9 +54,9 @@ fastify.register(async function authenticatedContext (childServer) {
handler (request, response) {
response.send({
answer: request.answer,
// request.foo will be undefined as it's only defined in publicContext
// request.foo will be undefined as it is only defined in publicContext
foo: request.foo,
// request.bar will be undefined as it's only defined in grandchildContext
// request.bar will be undefined as it is only defined in grandchildContext
bar: request.bar
})
}
@@ -71,7 +73,7 @@ fastify.register(async function publicContext (childServer) {
response.send({
answer: request.answer,
foo: request.foo,
// request.bar will be undefined as it's only defined in grandchildContext
// request.bar will be undefined as it is only defined in grandchildContext
bar: request.bar
})
}
@@ -97,16 +99,16 @@ fastify.register(async function publicContext (childServer) {
fastify.listen({ port: 8000 })
```
The above server example shows all of the encapsulation concepts outlined in the
The server example above demonstrates the encapsulation concepts from the
original diagram:
1. Each _child context_ (`authenticatedContext`, `publicContext`, and
`grandchildContext`) has access to the `answer` request decorator defined in
the _root context_.
`grandchildContext`) has access to the `answer` request decorator defined in
the _root context_.
2. Only the `authenticatedContext` has access to the `@fastify/bearer-auth`
plugin.
plugin.
3. Both the `publicContext` and `grandchildContext` have access to the `foo`
request decorator.
request decorator.
4. Only the `grandchildContext` has access to the `bar` request decorator.
To see this, start the server and issue requests:
@@ -125,16 +127,13 @@ To see this, start the server and issue requests:
## Sharing Between Contexts
<a id="shared-context"></a>
Notice that each context in the prior example inherits _only_ from the parent
contexts. Parent contexts cannot access any entities within their descendent
contexts. This default is occasionally not desired. In such cases, the
encapsulation context can be broken through the usage of
[fastify-plugin][fastify-plugin] such that anything registered in a descendent
context is available to the containing parent context.
Each context in the prior example inherits _only_ from its parent contexts. Parent
contexts cannot access entities within their descendant contexts. If needed,
encapsulation can be broken using [fastify-plugin][fastify-plugin], making
anything registered in a descendant context available to the parent context.
Assuming the `publicContext` needs access to the `bar` decorator defined
within the `grandchildContext` in the previous example, the code can be
rewritten as:
To allow `publicContext` access to the `bar` decorator in `grandchildContext`,
rewrite the code as follows:
```js
'use strict'

View File

@@ -5,7 +5,7 @@
**Table of contents**
- [Errors](#errors)
- [Error Handling In Node.js](#error-handling-in-node.js)
- [Error Handling In Node.js](#error-handling-in-nodejs)
- [Uncaught Errors](#uncaught-errors)
- [Catching Errors In Promises](#catching-errors-in-promises)
- [Errors In Fastify](#errors-in-fastify)
@@ -20,7 +20,6 @@
- [FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN](#fst_err_schema_error_formatter_not_fn)
- [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ](#fst_err_ajv_custom_options_opt_not_obj)
- [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR](#fst_err_ajv_custom_options_opt_not_arr)
- [FST_ERR_VERSION_CONSTRAINT_NOT_STR](#fst_err_version_constraint_not_str)
- [FST_ERR_CTP_ALREADY_PRESENT](#fst_err_ctp_already_present)
- [FST_ERR_CTP_INVALID_TYPE](#fst_err_ctp_invalid_type)
- [FST_ERR_CTP_EMPTY_TYPE](#fst_err_ctp_empty_type)
@@ -30,12 +29,15 @@
- [FST_ERR_CTP_INVALID_MEDIA_TYPE](#fst_err_ctp_invalid_media_type)
- [FST_ERR_CTP_INVALID_CONTENT_LENGTH](#fst_err_ctp_invalid_content_length)
- [FST_ERR_CTP_EMPTY_JSON_BODY](#fst_err_ctp_empty_json_body)
- [FST_ERR_CTP_INVALID_JSON_BODY](#fst_err_ctp_invalid_json_body)
- [FST_ERR_CTP_INSTANCE_ALREADY_STARTED](#fst_err_ctp_instance_already_started)
- [FST_ERR_INSTANCE_ALREADY_LISTENING](#fst_err_instance_already_listening)
- [FST_ERR_DEC_ALREADY_PRESENT](#fst_err_dec_already_present)
- [FST_ERR_DEC_DEPENDENCY_INVALID_TYPE](#fst_err_dec_dependency_invalid_type)
- [FST_ERR_DEC_MISSING_DEPENDENCY](#fst_err_dec_missing_dependency)
- [FST_ERR_DEC_AFTER_START](#fst_err_dec_after_start)
- [FST_ERR_DEC_REFERENCE_TYPE](#fst_err_dec_reference_type)
- [FST_ERR_DEC_UNDECLARED](#fst_err_dec_undeclared)
- [FST_ERR_HOOK_INVALID_TYPE](#fst_err_hook_invalid_type)
- [FST_ERR_HOOK_INVALID_HANDLER](#fst_err_hook_invalid_handler)
- [FST_ERR_HOOK_INVALID_ASYNC_HANDLER](#fst_err_hook_invalid_async_handler)
@@ -44,8 +46,12 @@
- [FST_ERR_HOOK_TIMEOUT](#fst_err_hook_timeout)
- [FST_ERR_LOG_INVALID_DESTINATION](#fst_err_log_invalid_destination)
- [FST_ERR_LOG_INVALID_LOGGER](#fst_err_log_invalid_logger)
- [FST_ERR_LOG_INVALID_LOGGER_INSTANCE](#fst_err_log_invalid_logger_instance)
- [FST_ERR_LOG_INVALID_LOGGER_CONFIG](#fst_err_log_invalid_logger_config)
- [FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED](#fst_err_log_logger_and_logger_instance_provided)
- [FST_ERR_REP_INVALID_PAYLOAD_TYPE](#fst_err_rep_invalid_payload_type)
- [FST_ERR_REP_RESPONSE_BODY_CONSUMED](#fst_err_rep_response_body_consumed)
- [FST_ERR_REP_READABLE_STREAM_LOCKED](#fst_err_rep_readable_stream_locked)
- [FST_ERR_REP_ALREADY_SENT](#fst_err_rep_already_sent)
- [FST_ERR_REP_SENT_VALUE](#fst_err_rep_sent_value)
- [FST_ERR_SEND_INSIDE_ONERR](#fst_err_send_inside_onerr)
@@ -64,13 +70,11 @@
- [FST_ERR_SCH_VALIDATION_BUILD](#fst_err_sch_validation_build)
- [FST_ERR_SCH_SERIALIZATION_BUILD](#fst_err_sch_serialization_build)
- [FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX](#fst_err_sch_response_schema_not_nested_2xx)
- [FST_ERR_HTTP2_INVALID_VERSION](#fst_err_http2_invalid_version)
- [FST_ERR_INIT_OPTS_INVALID](#fst_err_init_opts_invalid)
- [FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE](#fst_err_force_close_connections_idle_not_available)
- [FST_ERR_DUPLICATED_ROUTE](#fst_err_duplicated_route)
- [FST_ERR_BAD_URL](#fst_err_bad_url)
- [FST_ERR_ASYNC_CONSTRAINT](#fst_err_async_constraint)
- [FST_ERR_DEFAULT_ROUTE_INVALID_TYPE](#fst_err_default_route_invalid_type)
- [FST_ERR_INVALID_URL](#fst_err_invalid_url)
- [FST_ERR_ROUTE_OPTIONS_NOT_OBJ](#fst_err_route_options_not_obj)
- [FST_ERR_ROUTE_DUPLICATED_HANDLER](#fst_err_route_duplicated_handler)
@@ -90,16 +94,18 @@
- [FST_ERR_PARENT_PLUGIN_BOOTED](#fst_err_parent_plugin_booted)
- [FST_ERR_PLUGIN_TIMEOUT](#fst_err_plugin_timeout)
- [FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE](#fst_err_plugin_not_present_in_instance)
- [FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER](#fst_err_plugin_invalid_async_handler)
- [FST_ERR_VALIDATION](#fst_err_validation)
- [FST_ERR_LISTEN_OPTIONS_INVALID](#fst_err_listen_options_invalid)
- [FST_ERR_ERROR_HANDLER_NOT_FN](#fst_err_error_handler_not_fn)
- [FST_ERR_ERROR_HANDLER_ALREADY_SET](#fst_err_error_handler_already_set)
### Error Handling In Node.js
<a id="error-handling"></a>
#### Uncaught Errors
In Node.js, uncaught errors are likely to cause memory leaks, file descriptor
leaks, and other major production issues.
In Node.js, uncaught errors can cause memory leaks, file descriptor leaks, and
other major production issues.
[Domains](https://nodejs.org/en/docs/guides/domain-postmortem/) were a failed
attempt to fix this.
@@ -108,29 +114,28 @@ way to deal with them is to
[crash](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly).
#### Catching Errors In Promises
If you are using promises, you should attach a `.catch()` handler synchronously.
When using promises, attach a `.catch()` handler synchronously.
### Errors In Fastify
Fastify follows an all-or-nothing approach and aims to be lean and optimal as
much as possible. The developer is responsible for making sure that the errors
are handled properly.
Fastify follows an all-or-nothing approach and aims to be lean and optimal. The
developer is responsible for ensuring errors are handled properly.
#### Errors In Input Data
Most errors are a result of unexpected input data, so we recommend [validating
your input data against a JSON schema](./Validation-and-Serialization.md).
Most errors result from unexpected input data, so it is recommended to
[validate input data against a JSON schema](./Validation-and-Serialization.md).
#### Catching Uncaught Errors In Fastify
Fastify tries to catch as many uncaught errors as it can without hindering
Fastify tries to catch as many uncaught errors as possible without hindering
performance. This includes:
1. synchronous routes, e.g. `app.get('/', () => { throw new Error('kaboom') })`
2. `async` routes, e.g. `app.get('/', async () => { throw new Error('kaboom')
})`
The error in both cases will be caught safely and routed to Fastify's default
error handler for a generic `500 Internal Server Error` response.
In both cases, the error will be caught safely and routed to Fastify's default
error handler, resulting in a generic `500 Internal Server Error` response.
To customize this behavior you should use
To customize this behavior, use
[`setErrorHandler`](./Server.md#seterrorhandler).
### Errors In Fastify Lifecycle Hooks And A Custom Error Handler
@@ -140,52 +145,50 @@ From the [Hooks documentation](./Hooks.md#manage-errors-from-a-hook):
> `done()` and Fastify will automatically close the request and send the
> appropriate error code to the user.
When a custom error handler has been defined through
[`setErrorHandler`](./Server.md#seterrorhandler), the custom error handler will
receive the error passed to the `done()` callback (or through other supported
automatic error handling mechanisms). If `setErrorHandler` has been used
multiple times to define multiple handlers, the error will be routed to the most
precedent handler defined within the error [encapsulation
context](./Encapsulation.md). Error handlers are fully encapsulated, so a
`setErrorHandler` call within a plugin will limit the error handler to that
plugin's context.
When a custom error handler is defined through
[`setErrorHandler`](./Server.md#seterrorhandler), it will receive the error
passed to the `done()` callback or through other supported automatic error
handling mechanisms. If `setErrorHandler` is used multiple times, the error will
be routed to the most precedent handler within the error
[encapsulation context](./Encapsulation.md). Error handlers are fully
encapsulated, so a `setErrorHandler` call within a plugin will limit the error
handler to that plugin's context.
The root error handler is Fastify's generic error handler. This error handler
will use the headers and status code in the `Error` object, if they exist. The
headers and status code will not be automatically set if a custom error handler
is provided.
Some things to consider in your custom error handler:
The following should be considered when using a custom error handler:
- you can `reply.send(data)`, which will behave as it would in [regular route
handlers](./Reply.md#senddata)
- `reply.send(data)` behaves as in [regular route handlers](./Reply.md#senddata)
- objects are serialized, triggering the `preSerialization` lifecycle hook if
you have one defined
- strings, buffers, and streams are sent to the client, with appropriate
headers (no serialization)
defined
- strings, buffers, and streams are sent to the client with appropriate headers
(no serialization)
- You can throw a new error in your custom error handler - errors (new error or
the received error parameter re-thrown) - will call the parent `errorHandler`.
- `onError` hook will be triggered once only for the first error being thrown.
- an error will not be triggered twice from a lifecycle hook - Fastify
internally monitors the error invocation to avoid infinite loops for errors
thrown in the reply phases of the lifecycle. (those after the route handler)
- Throwing a new error in a custom error handler will call the parent
`errorHandler`.
- The `onError` hook will be triggered once for the first error thrown
- An error will not be triggered twice from a lifecycle hook. Fastify
internally monitors error invocation to avoid infinite loops for errors
thrown in the reply phases of the lifecycle (those after the route handler)
When utilizing Fastify's custom error handling through [`setErrorHandler`](./Server.md#seterrorhandler),
you should be aware of how errors are propagated between custom and default
error handlers.
When using Fastify's custom error handling through
[`setErrorHandler`](./Server.md#seterrorhandler), be aware of how errors are
propagated between custom and default error handlers.
If a plugin's error handler re-throws an error, and the error is not an
instance of [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
(as seen in the `/bad` route in the following example), it will not propagate
to the parent context error handler. Instead, it will be caught by the default
error handler.
If a plugin's error handler re-throws an error that is not an instance of
[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error),
it will not propagate to the parent context error handler. Instead, it will be
caught by the default error handler. This can be seen in the `/bad` route of the
example below.
To ensure consistent error handling, it is recommended to throw instances of
`Error`. For instance, in the following example, replacing `throw 'foo'` with
`throw new Error('foo')` in the `/bad` route ensures that errors propagate through
the custom error handling chain as intended. This practice helps avoid potential
pitfalls when working with custom error handling in Fastify.
To ensure consistent error handling, throw instances of `Error`. For example,
replace `throw 'foo'` with `throw new Error('foo')` in the `/bad` route to
ensure errors propagate through the custom error handling chain as intended.
This practice helps avoid potential pitfalls when working with custom error
handling in Fastify.
For example:
```js
@@ -238,7 +241,7 @@ You can access `errorCodes` for mapping:
// ESM
import { errorCodes } from 'fastify'
// CommonJs
// CommonJS
const errorCodes = require('fastify').errorCodes
```
@@ -263,7 +266,7 @@ fastify.setErrorHandler(function (error, request, reply) {
// Send error response
reply.status(500).send({ ok: false })
} else {
// fastify will use parent error handler to handle this
// Fastify will use parent error handler to handle this
reply.send(error)
}
})
@@ -278,7 +281,7 @@ fastify.listen({ port: 3000 }, function (err, address) {
})
```
Below is a table with all the error codes that Fastify uses.
Below is a table with all the error codes used by Fastify.
| Code | Description | How to solve | Discussion |
|------|-------------|--------------|------------|
@@ -289,7 +292,6 @@ Below is a table with all the error codes that Fastify uses.
| <a id="fst_err_schema_error_formatter_not_fn">FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN</a> | SchemaErrorFormatter option wrongly specified. | SchemaErrorFormatter option should be a non async function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_ajv_custom_options_opt_not_obj">FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ</a> | ajv.customOptions wrongly specified. | ajv.customOptions option should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_ajv_custom_options_opt_not_arr">FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR</a> | ajv.plugins option wrongly specified. | ajv.plugins option should be an array. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_version_constraint_not_str">FST_ERR_VERSION_CONSTRAINT_NOT_STR</a> | Version constraint wrongly specified. | Version constraint should be a string. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_ctp_already_present">FST_ERR_CTP_ALREADY_PRESENT</a> | The parser for this content type was already registered. | Use a different content type or delete the already registered parser. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_ctp_invalid_type">FST_ERR_CTP_INVALID_TYPE</a> | `Content-Type` wrongly specified | The `Content-Type` should be a string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_ctp_empty_type">FST_ERR_CTP_EMPTY_TYPE</a> | `Content-Type` is an empty string. | `Content-Type` cannot be an empty string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
@@ -298,13 +300,16 @@ Below is a table with all the error codes that Fastify uses.
| <a id="fst_err_ctp_body_too_large">FST_ERR_CTP_BODY_TOO_LARGE</a> | The request body is larger than the provided limit. | Increase the limit in the Fastify server instance setting: [bodyLimit](./Server.md#bodylimit) | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_ctp_invalid_media_type">FST_ERR_CTP_INVALID_MEDIA_TYPE</a> | The received media type is not supported (i.e. there is no suitable `Content-Type` parser for it). | Use a different content type. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_ctp_invalid_content_length">FST_ERR_CTP_INVALID_CONTENT_LENGTH</a> | Request body size did not match <code>Content-Length</code>. | Check the request body size and the <code>Content-Length</code> header. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_ctp_empty_json_body">FST_ERR_CTP_EMPTY_JSON_BODY</a> | Body cannot be empty when content-type is set to <code>application/json</code>. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) |
| <a id="fst_err_ctp_empty_json_body">FST_ERR_CTP_EMPTY_JSON_BODY</a> | Body is not valid JSON but content-type is set to <code>application/json</code>. | Check if the request body is valid JSON. | [#5925](https://github.com/fastify/fastify/pull/5925) |
| <a id="fst_err_ctp_invalid_json_body">FST_ERR_CTP_INVALID_JSON_BODY</a> | Body cannot be empty when content-type is set to <code>application/json</code>. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) |
| <a id="fst_err_ctp_instance_already_started">FST_ERR_CTP_INSTANCE_ALREADY_STARTED</a> | Fastify is already started. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_instance_already_listening">FST_ERR_INSTANCE_ALREADY_LISTENING</a> | Fastify instance is already listening. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_dec_already_present">FST_ERR_DEC_ALREADY_PRESENT</a> | A decorator with the same name is already registered. | Use a different decorator name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_dec_dependency_invalid_type">FST_ERR_DEC_DEPENDENCY_INVALID_TYPE</a> | The dependencies of decorator must be of type `Array`. | Use an array for the dependencies. | [#3090](https://github.com/fastify/fastify/pull/3090) |
| <a id="fst_err_dec_missing_dependency">FST_ERR_DEC_MISSING_DEPENDENCY</a> | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_dec_after_start">FST_ERR_DEC_AFTER_START</a> | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) |
| <a id="fst_err_dec_reference_type">FST_ERR_DEC_REFERENCE_TYPE</a> | The decorator cannot be a reference type. | Define the decorator with a getter/setter interface or an empty decorator with a hook. | [#5462](https://github.com/fastify/fastify/pull/5462) |
| <a id="fst_err_dec_undeclared">FST_ERR_DEC_UNDECLARED</a> | An attempt was made to access a decorator that has not been declared. | Declare the decorator before using it. | [#](https://github.com/fastify/fastify/pull/)
| <a id="fst_err_hook_invalid_type">FST_ERR_HOOK_INVALID_TYPE</a> | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_hook_invalid_handler">FST_ERR_HOOK_INVALID_HANDLER</a> | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_hook_invalid_async_handler">FST_ERR_HOOK_INVALID_ASYNC_HANDLER</a> | Async function has too many arguments. Async hooks should not use the `done` argument. | Remove the `done` argument from the async hook. | [#4367](https://github.com/fastify/fastify/pull/4367) |
@@ -313,8 +318,12 @@ Below is a table with all the error codes that Fastify uses.
| <a id="fst_err_hook_timeout">FST_ERR_HOOK_TIMEOUT</a> | A callback for a hook timed out. | Increase the timeout for the hook. | [#3106](https://github.com/fastify/fastify/pull/3106) |
| <a id="fst_err_log_invalid_destination">FST_ERR_LOG_INVALID_DESTINATION</a> | The logger does not accept the specified destination. | Use a `'stream'` or a `'file'` as the destination. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_log_invalid_logger">FST_ERR_LOG_INVALID_LOGGER</a> | The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. | Use a logger with all the required methods. | [#4520](https://github.com/fastify/fastify/pull/4520) |
| <a id="fst_err_log_invalid_logger_instance">FST_ERR_LOG_INVALID_LOGGER_INSTANCE</a> | The `loggerInstance` only accepts a logger instance, not a configuration object. | To pass a configuration object, use `'logger'` instead. | [#5020](https://github.com/fastify/fastify/pull/5020) |
| <a id="fst_err_log_invalid_logger_config">FST_ERR_LOG_INVALID_LOGGER_CONFIG</a> | The logger option only accepts a configuration object, not a logger instance. | To pass an instance, use `'loggerInstance'` instead. | [#5020](https://github.com/fastify/fastify/pull/5020) |
| <a id="fst_err_log_logger_and_logger_instance_provided">FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED</a> | You cannot provide both `'logger'` and `'loggerInstance'`. | Please provide only one option. | [#5020](https://github.com/fastify/fastify/pull/5020) |
| <a id="fst_err_rep_invalid_payload_type">FST_ERR_REP_INVALID_PAYLOAD_TYPE</a> | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) |
| <a id="fst_err_rep_response_body_consumed">FST_ERR_REP_RESPONSE_BODY_CONSUMED</a> | Using `Response` as reply payload, but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) |
| <a id="fst_err_rep_readable_stream_locked">FST_ERR_REP_READABLE_STREAM_LOCKED</a> | Using `ReadableStream` as reply payload, but locked with another reader. | Make sure you don't call the `Readable.getReader` before sending or release lock with `reader.releaseLock()` before sending. | [#5920](https://github.com/fastify/fastify/pull/5920) |
| <a id="fst_err_rep_already_sent">FST_ERR_REP_ALREADY_SENT</a> | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
| <a id="fst_err_rep_sent_value">FST_ERR_REP_SENT_VALUE</a> | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
| <a id="fst_err_send_inside_onerr">FST_ERR_SEND_INSIDE_ONERR</a> | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) |
@@ -333,13 +342,11 @@ Below is a table with all the error codes that Fastify uses.
| <a id="fst_err_sch_validation_build">FST_ERR_SCH_VALIDATION_BUILD</a> | The JSON schema provided for validation to a route is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) |
| <a id="fst_err_sch_serialization_build">FST_ERR_SCH_SERIALIZATION_BUILD</a> | The JSON schema provided for serialization of a route response is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) |
| <a id="fst_err_sch_response_schema_not_nested_2xx">FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX</a> | Response schemas should be nested under a valid status code (2XX). | Use a valid status code. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_http2_invalid_version">FST_ERR_HTTP2_INVALID_VERSION</a> | HTTP2 is available only from node >= 8.8.1. | Use a higher version of node. | [#1346](https://github.com/fastify/fastify/pull/1346) |
| <a id="fst_err_init_opts_invalid">FST_ERR_INIT_OPTS_INVALID</a> | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) |
| <a id="fst_err_force_close_connections_idle_not_available">FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE</a> | Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. | Use a different value for `forceCloseConnections`. | [#3925](https://github.com/fastify/fastify/pull/3925) |
| <a id="fst_err_duplicated_route">FST_ERR_DUPLICATED_ROUTE</a> | The HTTP method already has a registered controller for that URL. | Use a different URL or register the controller for another HTTP method. | [#2954](https://github.com/fastify/fastify/pull/2954) |
| <a id="fst_err_bad_url">FST_ERR_BAD_URL</a> | The router received an invalid URL. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) |
| <a id="fst_err_async_constraint">FST_ERR_ASYNC_CONSTRAINT</a> | The router received an error when using asynchronous constraints. | - | [#4323](https://github.com/fastify/fastify/pull/4323) |
| <a id="fst_err_default_route_invalid_type">FST_ERR_DEFAULT_ROUTE_INVALID_TYPE</a> | The `defaultRoute` type should be a function. | Use a function for the `defaultRoute`. | [#2733](https://github.com/fastify/fastify/pull/2733) |
| <a id="fst_err_invalid_url">FST_ERR_INVALID_URL</a> | URL must be a string. | Use a string for the URL. | [#3653](https://github.com/fastify/fastify/pull/3653) |
| <a id="fst_err_route_options_not_obj">FST_ERR_ROUTE_OPTIONS_NOT_OBJ</a> | Options for the route must be an object. | Use an object for the route options. | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_route_duplicated_handler">FST_ERR_ROUTE_DUPLICATED_HANDLER</a> | Duplicate handler for the route is not allowed. | Use a different handler. | [#4554](https://github.com/fastify/fastify/pull/4554) |
@@ -359,7 +366,7 @@ Below is a table with all the error codes that Fastify uses.
| <a id="fst_err_parent_plugin_booted">FST_ERR_PARENT_PLUGIN_BOOTED</a> | Impossible to load plugin because the parent (mapped directly from `avvio`) | - | [#3106](https://github.com/fastify/fastify/pull/3106) |
| <a id="fst_err_plugin_timeout">FST_ERR_PLUGIN_TIMEOUT</a> | Plugin did not start in time. | Increase the timeout for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) |
| <a id="fst_err_plugin_not_present_in_instance">FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE</a> | The decorator is not present in the instance. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
| <a id="fst_err_plugin_invalid_async_handler">FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER</a> | The plugin being registered mixes async and callback styles. | - | [#5141](https://github.com/fastify/fastify/pull/5141) |
| <a id="fst_err_validation">FST_ERR_VALIDATION</a> | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) |
| <a id="fst_err_listen_options_invalid">FST_ERR_LISTEN_OPTIONS_INVALID</a> | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) |
| <a id="fst_err_error_handler_not_fn">FST_ERR_ERROR_HANDLER_NOT_FN</a> | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) |
| <a id="fst_err_error_handler_not_fn">FST_ERR_ERROR_HANDLER_NOT_FN</a> | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | <a id="fst_err_error_handler_already_set">FST_ERR_ERROR_HANDLER_ALREADY_SET</a> | Error Handler already set in this scope. Set `allowErrorHandlerOverride: true` to allow overriding. | By default, `setErrorHandler` can only be called once per encapsulation context. | [#6097](https://github.com/fastify/fastify/pull/6098) |

View File

@@ -2,11 +2,11 @@
## HTTP2
_Fastify_ supports HTTP2 over either HTTPS (h2) or plaintext (h2c).
_Fastify_ supports HTTP2 over HTTPS (h2) or plaintext (h2c).
Currently, none of the HTTP2-specific APIs are available through _Fastify_, but
Node's `req` and `res` can be accessed through our `Request` and `Reply`
interface. PRs are welcome.
Node's `req` and `res` can be accessed through the `Request` and `Reply`
interfaces. PRs are welcome.
### Secure (HTTPS)
@@ -61,7 +61,7 @@ fastify.get('/', function (request, reply) {
fastify.listen({ port: 3000 })
```
You can test your new server with:
Test the new server with:
```
$ npx h2url https://localhost:3000
@@ -69,8 +69,8 @@ $ npx h2url https://localhost:3000
### Plain or insecure
If you are building microservices, you can connect to HTTP2 in plain text,
however, this is not supported by browsers.
For microservices, HTTP2 can connect in plain text, but this is not
supported by browsers.
```js
'use strict'
@@ -86,7 +86,7 @@ fastify.get('/', function (request, reply) {
fastify.listen({ port: 3000 })
```
You can test your new server with:
Test the new server with:
```
$ npx h2url http://localhost:3000

View File

@@ -34,9 +34,9 @@ are Request/Reply hooks and application hooks:
- [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties)
- [Diagnostics Channel Hooks](#diagnostics-channel-hooks)
**Notice:** the `done` callback is not available when using `async`/`await` or
returning a `Promise`. If you do invoke a `done` callback in this situation
unexpected behavior may occur, e.g. duplicate invocation of handlers.
> Note: The `done` callback is not available when using `async`/`await` or
> returning a `Promise`. If you do invoke a `done` callback in this situation
> unexpected behavior may occur, e.g. duplicate invocation of handlers.
## Request/Reply Hooks
@@ -68,9 +68,9 @@ fastify.addHook('onRequest', async (request, reply) => {
})
```
**Notice:** in the [onRequest](#onrequest) hook, `request.body` will always be
`undefined`, because the body parsing happens before the
[preValidation](#prevalidation) hook.
> Note: In the [onRequest](#onrequest) hook, `request.body` will always be
> `undefined`, because the body parsing happens before the
> [preValidation](#prevalidation) hook.
### preParsing
@@ -98,17 +98,17 @@ fastify.addHook('preParsing', async (request, reply, payload) => {
})
```
**Notice:** in the [preParsing](#preparsing) hook, `request.body` will always be
`undefined`, because the body parsing happens before the
[preValidation](#prevalidation) hook.
> Note: In the [preParsing](#preparsing) hook, `request.body` will always be
> `undefined`, because the body parsing happens before the
> [preValidation](#prevalidation) hook.
**Notice:** you should also add a `receivedEncodedLength` property to the
returned stream. This property is used to correctly match the request payload
with the `Content-Length` header value. Ideally, this property should be updated
on each received chunk.
> Note: You should also add a `receivedEncodedLength` property to the
> returned stream. This property is used to correctly match the request payload
> with the `Content-Length` header value. Ideally, this property should be updated
> on each received chunk.
**Notice:** The size of the returned stream is checked to not exceed the limit
set in [`bodyLimit`](./Server.md#bodylimit) option.
> Note: The size of the returned stream is checked to not exceed the limit
> set in [`bodyLimit`](./Server.md#bodylimit) option.
### preValidation
@@ -166,8 +166,8 @@ fastify.addHook('preSerialization', async (request, reply, payload) => {
})
```
Note: the hook is NOT called if the payload is a `string`, a `Buffer`, a
`stream`, or `null`.
> Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a
> `stream`, or `null`.
### onError
```js
@@ -189,15 +189,11 @@ specific header in case of error.
It is not intended for changing the error, and calling `reply.send` will throw
an exception.
This hook will be executed only after
the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler)
has been executed, and only if the custom error handler sends an error back to the
user
*(Note that the default error handler always sends the error back to the
user)*.
This hook will be executed before
the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler).
**Notice:** unlike the other hooks, passing an error to the `done` function is not
supported.
> Note: Unlike the other hooks, passing an error to the `done` function is not
> supported.
### onSend
If you are using the `onSend` hook, you can change the payload. For example:
@@ -233,8 +229,8 @@ fastify.addHook('onSend', (request, reply, payload, done) => {
> to `0`, whereas the `Content-Length` header will not be set if the payload is
> `null`.
Note: If you change the payload, you may only change it to a `string`, a
`Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`.
> Note: If you change the payload, you may only change it to a `string`, a
> `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`.
### onResponse
@@ -256,8 +252,8 @@ The `onResponse` hook is executed when a response has been sent, so you will not
be able to send more data to the client. It can however be useful for sending
data to external services, for example, to gather statistics.
**Note:** setting `disableRequestLogging` to `true` will disable any error log
inside the `onResponse` hook. In this case use `try - catch` to log errors.
> Note: Setting `disableRequestLogging` to `true` will disable any error log
> inside the `onResponse` hook. In this case use `try - catch` to log errors.
### onTimeout
@@ -298,7 +294,8 @@ The `onRequestAbort` hook is executed when a client closes the connection before
the entire request has been processed. Therefore, you will not be able to send
data to the client.
**Notice:** client abort detection is not completely reliable. See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md)
> Note: Client abort detection is not completely reliable.
> See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md)
### Manage Errors from a hook
If you get an error during the execution of your hook, just pass it to `done()`
@@ -428,8 +425,8 @@ fastify.addHook('onReady', async function () {
### onListen
Triggered when the server starts listening for requests. The hooks run one
after another. If a hook function causes an error, it is logged and
Triggered when the server starts listening for requests. The hooks run one
after another. If a hook function causes an error, it is logged and
ignored, allowing the queue of hooks to continue. Hook functions accept one
argument: a callback, `done`, to be invoked after the hook function is
complete. Hook functions are invoked with `this` bound to the associated
@@ -451,8 +448,8 @@ fastify.addHook('onListen', async function () {
})
```
> **Note**
> This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()`
> Note: This hook will not run when the server is started using
> fastify.inject()` or `fastify.ready()`.
### onClose
<a id="on-close"></a>
@@ -462,7 +459,7 @@ HTTP requests have been completed.
It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example,
to close an open connection to a database.
The hook function takes the Fastify instance as a first argument,
The hook function takes the Fastify instance as a first argument,
and a `done` callback for synchronous hook functions.
```js
// callback style
@@ -575,8 +572,8 @@ This hook can be useful if you are developing a plugin that needs to know when a
plugin context is formed, and you want to operate in that specific context, thus
this hook is encapsulated.
**Note:** This hook will not be called if a plugin is wrapped inside
[`fastify-plugin`](https://github.com/fastify/fastify-plugin).
> Note: This hook will not be called if a plugin is wrapped inside
> [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
```js
fastify.decorate('data', [])
@@ -773,7 +770,7 @@ fastify.route({
})
```
**Note**: both options also accept an array of functions.
> Note: Both options also accept an array of functions.
## Using Hooks to Inject Custom Properties
<a id="using-hooks-to-inject-custom-properties"></a>
@@ -828,19 +825,11 @@ consider creating a custom [Plugin](./Plugins.md) instead.
## Diagnostics Channel Hooks
> **Note:** The `diagnostics_channel` is currently experimental on Node.js, so
> its API is subject to change even in semver-patch releases of Node.js. For
> versions of Node.js supported by Fastify where `diagnostics_channel` is
> unavailable, the hook will use the
> [polyfill](https://www.npmjs.com/package/diagnostics_channel) if it is
> available. Otherwise, this feature will not be present.
Currently, one
[`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) publish
event, `'fastify.initialization'`, happens at initialization time. The Fastify
instance is passed into the hook as a property of the object passed in. At this
point, the instance can be interacted with to add hooks, plugins, routes, or any
other sort of modification.
One [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)
publish event, `'fastify.initialization'`, happens at initialization time. The
Fastify instance is passed into the hook as a property of the object passed in.
At this point, the instance can be interacted with to add hooks, plugins,
routes, or any other sort of modification.
For example, a tracing package might do something like the following (which is,
of course, a simplification). This would be in a file loaded in the
@@ -855,7 +844,7 @@ const spans = new WeakMap()
channel.subscribe(function ({ fastify }) {
fastify.addHook('onRequest', (request, reply, done) => {
const span = tracer.startSpan('fastify.request')
const span = tracer.startSpan('fastify.request.handler')
spans.set(request, span)
done()
})
@@ -867,3 +856,41 @@ channel.subscribe(function ({ fastify }) {
})
})
```
> Note: The TracingChannel class API is currently experimental and may undergo
> breaking changes even in semver-patch releases of Node.js.
Five other events are published on a per-request basis following the
[Tracing Channel](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel)
nomenclature. The list of the channel names and the event they receive is:
- `tracing:fastify.request.handler:start`: Always fires
- `{ request: Request, reply: Reply, route: { url, method } }`
- `tracing:fastify.request.handler:end`: Always fires
- `{ request: Request, reply: Reply, route: { url, method }, async: Bool }`
- `tracing:fastify.request.handler:asyncStart`: Fires for promise/async handlers
- `{ request: Request, reply: Reply, route: { url, method } }`
- `tracing:fastify.request.handler:asyncEnd`: Fires for promise/async handlers
- `{ request: Request, reply: Reply, route: { url, method } }`
- `tracing:fastify.request.handler:error`: Fires when an error occurs
- `{ request: Request, reply: Reply, route: { url, method }, error: Error }`
The object instance remains the same for all events associated with a given
request. All payloads include a `request` and `reply` property which are an
instance of Fastify's `Request` and `Reply` instances. They also include a
`route` property which is an object with the matched `url` pattern (e.g.
`/collection/:id`) and the `method` HTTP method (e.g. `GET`). The `:start` and
`:end` events always fire for requests. If a request handler is an `async`
function or one that returns a `Promise` then the `:asyncStart` and `:asyncEnd`
events also fire. Finally, the `:error` event contains an `error` property
associated with the request's failure.
These events can be received like so:
```js
const dc = require('node:diagnostics_channel')
const channel = dc.channel('tracing:fastify.request.handler:start')
channel.subscribe((msg) => {
console.log(msg.request, msg.reply)
})
```

View File

@@ -1,8 +1,7 @@
<h1 align="center">Fastify</h1>
## Long Term Support
`<a id="lts"></a>`
<a id="lts"></a>
Fastify's Long Term Support (LTS) is provided according to the schedule laid out
in this document:
@@ -25,13 +24,11 @@ in this document:
and verified against alternative runtimes that are compatible with Node.js.
The maintenance teams of these alternative runtimes are responsible for ensuring
and guaranteeing these tests work properly.
1. [N|Solid](https://docs.nodesource.com/nsolid), maintained by NodeSource,
commits to testing and verifying each Fastify major release against the N|Solid
LTS versions that are current at the time of the Fastify release.
NodeSource guarantees that Fastify will be compatible and function correctly
with N|Solid, aligning with the support and compatibility scope of the N|Solid
LTS versions available at the time of the Fastify release.
This ensures users of N|Solid can confidently use Fastify.
1. [N|Solid](https://docs.nodesource.com/docs/product_suite) tests and
verifies each Fastify major release against current N|Solid LTS versions.
NodeSource ensures Fastify compatibility with N|Solid, aligning with the
support scope of N|Solid LTS versions at the time of the Fastify release.
This guarantees N|Solid users can confidently use Fastify.
A "month" is defined as 30 consecutive days.
@@ -41,27 +38,32 @@ A "month" is defined as 30 consecutive days.
> occasions where we need to release breaking changes as a _minor_ version
> release. Such changes will _always_ be noted in the [release
> notes](https://github.com/fastify/fastify/releases).
>
>
> To avoid automatically receiving breaking security updates it is possible to
> use the tilde (`~`) range qualifier. For example, to get patches for the 3.15
> release, and avoid automatically updating to the 3.16 release, specify the
> dependency as `"fastify": "~3.15.x"`. This will leave your application
> vulnerable, so please use with caution.
> vulnerable, so please use it with caution.
### Security Support Beyond LTS
Fastify's partner, HeroDevs, provides commercial security support through the
OpenJS Ecosystem Sustainability Program for versions of Fastify that are EOL.
For more information, see their [Never Ending Support][hd-link] service.
### Schedule
`<a id="lts-schedule"></a>`
<a id="lts-schedule"></a>
| Version | Release Date | End Of LTS Date | Node.js | Nsolid(Node) |
| :------ | :----------- | :-------------- | :----------------- | :------------- |
| 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | |
| 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | |
| 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | v5(18) |
| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 | v5(18), v5(20) |
| 4.0.0 | 2022-06-08 | 2025-06-30 | 14, 16, 18, 20, 22 | v5(18), v5(20) |
| 5.0.0 | 2024-09-17 | TBD | 20, 22 | v5(20) |
### CI tested operating systems
`<a id="supported-os"></a>`
<a id="supported-os"></a>
Fastify uses GitHub Actions for CI testing, please refer to [GitHub&#39;s
documentation regarding workflow
@@ -71,12 +73,14 @@ YAML workflow labels below:
| OS | YAML Workflow Label | Package Manager | Node.js | Nsolid(Node) |
| ------- | ------------------- | --------------- | ----------- | ------------- |
| Linux | `ubuntu-latest` | npm | 14,16,18,20 | v5(18),v5(20) |
| Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 | v5(18),v5(20) |
| Windows | `windows-latest` | npm | 14,16,18,20 | v5(18),v5(20) |
| MacOS | `macos-latest` | npm | 14,16,18,20 | v5(18),v5(20) |
| Linux | `ubuntu-latest` | npm | 20 | v5(20) |
| Linux | `ubuntu-latest` | yarn,pnpm | 20 | v5(20) |
| Windows | `windows-latest` | npm | 20 | v5(20) |
| MacOS | `macos-latest` | npm | 20 | v5(20) |
Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines`
flag.
[semver]: https://semver.org/
[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=eol_support_fastify

View File

@@ -3,12 +3,11 @@
## Lifecycle
<a id="lifecycle"></a>
Following the schema of the internal lifecycle of Fastify.
This schema shows the internal lifecycle of Fastify.
On the right branch of every section there is the next phase of the lifecycle,
on the left branch there is the corresponding error code that will be generated
if the parent throws an error *(note that all the errors are automatically
handled by Fastify)*.
The right branch of each section shows the next phase of the lifecycle. The left
branch shows the corresponding error code generated if the parent throws an
error. All errors are automatically handled by Fastify.
```
Incoming Request
@@ -42,26 +41,23 @@ Incoming Request
└─▶ onResponse Hook
```
At any point before or during the `User Handler`, `reply.hijack()` can be called
to prevent Fastify from:
- Running all the following hooks and user handler
- Sending the response automatically
Before or during the `User Handler`, `reply.hijack()` can be called to:
- Prevent Fastify from running subsequent hooks and the user handler
- Prevent Fastify from sending the response automatically
NB (*): If `reply.raw` is used to send a response back to the user, `onResponse`
hooks will still be executed
If `reply.raw` is used to send a response, `onResponse` hooks will still
be executed.
## Reply Lifecycle
<a id="reply-lifecycle"></a>
Whenever the user handles the request, the result may be:
When the user handles the request, the result may be:
- in async handler: it returns a payload
- in async handler: it throws an `Error`
- in sync handler: it sends a payload
- in sync handler: it sends an `Error` instance
- In an async handler: it returns a payload or throws an `Error`
- In a sync handler: it sends a payload or an `Error` instance
If the reply was hijacked, we skip all the below steps. Otherwise, when it is
being submitted, the data flow performed is the following:
If the reply was hijacked, all subsequent steps are skipped. Otherwise, when
submitted, the data flow is as follows:
```
★ schema validation Error
@@ -74,16 +70,15 @@ being submitted, the data flow performed is the following:
★ send or return │ │
│ │ │
│ ▼ │
reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler ◀─────┘
reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook ◀───────┘
reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook
reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler
└─▶ reply sent
```
Note: `reply sent` means that the JSON payload will be serialized by:
- the [reply serialized](./Server.md#setreplyserializer) if set
- or by the [serializer compiler](./Server.md#setserializercompiler) when a JSON
schema has been set for the returning HTTP status code
- or by the default `JSON.stringify` function
`reply sent` means the JSON payload will be serialized by one of the following:
- The [reply serializer](./Server.md#setreplyserializer) if set
- The [serializer compiler](./Server.md#setserializercompiler) if a JSON schema
is set for the HTTP status code
- The default `JSON.stringify` function

View File

@@ -2,17 +2,18 @@
## Logging
### Enable logging
Logging is disabled by default, and you can enable it by passing `{ logger: true
}` or `{ logger: { level: 'info' } }` when you create a Fastify instance. Note
that if the logger is disabled, it is impossible to enable it at runtime. We use
[abstract-logging](https://www.npmjs.com/package/abstract-logging) for this
purpose.
### Enable Logging
Logging is disabled by default. Enable it by passing `{ logger: true }` or
`{ logger: { level: 'info' } }` when creating a Fastify instance. Note that if
the logger is disabled, it cannot be enabled at runtime.
[abstract-logging](https://www.npmjs.com/package/abstract-logging) is used for
this purpose.
As Fastify is focused on performance, it uses
[pino](https://github.com/pinojs/pino) as its logger, with the default log
level, when enabled, set to `'info'`.
level set to `'info'` when enabled.
#### Basic logging setup
Enabling the production JSON logger:
```js
@@ -21,8 +22,9 @@ const fastify = require('fastify')({
})
```
Enabling the logger with appropriate configuration for both local development
and production and test environment requires a bit more configuration:
#### Environment-Specific Configuration
Enabling the logger with appropriate configuration for local development,
production, and test environments requires more configuration:
```js
const envToLogger = {
@@ -42,11 +44,11 @@ const fastify = require('fastify')({
logger: envToLogger[environment] ?? true // defaults to true if no entry matches in the map
})
```
⚠️ `pino-pretty` needs to be installed as a dev dependency, it is not included
⚠️ `pino-pretty` needs to be installed as a dev dependency. It is not included
by default for performance reasons.
### Usage
You can use the logger like this in your route handlers:
The logger can be used in route handlers as follows:
```js
fastify.get('/', options, function (request, reply) {
@@ -55,16 +57,16 @@ fastify.get('/', options, function (request, reply) {
})
```
You can trigger new logs outside route handlers by using the Pino instance from
the Fastify instance:
Trigger new logs outside route handlers using the Pino instance from the Fastify
instance:
```js
fastify.log.info('Something important happened!');
```
If you want to pass some options to the logger, just pass them to Fastify.
You can find all available options in the
[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options).
If you want to specify a file destination, use:
#### Passing Logger Options
To pass options to the logger, provide them to Fastify. See the
[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options)
for available options. To specify a file destination, use:
```js
const fastify = require('fastify')({
@@ -80,8 +82,8 @@ fastify.get('/', options, function (request, reply) {
})
```
If you want to pass a custom stream to the Pino instance, just add a stream
field to the logger object.
To pass a custom stream to the Pino instance, add a `stream` field to the logger
object:
```js
const split = require('split2')
@@ -95,19 +97,25 @@ const fastify = require('fastify')({
})
```
<a id="logging-request-id"></a>
### Advanced Logger Configuration
<a id="logging-request-id"></a>
#### Request ID Tracking
By default, Fastify adds an ID to every request for easier tracking. If the
"request-id" header is present its value is used, otherwise a new incremental ID
is generated. See Fastify Factory
`requestIdHeader` option is set and the corresponding header is present, its
value is used; otherwise, a new incremental ID is generated. See Fastify Factory
[`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory
[`genReqId`](./Server.md#genreqid) for customization options.
The default logger is configured with a set of standard serializers that
serialize objects with `req`, `res`, and `err` properties. The object received
by `req` is the Fastify [`Request`](./Request.md) object, while the object
received by `res` is the Fastify [`Reply`](./Reply.md) object. This behavior
can be customized by specifying custom serializers.
> ⚠ Warning: enabling `requestIdHeader` allows any callers to set `reqId` to a
> value of their choosing.
> No validation is performed on `requestIdHeader`.
#### Serializers
The default logger uses standard serializers for objects with `req`, `res`, and
`err` properties. The `req` object is the Fastify [`Request`](./Request.md)
object, and the `res` object is the Fastify [`Reply`](./Reply.md) object. This
behavior can be customized with custom serializers.
```js
const fastify = require('fastify')({
@@ -121,7 +129,7 @@ const fastify = require('fastify')({
})
```
For example, the response payload and headers could be logged using the approach
below (even if it is *not recommended*):
below (not recommended):
```js
const fastify = require('fastify')({
@@ -140,12 +148,11 @@ const fastify = require('fastify')({
return {
method: request.method,
url: request.url,
path: request.routerPath,
path: request.routeOptions.url,
parameters: request.params,
// Including the headers in the log could be in violation
// of privacy laws, e.g. GDPR. You should use the "redact" option to
// remove sensitive fields. It could also leak authentication data in
// the logs.
// Including headers in the log could violate privacy laws,
// e.g., GDPR. Use the "redact" option to remove sensitive
// fields. It could also leak authentication data in the logs.
headers: request.headers
};
}
@@ -154,11 +161,11 @@ const fastify = require('fastify')({
});
```
**Note**: In certain cases, the [`Reply`](./Reply.md) object passed to the `res`
serializer cannot be fully constructed. When writing a custom `res` serializer,
it is necessary to check for the existence of any properties on `reply` aside
from `statusCode`, which is always present. For example, the existence of
`getHeaders` must be verified before it can be called:
> Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res`
> serializer cannot be fully constructed. When writing a custom `res`
> serializer, check for the existence of any properties on `reply` aside from
> `statusCode`, which is always present. For example, verify the existence of
> `getHeaders` before calling it:
```js
const fastify = require('fastify')({
@@ -170,7 +177,7 @@ const fastify = require('fastify')({
res (reply) {
// The default
return {
statusCode: reply.statusCode
statusCode: reply.statusCode,
headers: typeof reply.getHeaders === 'function'
? reply.getHeaders()
: {}
@@ -181,11 +188,11 @@ const fastify = require('fastify')({
});
```
**Note**: The body cannot be serialized inside a `req` method because the
request is serialized when we create the child logger. At that time, the body is
not yet parsed.
> Note: The body cannot be serialized inside a `req` method because the
request is serialized when the child logger is created. At that time, the body
is not yet parsed.
See an approach to log `req.body`
See the following approach to log `req.body`:
```js
app.addHook('preHandler', function (req, reply, done) {
@@ -196,23 +203,24 @@ app.addHook('preHandler', function (req, reply, done) {
})
```
**Note**: Care should be taken to ensure serializers never throw, as an error
thrown from a serializer has the potential to cause the Node process to exit.
See the [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers)
on serializers for more information.
> Note: Ensure serializers never throw errors, as this can cause the Node
> process to exit. See the
> [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more
> information.
*Any logger other than Pino will ignore this option.*
You can also supply your own logger instance. Instead of passing configuration
options, pass the instance. The logger you supply must conform to the Pino
interface; that is, it must have the following methods: `info`, `error`,
`debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a string property `level`.
### Using Custom Loggers
A custom logger instance can be supplied by passing it as `loggerInstance`. The
logger must conform to the Pino interface, with methods: `info`, `error`,
`debug`, `fatal`, `warn`, `trace`, `silent`, `child`, and a string property
`level`.
Example:
```js
const log = require('pino')({ level: 'info' })
const fastify = require('fastify')({ logger: log })
const fastify = require('fastify')({ loggerInstance: log })
log.info('does not have request information')
@@ -225,11 +233,11 @@ fastify.get('/', function (request, reply) {
*The logger instance for the current request is available in every part of the
[lifecycle](./Lifecycle.md).*
## Log Redaction
### Log Redaction
[Pino](https://getpino.io) supports low-overhead log redaction for obscuring
values of specific properties in recorded logs. As an example, we might want to
log all the HTTP headers minus the `Authorization` header for security concerns:
values of specific properties in recorded logs. For example, log all HTTP
headers except the `Authorization` header for security:
```js
const fastify = Fastify({
@@ -243,7 +251,7 @@ const fastify = Fastify({
method: request.method,
url: request.url,
headers: request.headers,
hostname: request.hostname,
host: request.host,
remoteAddress: request.ip,
remotePort: request.socket.remotePort
}

View File

@@ -22,36 +22,36 @@ fastify.use(require('ienoopen')())
fastify.use(require('x-xss-protection')())
```
You can also use [`@fastify/middie`](https://github.com/fastify/middie), which provides
support for simple Express-style middleware but with improved performance:
[`@fastify/middie`](https://github.com/fastify/middie) can also be used,
which provides support for simple Express-style middleware with improved
performance:
```js
await fastify.register(require('@fastify/middie'))
fastify.use(require('cors')())
```
Remember that middleware can be encapsulated; this means that you can decide
where your middleware should run by using `register` as explained in the
[plugins guide](../Guides/Plugins-Guide.md).
Middleware can be encapsulated, allowing control over where it runs using
`register` as explained in the [plugins guide](../Guides/Plugins-Guide.md).
Fastify middleware does not expose the `send` method or other methods specific to
the Fastify [Reply](./Reply.md#reply) instance. This is because Fastify wraps
Fastify middleware does not expose the `send` method or other methods specific
to the Fastify [Reply](./Reply.md#reply) instance. This is because Fastify wraps
the incoming `req` and `res` Node instances using the
[Request](./Request.md#request) and [Reply](./Reply.md#reply) objects
internally, but this is done after the middleware phase. If you need to create
middleware, you have to use the Node `req` and `res` instances. Otherwise, you
can use the `preHandler` hook that already has the
[Request](./Request.md#request) and [Reply](./Reply.md#reply) Fastify instances.
For more information, see [Hooks](./Hooks.md#hooks).
internally, but this is done after the middleware phase. To create middleware,
use the Node `req` and `res` instances. Alternatively, use the `preHandler` hook
that already has the Fastify [Request](./Request.md#request) and
[Reply](./Reply.md#reply) instances. For more information, see
[Hooks](./Hooks.md#hooks).
#### Restrict middleware execution to certain paths
<a id="restrict-usage"></a>
If you need to only run middleware under certain paths, just pass the path as
the first parameter to `use` and you are done!
To run middleware under certain paths, pass the path as the first parameter to
`use`.
*Note that this does not support routes with parameters, (e.g.
`/user/:id/comments`) and wildcards are not supported in multiple paths.*
> Note: This does not support routes with parameters
> (e.g. `/user/:id/comments`) and wildcards are not supported in multiple paths.
```js
const path = require('node:path')
@@ -69,10 +69,10 @@ fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))
### Alternatives
Fastify offers some alternatives to the most commonly used middleware, such as
[`@fastify/helmet`](https://github.com/fastify/fastify-helmet) in case of
Fastify offers alternatives to commonly used middleware, such as
[`@fastify/helmet`](https://github.com/fastify/fastify-helmet) for
[`helmet`](https://github.com/helmetjs/helmet),
[`@fastify/cors`](https://github.com/fastify/fastify-cors) for
[`cors`](https://github.com/expressjs/cors), and
[`@fastify/static`](https://github.com/fastify/fastify-static) for
[`serve-static`](https://github.com/expressjs/serve-static).
[`serve-static`](https://github.com/expressjs/serve-static).

View File

@@ -1,21 +1,18 @@
<h1 align="center">Fastify</h1>
## Plugins
Fastify allows the user to extend its functionalities with plugins. A plugin can
be a set of routes, a server [decorator](./Decorators.md), or whatever. The API
that you will need to use one or more plugins, is `register`.
Fastify can be extended with plugins, which can be a set of routes, a server
[decorator](./Decorators.md), or other functionality. Use the `register` API to
add one or more plugins.
By default, `register` creates a *new scope*, this means that if you make some
changes to the Fastify instance (via `decorate`), this change will not be
reflected by the current context ancestors, but only by its descendants. This
feature allows us to achieve plugin *encapsulation* and *inheritance*, in this
way we create a *directed acyclic graph* (DAG) and we will not have issues
caused by cross dependencies.
By default, `register` creates a *new scope*, meaning changes to the Fastify
instance (via `decorate`) will not affect the current context ancestors, only
its descendants. This feature enables plugin *encapsulation* and *inheritance*,
creating a *directed acyclic graph* (DAG) and avoiding cross-dependency issues.
You may have already seen in the [Getting
Started](../Guides/Getting-Started.md#your-first-plugin) guide how easy it is
to use this API:
```
The [Getting Started](../Guides/Getting-Started.md#your-first-plugin) guide
includes an example of using this API:
```js
fastify.register(plugin, [options])
```
@@ -33,10 +30,9 @@ Fastify specific options is:
+ [`logSerializers`](./Routes.md#custom-log-serializer)
+ [`prefix`](#route-prefixing-option)
**Note: Those options will be ignored when used with fastify-plugin**
These options will be ignored when used with fastify-plugin.
It is possible that Fastify will directly support other options in the future.
Thus, to avoid collisions, a plugin should consider namespacing its options. For
To avoid collisions, a plugin should consider namespacing its options. For
example, a plugin `foo` might be registered like so:
```js
@@ -49,8 +45,7 @@ fastify.register(require('fastify-foo'), {
})
```
If collisions are not a concern, the plugin may simply accept the options object
as-is:
If collisions are not a concern, the plugin may accept the options object as-is:
```js
fastify.register(require('fastify-foo'), {
@@ -60,9 +55,8 @@ fastify.register(require('fastify-foo'), {
})
```
The `options` parameter can also be a `Function` that will be evaluated at the
time the plugin is registered while giving access to the Fastify instance via
the first positional argument:
The `options` parameter can also be a `Function` evaluated at plugin registration,
providing access to the Fastify instance via the first argument:
```js
const fp = require('fastify-plugin')
@@ -77,40 +71,38 @@ fastify.register(fp((fastify, opts, done) => {
fastify.register(require('fastify-foo'), parent => parent.foo_bar)
```
The Fastify instance passed on to the function is the latest state of the
**external Fastify instance** the plugin was declared on, allowing access to
variables injected via [`decorate`](./Decorators.md) by preceding plugins
according to the **order of registration**. This is useful in case a plugin
depends on changes made to the Fastify instance by a preceding plugin i.e.
utilizing an existing database connection to wrap around it.
The Fastify instance passed to the function is the latest state of the **external
Fastify instance** the plugin was declared on, allowing access to variables
injected via [`decorate`](./Decorators.md) by preceding plugins according to the
**order of registration**. This is useful if a plugin depends on changes made to
the Fastify instance by a preceding plugin, such as utilizing an existing database
connection.
Keep in mind that the Fastify instance passed on to the function is the same as
the one that will be passed into the plugin, a copy of the external Fastify
instance rather than a reference. Any usage of the instance will behave the same
as it would if called within the plugins function i.e. if `decorate` is called,
the decorated variables will be available within the plugins function unless it
was wrapped with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
Keep in mind that the Fastify instance passed to the function is the same as the
one passed into the plugin, a copy of the external Fastify instance rather than
a reference. Any usage of the instance will behave the same as it would if called
within the plugin's function. For example, if `decorate` is called, the decorated
variables will be available within the plugin's function unless it was wrapped
with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
#### Route Prefixing option
<a id="route-prefixing-option"></a>
If you pass an option with the key `prefix` with a `string` value, Fastify will
use it to prefix all the routes inside the register, for more info check
If an option with the key `prefix` and a `string` value is passed, Fastify will
use it to prefix all the routes inside the register. For more info, check
[here](./Routes.md#route-prefixing).
Be aware that if you wrap your routes with
Be aware that if routes are wrapped with
[`fastify-plugin`](https://github.com/fastify/fastify-plugin), this option will
not work (there is a [workaround](./Routes.md#fastify-plugin) available).
not work (see the [workaround](./Routes.md#fastify-plugin)).
#### Error handling
<a id="error-handling"></a>
The error handling is done by
[avvio](https://github.com/mcollina/avvio#error-handling).
Error handling is done by [avvio](https://github.com/mcollina/avvio#error-handling).
As a general rule, it is highly recommended that you handle your errors in the
next `after` or `ready` block, otherwise you will get them inside the `listen`
callback.
As a general rule, handle errors in the next `after` or `ready` block, otherwise
they will be caught inside the `listen` callback.
```js
fastify.register(require('my-plugin'))
@@ -145,16 +137,15 @@ await fastify.ready()
await fastify.listen({ port: 3000 })
```
*Note: Using `await` when registering a plugin loads the plugin
and the underlying dependency tree, "finalizing" the encapsulation process.
Any mutations to the plugin after it and its dependencies have been
loaded will not be reflected in the parent instance.*
Using `await` when registering a plugin loads the plugin and its dependencies,
"finalizing" the encapsulation process. Any mutations to the plugin after it and
its dependencies have been loaded will not be reflected in the parent instance.
#### ESM support
<a id="esm-support"></a>
ESM is supported as well from [Node.js
`v13.3.0`](https://nodejs.org/api/esm.html) and above!
ESM is supported from [Node.js `v13.3.0`](https://nodejs.org/api/esm.html)
and above.
```js
// main.mjs
@@ -179,21 +170,29 @@ export default plugin
### Create a plugin
<a id="create-plugin"></a>
Creating a plugin is very easy, you just need to create a function that takes
three parameters, the `fastify` instance, an `options` object, and the `done`
callback.
Creating a plugin is easy. Create a function that takes three parameters: the
`fastify` instance, an `options` object, and the `done` callback. Alternatively,
use an `async` function and omit the `done` callback.
Example:
```js
module.exports = function (fastify, opts, done) {
module.exports = function callbackPlugin (fastify, opts, done) {
fastify.decorate('utility', function () {})
fastify.get('/', handler)
done()
}
// Or using async
module.exports = async function asyncPlugin (fastify, opts) {
fastify.decorate('utility', function () {})
fastify.get('/', handler)
}
```
You can also use `register` inside another `register`:
`register` can also be used inside another `register`:
```js
module.exports = function (fastify, opts, done) {
fastify.decorate('utility', function () {})
@@ -205,28 +204,23 @@ module.exports = function (fastify, opts, done) {
done()
}
```
Sometimes, you will need to know when the server is about to close, for example,
because you must close a connection to a database. To know when this is going to
happen, you can use the [`'onClose'`](./Hooks.md#on-close) hook.
Do not forget that `register` will always create a new Fastify scope, if you do
not need that, read the following section.
Remember, `register` always creates a new Fastify scope. If this is not needed,
read the following section.
### Handle the scope
<a id="handle-scope"></a>
If you are using `register` only for extending the functionality of the server
with [`decorate`](./Decorators.md), it is your responsibility to tell Fastify
not to create a new scope. Otherwise, your changes will not be accessible by the
user in the upper scope.
If `register` is used only to extend server functionality with
[`decorate`](./Decorators.md), tell Fastify not to create a new scope. Otherwise,
changes will not be accessible in the upper scope.
You have two ways to tell Fastify to avoid the creation of a new context:
There are two ways to avoid creating a new context:
- Use the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module
- Use the `'skip-override'` hidden property
We recommend using the `fastify-plugin` module, because it solves this problem
for you, and you can pass a version range of Fastify as a parameter that your
plugin will support.
Using the `fastify-plugin` module is recommended, as it solves this problem and
allows passing a version range of Fastify that the plugin will support:
```js
const fp = require('fastify-plugin')
@@ -238,10 +232,9 @@ module.exports = fp(function (fastify, opts, done) {
Check the [`fastify-plugin`](https://github.com/fastify/fastify-plugin)
documentation to learn more about how to use this module.
If you do not use the `fastify-plugin` module, you can use the `'skip-override'`
hidden property, but we do not recommend it. If in the future the Fastify API
changes it will be your responsibility to update the module, while if you use
`fastify-plugin`, you can be sure about backward compatibility.
If not using `fastify-plugin`, the `'skip-override'` hidden property can be used,
but it is not recommended. Future Fastify API changes will be your responsibility
to update, whilst `fastify-plugin` ensures backward compatibility.
```js
function yourPlugin (fastify, opts, done) {
fastify.decorate('utility', function () {})

View File

@@ -16,48 +16,41 @@ the following technical principles:
## "Zero" Overhead in Production
Fastify aims to implement its features by adding as minimal overhead to your
application as possible.
This is usually delivered by implementing fast algorithms and data structures,
as well as JavaScript-specific features.
Fastify aims to implement features with minimal overhead. This is achieved by
using fast algorithms, data structures, and JavaScript-specific features.
Given that JavaScript does not offer zero-overhead data structures, this principle
is at odds with providing a great developer experience and providing more features,
as usually those cost some overhead.
Since JavaScript does not offer zero-overhead data structures, this principle
can conflict with providing a great developer experience and additional features,
as these usually incur some overhead.
## "Good" Developer Experience
Fastify aims to provide the best developer experience at the performance point
it is operating.
It provides a great out-of-the-box experience that is flexible enough to be
adapted to a variety of situations.
Fastify aims to provide the best developer experience at its performance point.
It offers a great out-of-the-box experience that is flexible enough to adapt to
various situations.
As an example, this means that binary addons are forbidden because most JavaScript
developers would not
have access to a compiler.
For example, binary addons are forbidden because most JavaScript developers do
not have access to a compiler.
## Works great for small and big projects alike
We recognize that most applications start small and become more complex over time.
Fastify aims to grow with
the complexity of your application, providing advanced features to structure
your codebase.
Most applications start small and become more complex over time. Fastify aims to
grow with this complexity, providing advanced features to structure codebases.
## Easy to migrate to microservices (or even serverless) and back
How you deploy your routes should not matter. The framework should "just work".
Route deployment should not matter. The framework should "just work".
## Security and Data Validation
Your web framework is the first point of contact with untrusted data, and it
needs to act as the first line of defense for your system.
A web framework is the first point of contact with untrusted data and must act
as the first line of defense for the system.
## If something could be a plugin, it likely should
We recognize that there are an infinite amount of use cases for an HTTP framework
for Node.js. Catering to them in a single module would make the codebase unmaintainable.
Therefore we provide hooks and options to allow you to customize the framework
as you please.
Recognizing the infinite use cases for an HTTP framework, catering to all in a
single module would make the codebase unmaintainable. Therefore, hooks and
options are provided to customize the framework as needed.
## Easily testable
@@ -65,14 +58,16 @@ Testing Fastify applications should be a first-class concern.
## Do not monkeypatch core
Monkeypatch Node.js APIs or installing globals that alter the behavior of the
runtime makes building modular applications harder, and limit the use cases of Fastify.
Other frameworks do this and we do not.
Monkeypatching Node.js APIs or installing globals that alter the runtime makes
building modular applications harder and limits Fastify's use cases. Other
frameworks do this; Fastify does not.
## Semantic Versioning and Long Term Support
We provide a clear Long Term Support strategy so developers can know when to upgrade.
A clear [Long Term Support strategy is provided](./LTS.md) to inform developers
when to upgrade.
## Specification adherence
In doubt, we chose the strict behavior as defined by the relevant Specifications.
In doubt, we chose the strict behavior as defined by the relevant
Specifications.

View File

@@ -11,15 +11,14 @@
- [.headers(object)](#headersobject)
- [.getHeader(key)](#getheaderkey)
- [.getHeaders()](#getheaders)
- [set-cookie](#set-cookie)
- [.removeHeader(key)](#removeheaderkey)
- [.hasHeader(key)](#hasheaderkey)
- [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback)
- [.trailer(key, function)](#trailerkey-function)
- [.hasTrailer(key)](#hastrailerkey)
- [.removeTrailer(key)](#removetrailerkey)
- [.redirect(dest, [code ,])](#redirectdest--code)
- [.callNotFound()](#callnotfound)
- [.getResponseTime()](#getresponsetime)
- [.type(contentType)](#typecontenttype)
- [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
- [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
@@ -33,8 +32,9 @@
- [Strings](#strings)
- [Streams](#streams)
- [Buffers](#buffers)
- [ReadableStream](#send-readablestream)
- [Response](#send-response)
- [TypedArrays](#typedarrays)
- [ReadableStream](#readablestream)
- [Response](#response)
- [Errors](#errors)
- [Type of the final payload](#type-of-the-final-payload)
- [Async-Await and Promises](#async-await-and-promises)
@@ -58,6 +58,8 @@ since the request was received by Fastify.
- `.getHeaders()` - Gets a shallow copy of all current response headers.
- `.removeHeader(key)` - Remove the value of a previously set header.
- `.hasHeader(name)` - Determine if a header has been set.
- `.writeEarlyHints(hints, callback)` - Sends early hints to the user
while the response is being prepared.
- `.trailer(key, function)` - Sets a response trailer.
- `.hasTrailer(key)` - Determine if a trailer has been set.
- `.removeTrailer(key)` - Remove the value of a previously set trailer.
@@ -68,16 +70,17 @@ since the request was received by Fastify.
- `.serialize(payload)` - Serializes the specified payload using the default
JSON serializer or using the custom serializer (if one is set) and returns the
serialized payload.
- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization
function for the specified schema or http status, if any of either are set.
- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles
the specified schema and returns a serialization function using the default
(or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded
- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the
serialization function for the specified schema or http status, if any of
either are set.
- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles
the specified schema and returns a serialization function using the default
(or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded
to the `SerializerCompiler` if provided, default to `undefined`.
- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes
- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes
the specified data using the specified schema and returns the serialized payload.
If the optional `httpStatus`, and `contentType` are provided, the function
will use the serializer function given for that specific content type and
If the optional `httpStatus`, and `contentType` are provided, the function
will use the serializer function given for that specific content type and
HTTP Status Code. Default to `undefined`.
- `.serializer(function)` - Sets a custom serializer for the payload.
- `.send(payload)` - Sends the payload to the user, could be a plain text, a
@@ -86,13 +89,10 @@ since the request was received by Fastify.
already been called.
- `.hijack()` - interrupt the normal request lifecycle.
- `.raw` - The
[`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse)
[`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
from Node core.
- `.log` - The logger instance of the incoming request.
- `.request` - The incoming request.
- `.getResponseTime()` - Deprecated, returns the amount of time passed
since the request was received by Fastify.
- `.context` - Deprecated, access the [Request's context](./Request.md) property.
```js
fastify.get('/', options, function (request, reply) {
@@ -104,14 +104,6 @@ fastify.get('/', options, function (request, reply) {
})
```
Additionally, `Reply` provides access to the context of the request:
```js
fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) {
reply.send('handler config.foo = ' + reply.context.config.foo)
})
```
### .code(statusCode)
<a id="code"></a>
@@ -123,9 +115,6 @@ If not set via `reply.code`, the resulting `statusCode` will be `200`.
Invokes the custom response time getter to calculate the amount of time passed
since the request was received by Fastify.
Note that unless this function is called in the [`onResponse`
hook](./Hooks.md#onresponse) it will always return `0`.
```js
const milliseconds = reply.elapsedTime
```
@@ -163,14 +152,14 @@ fastify.get('/', async function (req, rep) {
Sets a response header. If the value is omitted or undefined, it is coerced to
`''`.
> Note: the header's value must be properly encoded using
> Note: The header's value must be properly encoded using
> [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
> or similar modules such as
> [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters
> will result in a 500 `TypeError` response.
For more information, see
[`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_response_setheader_name_value).
[`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_response_setheader_name_value).
- ### set-cookie
<a id="set-cookie"></a>
@@ -243,6 +232,27 @@ reply.getHeader('x-foo') // undefined
Returns a boolean indicating if the specified header has been set.
### .writeEarlyHints(hints, callback)
<a id="writeEarlyHints"></a>
Sends early hints to the client. Early hints allow the client to
start processing resources before the final response is sent.
This can improve performance by allowing the client to preload
or preconnect to resources while the server is still generating the response.
The hints parameter is an object containing the early hint key-value pairs.
Example:
```js
reply.writeEarlyHints({
Link: '</styles.css>; rel=preload; as=style'
});
```
The optional callback parameter is a function that will be called
once the hint is sent or if an error occurs.
### .trailer(key, function)
<a id="trailer"></a>
@@ -251,11 +261,11 @@ requires heavy resources to be sent after the `data`, for example,
`Server-Timing` and `Etag`. It can ensure the client receives the response data
as soon as possible.
*Note: The header `Transfer-Encoding: chunked` will be added once you use the
trailer. It is a hard requirement for using trailer in Node.js.*
> Note: The header `Transfer-Encoding: chunked` will be added once you use
> the trailer. It is a hard requirement for using trailer in Node.js.
*Note: Any error passed to `done` callback will be ignored. If you interested
in the error, you can turn on `debug` level logging.*
> Note: Any error passed to `done` callback will be ignored. If you interested
> in the error, you can turn on `debug` level logging.*
```js
reply.trailer('server-timing', function() {
@@ -270,14 +280,14 @@ const { createHash } = require('node:crypto')
reply.trailer('content-md5', function(reply, payload, done) {
const hash = createHash('md5')
hash.update(payload)
done(null, hash.disgest('hex'))
done(null, hash.digest('hex'))
})
// when you prefer async-await
reply.trailer('content-md5', async function(reply, payload) {
const hash = createHash('md5')
hash.update(payload)
return hash.disgest('hex')
return hash.digest('hex')
})
```
@@ -305,7 +315,7 @@ reply.getTrailer('server-timing') // undefined
Redirects a request to the specified URL, the status code is optional, default
to `302` (if status code is not already set by calling `code`).
> Note: the input URL must be properly encoded using
> Note: The input URL must be properly encoded using
> [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
> or similar modules such as
> [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will
@@ -343,22 +353,6 @@ hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler).
reply.callNotFound()
```
### .getResponseTime()
<a id="getResponseTime"></a>
Invokes the custom response time getter to calculate the amount of time passed
since the request was received by Fastify.
Note that unless this function is called in the [`onResponse`
hook](./Hooks.md#onresponse) it will always return `0`.
```js
const milliseconds = reply.getResponseTime()
```
*Note: This method is deprecated and will be removed in `fastify@5`.
Use the [.elapsedTime](#elapsedtime) property instead.*
### .type(contentType)
<a id="type"></a>
@@ -369,13 +363,14 @@ Sets the content type for the response. This is a shortcut for
reply.type('text/html')
```
If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
`utf-8` will be used as the charset by default.
`utf-8` will be used as the charset by default. For other content types, the
charset must be set explicitly.
### .getSerializationFunction(schema | httpStatus, [contentType])
<a id="getserializationfunction"></a>
By calling this function using a provided `schema` or `httpStatus`,
and the optional `contentType`, it will return a `serialzation` function
By calling this function using a provided `schema` or `httpStatus`,
and the optional `contentType`, it will return a `serialization` function
that can be used to serialize diverse inputs. It returns `undefined` if no
serialization function was found using either of the provided inputs.
@@ -385,12 +380,12 @@ the serialization functions compiled by using `compileSerializationSchema`.
```js
const serialize = reply
.getSerializationFunction({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
})
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
@@ -419,8 +414,8 @@ The function returned (a.k.a. _serialization function_) returned is compiled
by using the provided `SerializerCompiler`. Also this is cached by using
a `WeakMap` for reducing compilation calls.
The optional parameters `httpStatus` and `contentType`, if provided,
are forwarded directly to the `SerializerCompiler`, so it can be used
The optional parameters `httpStatus` and `contentType`, if provided,
are forwarded directly to the `SerializerCompiler`, so it can be used
to compile the serialization function if a custom `SerializerCompiler` is used.
This heavily depends of the `schema#responses` attached to the route, or
@@ -429,12 +424,12 @@ the serialization functions compiled by using `compileSerializationSchema`.
```js
const serialize = reply
.compileSerializationSchema({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
})
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
@@ -442,12 +437,12 @@ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
const serialize = reply
.compileSerializationSchema({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}, 200)
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
@@ -493,7 +488,7 @@ const schema1 = {
```
*Not*
```js
```js
const serialize = reply.compileSerializationSchema(schema1)
// Later on...
@@ -527,25 +522,25 @@ function will be compiled, forwarding the `httpStatus` and `contentType` if prov
```js
reply
.serializeInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
.serializeInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}) // '{"foo":"bar"}'
// or
reply
.serializeInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}, 200) // '{"foo":"bar"}'
// or
@@ -594,7 +589,7 @@ values.
<a id="raw"></a>
This is the
[`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse)
[`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
from Node core. Whilst you are using the Fastify `Reply` object, the use of
`Reply.raw` functions is at your own risk as you are skipping all the Fastify
logic of handling the HTTP response. e.g.:
@@ -694,9 +689,13 @@ If you are sending a stream and you have not set a `'Content-Type'` header,
As noted above, streams are considered to be pre-serialized, so they will be
sent unmodified without response validation.
See special note about error handling for streams in
[`setErrorHandler`](./Server.md#seterrorhandler).
```js
const fs = require('node:fs')
fastify.get('/streams', function (request, reply) {
const fs = require('node:fs')
const stream = fs.createReadStream('some-file', 'utf8')
reply.header('Content-Type', 'application/octet-stream')
reply.send(stream)
@@ -704,8 +703,9 @@ fastify.get('/streams', function (request, reply) {
```
When using async-await you will need to return or await the reply object:
```js
const fs = require('node:fs')
fastify.get('/streams', async function (request, reply) {
const fs = require('node:fs')
const stream = fs.createReadStream('some-file', 'utf8')
reply.header('Content-Type', 'application/octet-stream')
return reply.send(stream)
@@ -718,11 +718,12 @@ fastify.get('/streams', async function (request, reply) {
If you are sending a buffer and you have not set a `'Content-Type'` header,
*send* will set it to `'application/octet-stream'`.
As noted above, Buffers are considered to be pre-serialized, so they will be
As noted above, Buffers are considered to be pre-serialized, so they will be
sent unmodified without response validation.
```js
const fs = require('node:fs')
fastify.get('/streams', function (request, reply) {
fs.readFile('some-file', (err, fileBuffer) => {
reply.send(err || fileBuffer)
@@ -733,6 +734,7 @@ fastify.get('/streams', function (request, reply) {
When using async-await you will need to return or await the reply object:
```js
const fs = require('node:fs')
fastify.get('/streams', async function (request, reply) {
fs.readFile('some-file', (err, fileBuffer) => {
reply.send(err || fileBuffer)
@@ -747,11 +749,12 @@ fastify.get('/streams', async function (request, reply) {
`send` manages TypedArray like a Buffer, and sets the `'Content-Type'`
header to `'application/octet-stream'` if not already set.
As noted above, TypedArray/Buffers are considered to be pre-serialized, so they
As noted above, TypedArray/Buffers are considered to be pre-serialized, so they
will be sent unmodified without response validation.
```js
const fs = require('node:fs')
fastify.get('/streams', function (request, reply) {
const typedArray = new Uint16Array(10)
reply.send(typedArray)
@@ -762,12 +765,13 @@ fastify.get('/streams', function (request, reply) {
<a id="send-readablestream"></a>
`ReadableStream` will be treated as a node stream mentioned above,
the content is considered to be pre-serialized, so they will be
the content is considered to be pre-serialized, so they will be
sent unmodified without response validation.
```js
const fs = require('node:fs')
const { ReadableStream } = require('node:stream/web')
fastify.get('/streams', function (request, reply) {
const stream = fs.createReadStream('some-file')
reply.header('Content-Type', 'application/octet-stream')
@@ -780,7 +784,7 @@ fastify.get('/streams', function (request, reply) {
`Response` allows to manage the reply payload, status code and
headers in one place. The payload provided inside `Response` is
considered to be pre-serialized, so they will be sent unmodified
considered to be pre-serialized, so they will be sent unmodified
without response validation.
Please be aware when using `Response`, the status code and headers
@@ -792,6 +796,7 @@ and may confuse when checking the `payload` in `onSend` hooks.
```js
const fs = require('node:fs')
const { ReadableStream } = require('node:stream/web')
fastify.get('/streams', function (request, reply) {
const stream = fs.createReadStream('some-file')
const readableStream = ReadableStream.from(stream)
@@ -822,8 +827,8 @@ automatically create an error structured as the following:
You can add custom properties to the Error object, such as `headers`, that will
be used to enhance the HTTP response.
*Note: If you are passing an error to `send` and the statusCode is less than
400, Fastify will automatically set it at 500.*
> Note: If you are passing an error to `send` and the statusCode is less than
> 400, Fastify will automatically set it at 500.
Tip: you can simplify errors by using the
[`http-errors`](https://npm.im/http-errors) module or
@@ -870,14 +875,14 @@ fastify.get('/', {
If you want to customize error handling, check out
[`setErrorHandler`](./Server.md#seterrorhandler) API.
*Note: you are responsible for logging when customizing the error handler*
> Note: you are responsible for logging when customizing the error handler.
API:
```js
fastify.setErrorHandler(function (error, request, reply) {
request.log.warn(error)
var statusCode = error.statusCode >= 400 ? error.statusCode : 500
const statusCode = error.statusCode >= 400 ? error.statusCode : 500
reply
.code(statusCode)
.type('text/plain')

View File

@@ -4,80 +4,71 @@
The first parameter of the handler function is `Request`.
Request is a core Fastify object containing the following fields:
- `query` - the parsed querystring, its format is specified by
[`querystringParser`](./Server.md#querystringparser)
- `body` - the request payload, see [Content-Type
Parser](./ContentTypeParser.md) for details on what request payloads Fastify
natively parses and how to support other content types
- `params` - the params matching the URL
- [`headers`](#headers) - the headers getter and setter
- `raw` - the incoming HTTP request from Node core
- `server` - The Fastify server instance, scoped to the current [encapsulation
context](./Encapsulation.md)
- `id` - the request ID
- `log` - the logger instance of the incoming request
- `ip` - the IP address of the incoming request
- `ips` - an array of the IP addresses, ordered from closest to furthest, in the
- `query` - The parsed querystring, its format is specified by
[`querystringParser`](./Server.md#querystringparser).
- `body` - The request payload, see [Content-Type Parser](./ContentTypeParser.md)
for details on what request payloads Fastify natively parses and how to support
other content types.
- `params` - The params matching the URL.
- [`headers`](#headers) - The headers getter and setter.
- `raw` - The incoming HTTP request from Node core.
- `server` - The Fastify server instance, scoped to the current
[encapsulation context](./Encapsulation.md).
- `id` - The request ID.
- `log` - The logger instance of the incoming request.
- `ip` - The IP address of the incoming request.
- `ips` - An array of the IP addresses, ordered from closest to furthest, in the
`X-Forwarded-For` header of the incoming request (only when the
[`trustProxy`](./Server.md#factory-trust-proxy) option is enabled)
- `hostname` - the host of the incoming request (derived from `X-Forwarded-Host`
[`trustProxy`](./Server.md#factory-trust-proxy) option is enabled).
- `host` - The host of the incoming request (derived from `X-Forwarded-Host`
header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is
enabled). For HTTP/2 compatibility it returns `:authority` if no host header
exists.
- `protocol` - the protocol of the incoming request (`https` or `http`)
- `method` - the method of the incoming request
- `url` - the URL of the incoming request
- `originalUrl` - similar to `url`, this allows you to access the
original `url` in case of internal re-routing
- `routerMethod` - Deprecated, use `request.routeOptions.method` instead. The
method defined for the router that is handling the request
- `routerPath` - Deprecated, use `request.routeOptions.url` instead. The
path pattern defined for the router that is handling the request
- `is404` - true if request is being handled by 404 handler, false if it is not
- `connection` - Deprecated, use `socket` instead. The underlying connection of
the incoming request.
- `socket` - the underlying connection of the incoming request
- `context` - Deprecated, use `request.routeOptions.config` instead.
A Fastify internal object. You should not use
it directly or modify it. It is useful to access one special key:
enabled). For HTTP/2 compatibility, it returns `:authority` if no host header
exists. The host header may return an empty string if `requireHostHeader` is
`false`, not provided with HTTP/1.0, or removed by schema validation.
- `hostname` - The hostname derived from the `host` property of the incoming request.
- `port` - The port from the `host` property, which may refer to the port the
server is listening on.
- `protocol` - The protocol of the incoming request (`https` or `http`).
- `method` - The method of the incoming request.
- `url` - The URL of the incoming request.
- `originalUrl` - Similar to `url`, allows access to the original `url` in
case of internal re-routing.
- `is404` - `true` if request is being handled by 404 handler, `false` otherwise.
- `socket` - The underlying connection of the incoming request.
- `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify
internal object. Do not use or modify it directly. It is useful to access one
special key:
- `context.config` - The route [`config`](./Routes.md#routes-config) object.
- `routeSchema` - Deprecated, use `request.routeOptions.schema` instead. The
scheme definition set for the router that is handling the request
- `routeConfig` - Deprecated, use `request.routeOptions.config` instead. The
route [`config`](./Routes.md#routes-config)
object.
- `routeOptions` - The route [`option`](./Routes.md#routes-options) object
- `bodyLimit` - either server limit or route limit
- `config` - the [`config`](./Routes.md#routes-config) object for this route
- `method` - the http method for the route
- `url` - the path of the URL to match this route
- `handler` - the handler for this route
- `attachValidation` - attach `validationError` to request
(if there is a schema defined)
- `logLevel` - log level defined for this route
- `schema` - the JSON schemas definition for this route
- `version` - a semver compatible string that defines the version of the endpoint
- `exposeHeadRoute` - creates a sibling HEAD route for any GET routes
- `prefixTrailingSlash` - string used to determine how to handle passing /
- `routeOptions` - The route [`option`](./Routes.md#routes-options) object.
- `bodyLimit` - Either server limit or route limit.
- `config` - The [`config`](./Routes.md#routes-config) object for this route.
- `method` - The HTTP method for the route.
- `url` - The path of the URL to match this route.
- `handler` - The handler for this route.
- `attachValidation` - Attach `validationError` to request (if there is
a schema defined).
- `logLevel` - Log level defined for this route.
- `schema` - The JSON schemas definition for this route.
- `version` - A semver compatible string that defines the version of the endpoint.
- `exposeHeadRoute` - Creates a sibling HEAD route for any GET routes.
- `prefixTrailingSlash` - String used to determine how to handle passing `/`
as a route with a prefix.
- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
Returns a validation function for the specified schema or http part,
if any of either are set or cached.
- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
Returns a validation function for the specified schema or HTTP part, if
set or cached.
- [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) -
Compiles the specified schema and returns a validation function
using the default (or customized) `ValidationCompiler`.
The optional `httpPart` is forwarded to the `ValidationCompiler`
if provided, defaults to `null`.
Compiles the specified schema and returns a validation function using the
default (or customized) `ValidationCompiler`. The optional `httpPart` is
forwarded to the `ValidationCompiler` if provided, defaults to `null`.
- [.validateInput(data, schema | httpPart, [httpPart])](#validate) -
Validates the specified input by using the specified
schema and returns the serialized payload. If the optional
`httpPart` is provided, the function will use the serializer
function given for that HTTP Status Code. Defaults to `null`.
Validates the input using the specified schema and returns the serialized
payload. If `httpPart` is provided, the function uses the serializer for
that HTTP Status Code. Defaults to `null`.
### Headers
The `request.headers` is a getter that returns an Object with the headers of the
incoming request. You can set custom headers like this:
The `request.headers` is a getter that returns an object with the headers of the
incoming request. Set custom headers as follows:
```js
request.headers = {
@@ -86,12 +77,15 @@ request.headers = {
}
```
This operation will add to the request headers the new values that can be read
calling `request.headers.bar`. Moreover, you can still access the standard
request's headers with the `request.raw.headers` property.
This operation adds new values to the request headers, accessible via
`request.headers.bar`. Standard request headers remain accessible via
`request.raw.headers`.
> Note: For performance reason on `not found` route, you may see that we will
add an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers.
For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added
to headers on `not found` routes.
> Note: Schema validation may mutate the `request.headers` and
> `request.raw.headers` objects, causing the headers to become empty.
```js
fastify.post('/:params', options, function (request, reply) {
@@ -104,7 +98,9 @@ fastify.post('/:params', options, function (request, reply) {
console.log(request.id)
console.log(request.ip)
console.log(request.ips)
console.log(request.host)
console.log(request.hostname)
console.log(request.port)
console.log(request.protocol)
console.log(request.url)
console.log(request.routeOptions.method)
@@ -123,23 +119,22 @@ fastify.post('/:params', options, function (request, reply) {
### .getValidationFunction(schema | httpPart)
<a id="getvalidationfunction"></a>
By calling this function using a provided `schema` or `httpPart`,
it will return a `validation` function that can be used to
validate diverse inputs. It returns `undefined` if no
serialization function was found using either of the provided inputs.
By calling this function with a provided `schema` or `httpPart`, it returns a
`validation` function to validate diverse inputs. It returns `undefined` if no
serialization function is found using the provided inputs.
This function has property errors. Errors encountered during the last validation
are assigned to errors
This function has an `errors` property. Errors encountered during the last
validation are assigned to `errors`.
```js
const validate = request
.getValidationFunction({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
})
console.log(validate({ foo: 'bar' })) // true
console.log(validate.errors) // null
@@ -152,34 +147,33 @@ console.log(validate({ foo: 0.5 })) // false
console.log(validate.errors) // validation errors
```
See [.compileValidationSchema(schema, [httpStatus])](#compilevalidationschema)
for more information on how to compile validation function.
See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
for more information on compiling validation schemas.
### .compileValidationSchema(schema, [httpPart])
<a id="compilevalidationschema"></a>
This function will compile a validation schema and
return a function that can be used to validate data.
The function returned (a.k.a. _validation function_) is compiled
by using the provided [`SchemaController#ValidationCompiler`](./Server.md#schema-controller).
A `WeakMap` is used to cached this, reducing compilation calls.
This function compiles a validation schema and returns a function to validate data.
The returned function (a.k.a. _validation function_) is compiled using the provided
[`SchemaController#ValidationCompiler`](./Server.md#schema-controller). A `WeakMap`
is used to cache this, reducing compilation calls.
The optional parameter `httpPart`, if provided, is forwarded directly
the `ValidationCompiler`, so it can be used to compile the validation
function if a custom `ValidationCompiler` is provided for the route.
The optional parameter `httpPart`, if provided, is forwarded to the
`ValidationCompiler`, allowing it to compile the validation function if a custom
`ValidationCompiler` is provided for the route.
This function has property errors. Errors encountered during the last validation
are assigned to errors
This function has an `errors` property. Errors encountered during the last
validation are assigned to `errors`.
```js
const validate = request
.compileValidationSchema({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
})
console.log(validate({ foo: 'bar' })) // true
console.log(validate.errors) // null
@@ -188,27 +182,24 @@ console.log(validate.errors) // null
const validate = request
.compileValidationSchema({
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}, 200)
console.log(validate({ hello: 'world' })) // false
console.log(validate.errors) // validation errors
```
Note that you should be careful when using this function, as it will cache
the compiled validation functions based on the schema provided. If the
schemas provided are mutated or changed, the validation functions will not
detect that the schema has been altered and for instance it will reuse the
previously compiled validation function, as the cache is based on
the reference of the schema (Object) previously provided.
Be careful when using this function, as it caches compiled validation functions
based on the provided schema. If schemas are mutated or changed, the validation
functions will not detect the alterations and will reuse the previously compiled
validation function, as the cache is based on the schema's reference.
If there is a need to change the properties of a schema, always opt to create
a totally new schema (object), otherwise the implementation will not benefit from
the cache mechanism.
If schema properties need to be changed, create a new schema object to benefit
from the cache mechanism.
Using the following schema as an example:
```js
@@ -223,7 +214,7 @@ const schema1 = {
```
*Not*
```js
```js
const validate = request.compileValidationSchema(schema1)
// Later on...
@@ -246,37 +237,36 @@ const newValidate = request.compileValidationSchema(newSchema)
console.log(newValidate === validate) // false
```
### .validateInput(data, [schema | httpStatus], [httpStatus])
### .validateInput(data, [schema | httpPart], [httpPart])
<a id="validate"></a>
This function will validate the input based on the provided schema,
or HTTP part passed. If both are provided, the `httpPart` parameter
will take precedence.
This function validates the input based on the provided schema or HTTP part. If
both are provided, the `httpPart` parameter takes precedence.
If there is not a validation function for a given `schema`, a new validation
function will be compiled, forwarding the `httpPart` if provided.
If no validation function exists for a given `schema`, a new validation function
will be compiled, forwarding the `httpPart` if provided.
```js
request
.validateInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
.validateInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}) // true
// or
request
.validateInput({ foo: 'bar'}, {
type: 'object',
properties: {
foo: {
type: 'string'
}
}
type: 'object',
properties: {
foo: {
type: 'string'
}
}
}, 'body') // true
// or
@@ -286,4 +276,4 @@ request
```
See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
for more information on how to compile validation schemas.
for more information on compiling validation schemas.

View File

@@ -2,9 +2,8 @@
## Routes
The route methods will configure the endpoints of your application. You have two
ways to declare a route with Fastify: the shorthand method and the full
declaration.
The route methods configure the endpoints of the application. Routes can be
declared using the shorthand method or the full declaration.
- [Full declaration](#full-declaration)
- [Routes options](#routes-options)
@@ -32,10 +31,9 @@ fastify.route(options)
### Routes options
<a id="options"></a>
* `method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`,
`'POST'`, `'PUT'`, `'OPTIONS'`, `'SEARCH'`, `'TRACE'`, `'PROPFIND'`,
`'PROPPATCH'`, `'MKCOL'`, `'COPY'`, `'MOVE'`, `'LOCK'`, `'UNLOCK'`,
`'REPORT'` and `'MKCALENDAR'`.
* `method`: currently it supports `GET`, `HEAD`, `TRACE`, `DELETE`,
`OPTIONS`, `PATCH`, `PUT` and `POST`. To accept more methods,
the [`addHttpMethod`](./Server.md#addHttpMethod) must be used.
It could also be an array of methods.
* `url`: the path of the URL to match this route (alias: `path`).
* `schema`: an object containing the schemas for the request and response. They
@@ -43,8 +41,7 @@ fastify.route(options)
[here](./Validation-and-Serialization.md) for more info.
* `body`: validates the body of the request if it is a POST, PUT, PATCH,
TRACE, SEARCH, PROPFIND, PROPPATCH, COPY, MOVE, MKCOL, REPORT, MKCALENDAR
or LOCK method.
TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method.
* `querystring` or `query`: validates the querystring. This can be a complete
JSON Schema object, with the property `type` of `object` and `properties`
object of parameters, or simply the values of what would be contained in the
@@ -62,8 +59,9 @@ fastify.route(options)
one.
* `onRequest(request, reply, done)`: a [function](./Hooks.md#onrequest) called
as soon as a request is received, it could also be an array of functions.
* `preParsing(request, reply, done)`: a [function](./Hooks.md#preparsing) called
before parsing the request, it could also be an array of functions.
* `preParsing(request, reply, payload, done)`: a
[function](./Hooks.md#preparsing) called before parsing the request, it could
also be an array of functions.
* `preValidation(request, reply, done)`: a [function](./Hooks.md#prevalidation)
called after the shared `preValidation` hooks, useful if you need to perform
authentication at route level for example, it could also be an array of
@@ -95,16 +93,16 @@ fastify.route(options)
* `childLoggerFactory(logger, binding, opts, rawReq)`: a custom factory function
that will be called to produce a child logger instance for every request.
See [`childLoggerFactory`](./Server.md#childloggerfactory) for more info.
Overrides the default logger factory, and anything set by
Overrides the default logger factory, and anything set by
[`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to
the route. To access the default factory, you can access
the route. To access the default factory, you can access
`instance.childLoggerFactory`. Note that this will point to Fastify's default
`childLoggerFactory` only if a plugin hasn't overridden it already.
* `validatorCompiler({ schema, method, url, httpPart })`: function that builds
schemas for request validations. See the [Validation and
Serialization](./Validation-and-Serialization.md#schema-validator)
documentation.
* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:
* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:
function that builds schemas for response serialization. See the [Validation and
Serialization](./Validation-and-Serialization.md#schema-serializer)
documentation.
@@ -123,8 +121,8 @@ fastify.route(options)
* `version`: a [semver](https://semver.org/) compatible string that defined the
version of the endpoint. [Example](#version-constraints).
* `constraints`: defines route restrictions based on request properties or
values, enabling customized matching using
[find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes
values, enabling customized matching using
[find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes
built-in `version` and `host` constraints, with support for custom constraint
strategies.
* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a
@@ -140,11 +138,11 @@ fastify.route(options)
* `reply` is defined in [Reply](./Reply.md).
**Notice:** The documentation of `onRequest`, `preParsing`, `preValidation`,
`preHandler`, `preSerialization`, `onSend`, and `onResponse` are described in
more detail in [Hooks](./Hooks.md). Additionally, to send a response before the
request is handled by the `handler` please refer to [Respond to a request from a
hook](./Hooks.md#respond-to-a-request-from-a-hook).
> Note: The documentation for `onRequest`, `preParsing`, `preValidation`,
> `preHandler`, `preSerialization`, `onSend`, and `onResponse` is detailed in
> [Hooks](./Hooks.md). To send a response before the request is handled by the
> `handler`, see [Respond to a request from
> a hook](./Hooks.md#respond-to-a-request-from-a-hook).
Example:
```js
@@ -153,8 +151,11 @@ fastify.route({
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' }
type: 'object',
properties: {
name: { type: 'string' },
excitement: { type: 'integer' }
}
},
response: {
200: {
@@ -233,17 +234,17 @@ const opts = {
fastify.get('/', opts)
```
> Note: if the handler is specified in both the `options` and as the third
> parameter to the shortcut method then throws a duplicate `handler` error.
> Note: Specifying the handler in both `options` and as the third parameter to
> the shortcut method throws a duplicate `handler` error.
### Url building
<a id="url-building"></a>
Fastify supports both static and dynamic URLs.
To register a **parametric** path, use the *colon* before the parameter name.
For **wildcard**, use the *star*. *Remember that static routes are always
checked before parametric and wildcard.*
To register a **parametric** path, use a *colon* before the parameter name. For
**wildcard**, use a *star*. Static routes are always checked before parametric
and wildcard routes.
```js
// parametric
@@ -265,9 +266,8 @@ fastify.get('/example/:userId/:secretToken', function (request, reply) {
fastify.get('/example/*', function (request, reply) {})
```
Regular expression routes are supported as well, but be aware that you have to
escape slashes. Take note that RegExp is also very expensive in terms of
performance!
Regular expression routes are supported, but slashes must be escaped.
Take note that RegExp is also very expensive in terms of performance!
```js
// parametric with regexp
fastify.get('/example/:file(^\\d+).png', function (request, reply) {
@@ -305,24 +305,24 @@ fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, r
In this case as parameter separator it is possible to use whatever character is
not matched by the regular expression.
The last parameter can be made optional if you add a question mark ("?") to the
end of the parameters name.
The last parameter can be made optional by adding a question mark ("?") to the
end of the parameter name.
```js
fastify.get('/example/posts/:id?', function (request, reply) {
const { id } = request.params;
// your code here
})
```
In this case you can request `/example/posts` as well as `/example/posts/1`.
The optional param will be undefined if not specified.
In this case, `/example/posts` and `/example/posts/1` are both valid. The
optional param will be `undefined` if not specified.
Having a route with multiple parameters may negatively affect performance, so
prefer a single parameter approach whenever possible, especially on routes that
are on the hot path of your application. If you are interested in how we handle
the routing, check out [find-my-way](https://github.com/delvedor/find-my-way).
Having a route with multiple parameters may negatively affect performance.
Prefer a single parameter approach, especially on routes that are on the hot
path of your application. For more details, see
[find-my-way](https://github.com/delvedor/find-my-way).
If you want a path containing a colon without declaring a parameter, use a
double colon. For example:
To include a colon in a path without declaring a parameter, use a double colon.
For example:
```js
fastify.post('/name::verb') // will be interpreted as /name:verb
```
@@ -333,23 +333,23 @@ fastify.post('/name::verb') // will be interpreted as /name:verb
Are you an `async/await` user? We have you covered!
```js
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
const data = await getData()
const processed = await processData(data)
return processed
})
```
As you can see, we are not calling `reply.send` to send back the data to the
user. You just need to return the body and you are done!
As shown, `reply.send` is not called to send data back to the user. Simply
return the body and you are done!
If you need it you can also send back the data to the user with `reply.send`. In
this case do not forget to `return reply` or `await reply` in your `async`
handler or you will introduce a race condition in certain situations.
If needed, you can also send data back with `reply.send`. In this case, do not
forget to `return reply` or `await reply` in your `async` handler to avoid race
conditions.
```js
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
const data = await getData()
const processed = await processData(data)
return reply.send(processed)
})
```
@@ -377,48 +377,42 @@ fastify.get('/', options, async function (request, reply) {
})
```
**Warning:**
* When using both `return value` and `reply.send(value)` at the same time, the
first one that happens takes precedence, the second value will be discarded,
and a *warn* log will also be emitted because you tried to send a response
twice.
* Calling `reply.send()` outside of the promise is possible but requires special
attention. For more details read [promise-resolution](#promise-resolution).
* You cannot return `undefined`. For more details read
[promise-resolution](#promise-resolution).
> Warning:
> * When using both `return value` and `reply.send(value)`, the first one takes
> precedence, the second is discarded, and a *warn* log is emitted.
> * Calling `reply.send()` outside of the promise is possible but requires special
> attention. See [promise-resolution](#promise-resolution).
> * `undefined` cannot be returned. See [promise-resolution](#promise-resolution).
### Promise resolution
<a id="promise-resolution"></a>
If your handler is an `async` function or returns a promise, you should be aware
of the special behavior that is necessary to support the callback and promise
control-flow. When the handler's promise is resolved, the reply will be
automatically sent with its value unless you explicitly await or return `reply`
in your handler.
If the handler is an `async` function or returns a promise, be aware of the
special behavior to support callback and promise control-flow. When the
handler's promise resolves, the reply is automatically sent with its value
unless you explicitly await or return `reply` in the handler.
1. If you want to use `async/await` or promises but respond with a value with
`reply.send`:
1. If using `async/await` or promises but responding with `reply.send`:
- **Do** `return reply` / `await reply`.
- **Do not** forget to call `reply.send`.
2. If you want to use `async/await` or promises:
2. If using `async/await` or promises:
- **Do not** use `reply.send`.
- **Do** return the value that you want to send.
- **Do** return the value to send.
In this way, we can support both `callback-style` and `async-await`, with the
minimum trade-off. Despite so much freedom we highly recommend going with only
one style because error handling should be handled in a consistent way within
your application.
This approach supports both `callback-style` and `async-await` with minimal
trade-off. However, it is recommended to use only one style for consistent
error handling within your application.
**Notice**: Every async function returns a promise by itself.
> Note: Every async function returns a promise by itself.
### Route Prefixing
<a id="route-prefixing"></a>
Sometimes you need to maintain two or more different versions of the same API; a
classic approach is to prefix all the routes with the API version number,
`/v1/user` for example. Fastify offers you a fast and smart way to create
different versions of the same API without changing all the route names by hand,
*route prefixing*. Let's see how it works:
Sometimes maintaining multiple versions of the same API is necessary. A common
approach is to prefix routes with the API version number, e.g., `/v1/user`.
Fastify offers a fast and smart way to create different versions of the same API
without changing all the route names by hand, called *route prefixing*. Here is
how it works:
```js
// server.js
@@ -445,19 +439,18 @@ module.exports = function (fastify, opts, done) {
done()
}
```
Fastify will not complain because you are using the same name for two different
routes, because at compilation time it will handle the prefix automatically
*(this also means that the performance will not be affected at all!)*.
Fastify will not complain about using the same name for two different routes
because it handles the prefix automatically at compilation time. This ensures
performance is not affected.
Now your clients will have access to the following routes:
Now clients will have access to the following routes:
- `/v1/user`
- `/v2/user`
You can do this as many times as you want, it also works for nested `register`,
and route parameters are supported as well.
This can be done multiple times and works for nested `register`. Route
parameters are also supported.
In case you want to use prefix for all of your routes, you can put them inside a
plugin:
To use a prefix for all routes, place them inside a plugin:
```js
const fastify = require('fastify')()
@@ -469,23 +462,21 @@ const route = {
schema: {},
}
fastify.register(function(app, _, done) {
fastify.register(function (app, _, done) {
app.get('/users', () => {})
app.route(route)
done()
}, { prefix: '/v1' }) // global route prefix
await fastify.listen({ port: 0 })
await fastify.listen({ port: 3000 })
```
### Route Prefixing and fastify-plugin
<a id="fastify-plugin"></a>
Be aware that if you use
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) for wrapping your
routes, this option will not work. You can still make it work by wrapping a
plugin in a plugin, e. g.:
If using [`fastify-plugin`](https://github.com/fastify/fastify-plugin) to wrap
routes, this option will not work. To make it work, wrap a plugin in a plugin:
```js
const fp = require('fastify-plugin')
const routes = require('./lib/routes')
@@ -501,27 +492,23 @@ module.exports = fp(async function (app, opts) {
#### Handling of / route inside prefixed plugins
The `/` route has different behavior depending on if the prefix ends with `/` or
not. As an example, if we consider a prefix `/something/`, adding a `/` route
will only match `/something/`. If we consider a prefix `/something`, adding a
`/` route will match both `/something` and `/something/`.
The `/` route behaves differently based on whether the prefix ends with `/`.
For example, with a prefix `/something/`, adding a `/` route matches only
`/something/`. With a prefix `/something`, adding a `/` route matches both
`/something` and `/something/`.
See the `prefixTrailingSlash` route option above to change this behavior.
### Custom Log Level
<a id="custom-log-level"></a>
You might need different log levels in your routes; Fastify achieves this in a
very straightforward way.
Different log levels can be set for routes in Fastify by passing the `logLevel`
option to the plugin or route with the desired
[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string).
You just need to pass the option `logLevel` to the plugin option or the route
option with the
[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string)
that you need.
Be aware that if you set the `logLevel` at plugin level, also the
Be aware that setting `logLevel` at the plugin level also affects
[`setNotFoundHandler`](./Server.md#setnotfoundhandler) and
[`setErrorHandler`](./Server.md#seterrorhandler) will be affected.
[`setErrorHandler`](./Server.md#seterrorhandler).
```js
// server.js
@@ -533,22 +520,21 @@ fastify.register(require('./routes/events'), { logLevel: 'debug' })
fastify.listen({ port: 3000 })
```
Or you can directly pass it to a route:
Or pass it directly to a route:
```js
fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
reply.send({ hello: 'world' })
})
```
*Remember that the custom log level is applied only to the routes, and not to
the global Fastify Logger, accessible with `fastify.log`*
*Remember that the custom log level applies only to routes, not to the global
Fastify Logger, accessible with `fastify.log`.*
### Custom Log Serializer
<a id="custom-log-serializer"></a>
In some contexts, you may need to log a large object but it could be a waste of
resources for some routes. In this case, you can define custom
In some contexts, logging a large object may waste resources. Define custom
[`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object)
and attach them in the right context!
and attach them in the appropriate context.
```js
const fastify = require('fastify')({ logger: true })
@@ -567,7 +553,7 @@ fastify.register(require('./routes/events'), {
fastify.listen({ port: 3000 })
```
You can inherit serializers by context:
Serializers can be inherited by context:
```js
const fastify = Fastify({
@@ -579,7 +565,7 @@ const fastify = Fastify({
method: req.method,
url: req.url,
headers: req.headers,
hostname: req.hostname,
host: req.host,
remoteAddress: req.ip,
remotePort: req.socket.remotePort
}
@@ -616,7 +602,7 @@ retrieve it in the handler.
const fastify = require('fastify')()
function handler (req, reply) {
reply.send(reply.context.config.output)
reply.send(reply.routeOptions.config.output)
}
fastify.get('/en', { config: { output: 'hello world!' } }, handler)
@@ -628,31 +614,29 @@ fastify.listen({ port: 3000 })
### Constraints
<a id="constraints"></a>
Fastify supports constraining routes to match only certain requests based on
some property of the request, like the `Host` header, or any other value via
Fastify supports constraining routes to match certain requests based on
properties like the `Host` header or any other value via
[`find-my-way`](https://github.com/delvedor/find-my-way) constraints.
Constraints are specified in the `constraints` property of the route options.
Fastify has two built-in constraints ready for use: the `version` constraint and
the `host` constraint, and you can add your own custom constraint strategies to
inspect other parts of a request to decide if a route should be executed for a
request.
Fastify has two built-in constraints: `version` and `host`. Custom constraint
strategies can be added to inspect other parts of a request to decide if a route
should be executed.
#### Version Constraints
You can provide a `version` key in the `constraints` option to a route.
Versioned routes allow you to declare multiple handlers for the same HTTP route
path, which will then be matched according to each request's `Accept-Version`
header. The `Accept-Version` header value should follow the
[semver](https://semver.org/) specification, and routes should be declared with
exact semver versions for matching.
Versioned routes allows multiple handlers to be declared for the same HTTP
route path, matched according to the request's `Accept-Version` header.
The `Accept-Version` header value should follow the
[semver](https://semver.org/) specification, and routes should be declared
with exact semver versions for matching.
Fastify will require a request `Accept-Version` header to be set if the route
has a version set, and will prefer a versioned route to a non-versioned route
for the same path. Advanced version ranges and pre-releases currently are not
supported.
*Be aware that using this feature will cause a degradation of the overall
performances of the router.*
> **Note:** using this feature can degrade the routers performance.
```js
fastify.route({
@@ -675,20 +659,20 @@ fastify.inject({
})
```
> ## ⚠ Security Notice
> Remember to set a
> ⚠ Warning:
> Set a
> [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary)
> header in your responses with the value you are using for defining the
> versioning (e.g.: `'Accept-Version'`), to prevent cache poisoning attacks. You
> can also configure this as part of your Proxy/CDN.
> header in responses with the value used for versioning
> (e.g., `'Accept-Version'`) to prevent cache poisoning attacks.
> This can also be configured in a Proxy/CDN.
>
> ```js
> const append = require('vary').append
> fastify.addHook('onSend', (req, reply, payload, done) => {
> if (req.headers['accept-version']) { // or the custom header you are using
> if (req.headers['accept-version']) { // or the custom header being used
> let value = reply.getHeader('Vary') || ''
> const header = Array.isArray(value) ? value.join(', ') : String(value)
> if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using
> if ((value = append(header, 'Accept-Version'))) { // or the custom header being used
> reply.header('Vary', value)
> }
> }
@@ -696,22 +680,20 @@ fastify.inject({
> })
> ```
If you declare multiple versions with the same major or minor, Fastify will
If multiple versions with the same major or minor are declared, Fastify will
always choose the highest compatible with the `Accept-Version` header value.
If the request will not have the `Accept-Version` header, a 404 error will be
returned.
If the request lacks an `Accept-Version` header, a 404 error will be returned.
It is possible to define a custom version matching logic. This can be done
through the [`constraints`](./Server.md#constraints) configuration when creating
a Fastify server instance.
Custom version matching logic can be defined through the
[`constraints`](./Server.md#constraints) configuration when creating a Fastify
server instance.
#### Host Constraints
You can provide a `host` key in the `constraints` route option for to limit that
route to only be matched for certain values of the request `Host` header. `host`
constraint values can be specified as strings for exact matches or RegExps for
arbitrary host matching.
Provide a `host` key in the `constraints` route option to limit the route to
certain values of the request `Host` header. `host` constraint values can be
specified as strings for exact matches or RegExps for arbitrary host matching.
```js
fastify.route({
@@ -751,7 +733,7 @@ matching wildcard subdomains (or any other pattern):
fastify.route({
method: 'GET',
url: '/',
constraints: { host: /.*\.fastify\.io/ }, // will match any subdomain of fastify.dev
constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev
handler: function (request, reply) {
reply.send('hello world from ' + request.headers.host)
}
@@ -760,10 +742,9 @@ fastify.route({
#### Asynchronous Custom Constraints
Custom constraints can be provided and the `constraint` criteria can be
fetched from another source such as `database`. The use of asynchronous
custom constraints should be a last resort as it impacts router
performance.
Custom constraints can be provided, and the `constraint` criteria can be
fetched from another source such as a database. Use asynchronous custom
constraints as a last resort, as they impact router performance.
```js
function databaseOperation(field, done) {
@@ -790,18 +771,18 @@ const secret = {
}
```
> ## ⚠ Security Notice
> When using with asynchronous constraint. It is highly recommend never return error
> inside the callback. If the error is not preventable, it is recommended to provide
> a custom `frameworkErrors` handler to deal with it. Otherwise, you route selection
> may break or expose sensitive information to attackers.
>
> ⚠ Warning:
> When using asynchronous constraints, avoid returning errors inside the
> callback. If errors are unavoidable, provide a custom `frameworkErrors`
> handler to manage them. Otherwise, route selection may break or expose
> sensitive information.
>
> ```js
> const Fastify = require('fastify')
>
>
> const fastify = Fastify({
> frameworkErrors: function(err, res, res) {
> if(err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
> frameworkErrors: function (err, req, res) {
> if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
> res.code(400)
> return res.send("Invalid header provided")
> } else {
@@ -810,25 +791,3 @@ const secret = {
> }
> })
> ```
### ⚠ HTTP version check
Fastify will check the HTTP version of every request, based on configuration
options ([http2](./Server.md#http2), [https](./Server.md#https), and
[serverFactory](./Server.md#serverfactory)), to determine if it matches one or
all of the > following versions: `2.0`, `1.1`, and `1.0`. If Fastify receives a
different HTTP version in the request it will return a `505 HTTP Version Not
Supported` error.
| | 2.0 | 1.1 | 1.0 | skip |
|:------------------------:|:---:|:---:|:---:|:----:|
| http2 | ✓ | | | |
| http2 + https | ✓ | | | |
| http2 + https.allowHTTP1 | ✓ | ✓ | ✓ | |
| https | | ✓ | ✓ | |
| http | | ✓ | ✓ | |
| serverFactory | | | | ✓ |
Note: The internal HTTP version check will be removed in the future when Node
implements [this feature](https://github.com/nodejs/node/issues/43115).

File diff suppressed because it is too large Load Diff

View File

@@ -2,18 +2,16 @@
## Type Providers
Type Providers are a TypeScript only feature that enables Fastify to statically
infer type information directly from inline JSON Schema. They are an alternative
to specifying generic arguments on routes; and can greatly reduce the need to
keep associated types for each schema defined in your project.
Type Providers are a TypeScript feature that enables Fastify to infer type
information from inline JSON Schema. They are an alternative to specifying
generic arguments on routes and can reduce the need to keep associated types for
each schema in a project.
### Providers
Type Providers are offered as additional packages you will need to install into
your project. Each provider uses a different inference library under the hood;
allowing you to select the library most appropriate for your needs. Official Type
Provider packages follow a `@fastify/type-provider-{provider-name}` naming
convention, and there are several community ones available as well.
Official Type Provider packages follow the
`@fastify/type-provider-{provider-name}` naming convention.
Several community providers are also available.
The following inference packages are supported:
@@ -30,78 +28,73 @@ See also the Type Provider wrapper packages for each of the packages respectivel
### Json Schema to Ts
The following sets up a `json-schema-to-ts` Type Provider
The following sets up a `json-schema-to-ts` Type Provider:
```bash
$ npm i @fastify/type-provider-json-schema-to-ts
```
```typescript
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
import fastify from 'fastify'
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
const server = fastify().withTypeProvider<JsonSchemaToTsProvider>()
server.get('/route', {
schema: {
querystring: {
type: 'object',
properties: {
foo: { type: 'number' },
bar: { type: 'string' },
},
required: ['foo', 'bar']
}
schema: {
querystring: {
type: 'object',
properties: {
foo: { type: 'number' },
bar: { type: 'string' },
},
required: ['foo', 'bar']
}
}
}, (request, reply) => {
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
})
```
### TypeBox
The following sets up a TypeBox Type Provider
The following sets up a TypeBox Type Provider:
```bash
$ npm i @fastify/type-provider-typebox
```
```typescript
import fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'
import fastify from 'fastify'
const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
server.get('/route', {
schema: {
querystring: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
schema: {
querystring: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
}, (request, reply) => {
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
})
```
See also the [TypeBox
documentation](https://github.com/sinclairzx81/typebox#validation) on how to set
up AJV to work with TypeBox.
See the [TypeBox
documentation](https://sinclairzx81.github.io/typebox/#/docs/overview/2_setup)
for setting-up AJV to work with TypeBox.
### Zod
See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod)
for Zod type provider instructions.
for Zod Type Provider instructions.
### Scoped Type-Provider
@@ -159,9 +152,9 @@ fastify.register(pluginWithJsonSchema)
fastify.register(pluginWithTypebox)
```
It's also important to mention that once the types don't propagate globally,
_currently_ is not possible to avoid multiple registrations on routes when
dealing with several scopes, see below:
It is important to note that since the types do not propagate globally, it is
currently not possible to avoid multiple registrations on routes when dealing
with several scopes, as shown below:
```ts
import Fastify from 'fastify'
@@ -183,7 +176,7 @@ function plugin1(fastify: FastifyInstance, _opts, done): void {
})
}
}, (req) => {
// it doesn't work! in a new scope needs to call `withTypeProvider` again
// In a new scope, call `withTypeProvider` again to ensure it works
const { x, y, z } = req.body
});
done()
@@ -210,8 +203,8 @@ function plugin2(fastify: FastifyInstance, _opts, done): void {
### Type Definition of FastifyInstance + TypeProvider
When working with modules one has to make use of `FastifyInstance` with Type
Provider generics. See the example below:
When working with modules, use `FastifyInstance` with Type Provider generics.
See the example below:
```ts
// index.ts

View File

@@ -147,7 +147,7 @@ route-level `request` object.
reply.code(200).send('uh-oh');
// it even works for wildcards
reply.code(404).send({ error: 'Not found' });
return `logged in!`
return { success: true }
})
```
@@ -173,7 +173,7 @@ route-level `request` object.
}, async (request, reply) => {
const customerHeader = request.headers['h-Custom']
// do something with request data
return `logged in!`
return { success: true }
})
```
7. Build and run and query with the `username` query string option set to
@@ -182,7 +182,7 @@ route-level `request` object.
admin"}`
🎉 Good work, now you can define interfaces for each route and have strictly
typed request and reply instances. Other parts of the Fastify type system rely
typed request and reply instances. Other parts of the Fastify type system rely
on generic properties. Make sure to reference the detailed type system
documentation below to learn more about what is available.
@@ -210,24 +210,23 @@ And a `zod` wrapper by a third party called [`fastify-type-provider-zod`](https:
They simplify schema validation setup and you can read more about them in [Type
Providers](./Type-Providers.md) page.
Below is how to setup schema validation using _vanilla_ `typebox` and
`json-schema-to-ts` packages.
Below is how to setup schema validation using the `typebox`,
`json-schema-to-typescript`, and `json-schema-to-ts` packages without type
providers.
#### TypeBox
A useful library for building types and a schema at once is
[TypeBox](https://www.npmjs.com/package/@sinclair/typebox) along with
[fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox).
With TypeBox you define your schema within your code and use them
directly as types or schemas as you need them.
A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/@sinclair/typebox).
With TypeBox you define your schema within your code and use them directly as
types or schemas as you need them.
When you want to use it for validation of some payload in a fastify route you
can do it as follows:
1. Install `typebox` and `fastify-type-provider-typebox` in your project.
1. Install `typebox` in your project.
```bash
npm i @sinclair/typebox @fastify/type-provider-typebox
npm i @sinclair/typebox
```
2. Define the schema you need with `Type` and create the respective type with
@@ -248,10 +247,9 @@ can do it as follows:
```typescript
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
// ...
const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()
const fastify = Fastify()
fastify.post<{ Body: UserType, Reply: UserType }>(
'/',
@@ -271,12 +269,12 @@ can do it as follows:
)
```
#### Schemas in JSON Files
#### json-schema-to-typescript
In the last example we used interfaces to define the types for the request
querystring and headers. Many users will already be using JSON Schemas to define
these properties, and luckily there is a way to transform existing JSON Schemas
into TypeScript interfaces!
In the last example we used Typebox to define the types and schemas for our
route. Many users will already be using JSON Schemas to define these properties,
and luckily there is a way to transform existing JSON Schemas into TypeScript
interfaces!
1. If you did not complete the 'Getting Started' example, go back and follow
steps 1-4 first.
@@ -596,7 +594,7 @@ your plugin.
}
module.exports = fp(myPlugin, {
fastify: '3.x',
fastify: '5.x',
name: 'my-plugin' // this is used by fastify-plugin to derive the property name
})
```
@@ -634,7 +632,7 @@ newer, automatically adds `.default` property and a named export to the exported
plugin. Be sure to `export default` and `export const myPlugin` in your typings
to provide the best developer experience. For a complete example you can check
out
[@fastify/swagger](https://github.com/fastify/fastify-swagger/blob/master/index.d.ts).
[@fastify/swagger](https://github.com/fastify/fastify-swagger/blob/main/index.d.ts).
With those files completed, the plugin is now ready to be consumed by any
TypeScript project!
@@ -689,6 +687,143 @@ Or even explicit config on tsconfig
}
```
#### `getDecorator<T>`
Fastify's `getDecorator<T>` method retrieves decorators with enhanced type safety.
The `getDecorator<T>` method supports generic type parameters for enhanced type
safety:
```typescript
// Type-safe decorator retrieval
const usersRepository = fastify.getDecorator<IUsersRepository>('usersRepository')
const session = request.getDecorator<ISession>('session')
const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
```
**Alternative to Module Augmentation**
Decorators are typically typed via module augmentation:
```typescript
declare module 'fastify' {
interface FastifyInstance {
usersRepository: IUsersRepository
}
interface FastifyRequest {
session: ISession
}
interface FastifyReply {
sendSuccess: SendSuccessFn
}
}
```
This approach modifies the Fastify instance globally, which may lead to conflicts
and inconsistent behavior in multi-server setups or with plugin encapsulation.
Using `getDecorator<T>` allows limiting types scope:
```typescript
serverOne.register(async function (fastify) {
const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
'usersRepository'
)
fastify.decorateRequest('session', null)
fastify.addHook('onRequest', async (req, reply) => {
req.setDecorator('session', { user: 'Jean' })
})
fastify.get('/me', (request, reply) => {
const session = request.getDecorator<ISession>('session')
reply.send(session)
})
})
serverTwo.register(async function (fastify) {
const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
'usersRepository'
)
fastify.decorateReply('sendSuccess', function (data) {
return this.send({ success: true })
})
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
await sendSuccess()
})
})
```
**Bound Functions Inference**
To save time, it is common to infer function types instead of writing them manually:
```typescript
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
export type SendSuccess = typeof sendSuccess
```
However, `getDecorator` returns functions with the `this` context already **bound**,
meaning the `this` parameter disappears from the function signature.
To correctly type it, use the `OmitThisParameter` utility:
```typescript
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
fastify.decorateReply('sendSuccess', sendSuccess)
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
await sendSuccess()
})
```
#### `setDecorator<T>`
Fastify's `setDecorator<T>` method provides enhanced type safety for updating request
decorators.
The `setDecorator<T>` method provides enhanced type safety for updating request
decorators:
```typescript
fastify.decorateRequest('user', '')
fastify.addHook('preHandler', async (req, reply) => {
// Type-safe decorator setting
req.setDecorator<string>('user', 'Bob Dylan')
})
```
**Type Safety Benefits**
If the `FastifyRequest` interface does not declare the decorator, type assertions
are typically needed:
```typescript
fastify.addHook('preHandler', async (req, reply) => {
(req as typeof req & { user: string }).user = 'Bob Dylan'
})
```
The `setDecorator<T>` method eliminates the need for explicit type assertions
while providing type safety:
```typescript
fastify.addHook('preHandler', async (req, reply) => {
req.setDecorator<string>('user', 'Bob Dylan')
})
```
## Code Completion In Vanilla JavaScript
Vanilla JavaScript can use the published types to provide code completion (e.g.
@@ -831,7 +966,7 @@ Constraints: `string | Buffer`
#### Fastify
##### fastify<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(opts?: [FastifyServerOptions][FastifyServerOptions]): [FastifyInstance][FastifyInstance]
##### fastify< [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(opts?: [FastifyServerOptions][FastifyServerOptions]): [FastifyInstance][FastifyInstance]
[src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L19)
The main Fastify API method. By default creates an HTTP server. Utilizing
@@ -858,11 +993,11 @@ a more detailed http server walkthrough.
1. Create the following imports from `@types/node` and `fastify`
```typescript
import fs from 'fs'
import path from 'path'
import fs from 'node:fs'
import path from 'node:path'
import fastify from 'fastify'
```
2. Perform the following steps before setting up a Fastify HTTPS server
2. Perform the following steps before setting up a Fastify HTTPS server
to create the `key.pem` and `cert.pem` files:
```sh
openssl genrsa -out key.pem
@@ -920,7 +1055,7 @@ specified at server instantiation, the custom type becomes available on all
further instances of the custom type.
```typescript
import fastify from 'fastify'
import http from 'http'
import http from 'node:http'
interface customRequest extends http.IncomingMessage {
mySpecialProp: string
@@ -986,7 +1121,7 @@ Type alias for `http.Server`
---
##### fastify.FastifyServerOptions<[RawServer][RawServerGeneric], [Logger][LoggerGeneric]>
##### fastify.FastifyServerOptions< [RawServer][RawServerGeneric], [Logger][LoggerGeneric]>
[src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L29)
@@ -997,7 +1132,7 @@ generic parameters are passed down through that method.
See the main [fastify][Fastify] method type definition section for examples on
instantiating a Fastify server with TypeScript.
##### fastify.FastifyInstance<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RequestGeneric][FastifyRequestGenericInterface], [Logger][LoggerGeneric]>
##### fastify.FastifyInstance< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RequestGeneric][FastifyRequestGenericInterface], [Logger][LoggerGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/instance.d.ts#L16)
@@ -1020,7 +1155,7 @@ details on this interface.
#### Request
##### fastify.FastifyRequest<[RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
##### fastify.FastifyRequest< [RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/request.d.ts#L15)
This interface contains properties of Fastify request object. The properties
@@ -1108,8 +1243,8 @@ returns `http.IncomingMessage`, otherwise, it returns
`http2.Http2ServerRequest`.
```typescript
import http from 'http'
import http2 from 'http2'
import http from 'node:http'
import http2 from 'node:http2'
import { RawRequestDefaultExpression } from 'fastify'
RawRequestDefaultExpression<http.Server> // -> http.IncomingMessage
@@ -1120,7 +1255,7 @@ RawRequestDefaultExpression<http2.Http2Server> // -> http2.Http2ServerRequest
#### Reply
##### fastify.FastifyReply<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
##### fastify.FastifyReply<[RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [ContextConfig][ContextConfigGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/reply.d.ts#L32)
This interface contains the custom properties that Fastify adds to the standard
@@ -1156,7 +1291,7 @@ declare module 'fastify' {
}
```
##### fastify.RawReplyDefaultExpression<[RawServer][RawServerGeneric]>
##### fastify.RawReplyDefaultExpression< [RawServer][RawServerGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L27)
Dependent on `@types/node` modules `http`, `https`, `http2`
@@ -1168,8 +1303,8 @@ returns `http.ServerResponse`, otherwise, it returns
`http2.Http2ServerResponse`.
```typescript
import http from 'http'
import http2 from 'http2'
import http from 'node:http'
import http2 from 'node:http2'
import { RawReplyDefaultExpression } from 'fastify'
RawReplyDefaultExpression<http.Server> // -> http.ServerResponse
@@ -1188,19 +1323,19 @@ When creating plugins for Fastify, it is recommended to use the `fastify-plugin`
module. Additionally, there is a guide to creating plugins with TypeScript and
Fastify available in the Learn by Example, [Plugins](#plugins) section.
##### fastify.FastifyPluginCallback<[Options][FastifyPluginOptions]>
##### fastify.FastifyPluginCallback< [Options][FastifyPluginOptions]>
[src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L9)
Interface method definition used within the
[`fastify.register()`][FastifyRegister] method.
##### fastify.FastifyPluginAsync<[Options][FastifyPluginOptions]>
##### fastify.FastifyPluginAsync< [Options][FastifyPluginOptions]>
[src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L20)
Interface method definition used within the
[`fastify.register()`][FastifyRegister] method.
##### fastify.FastifyPlugin<[Options][FastifyPluginOptions]>
##### fastify.FastifyPlugin< [Options][FastifyPluginOptions]>
[src](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L29)
Interface method definition used within the
@@ -1269,7 +1404,7 @@ a function that returns the previously described intersection.
Check out the [Specifying Logger Types](#example-5-specifying-logger-types)
example for more details on specifying a custom logger.
##### fastify.FastifyLoggerOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]>
##### fastify.FastifyLoggerOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/logger.d.ts#L17)
@@ -1332,7 +1467,7 @@ One of the core principles in Fastify is its routing capabilities. Most of the
types defined in this section are used under-the-hood by the Fastify instance
`.route` and `.get/.post/.etc` methods.
##### fastify.RouteHandlerMethod<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
##### fastify.RouteHandlerMethod< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L105)
@@ -1342,7 +1477,7 @@ The generics parameters are passed through to these arguments. The method
returns either `void` or `Promise<any>` for synchronous and asynchronous
handlers respectively.
##### fastify.RouteOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
##### fastify.RouteOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L78)
@@ -1354,14 +1489,14 @@ required properties:
3. `handler` the route handler method, see [RouteHandlerMethod][] for more
details
##### fastify.RouteShorthandMethod<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]>
##### fastify.RouteShorthandMethod< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#12)
An overloaded function interface for three kinds of shorthand route methods to
be used in conjunction with the `.get/.post/.etc` methods.
##### fastify.RouteShorthandOptions<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
##### fastify.RouteShorthandOptions< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#55)
@@ -1369,7 +1504,7 @@ An interface that covers all of the base options for a route. Each property on
this interface is optional, and it serves as the base for the RouteOptions and
RouteShorthandOptionsWithHandler interfaces.
##### fastify.RouteShorthandOptionsWithHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
##### fastify.RouteShorthandOptionsWithHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/route.d.ts#93)
@@ -1384,21 +1519,21 @@ interface `handler` which is of type RouteHandlerMethod
A generic type that is either a `string` or `Buffer`
##### fastify.FastifyBodyParser<[RawBody][RawBodyGeneric], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
##### fastify.FastifyBodyParser< [RawBody][RawBodyGeneric], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L7)
A function type definition for specifying a body parser method. Use the
`RawBody` generic to specify the type of the body being parsed.
##### fastify.FastifyContentTypeParser<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
##### fastify.FastifyContentTypeParser< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L17)
A function type definition for specifying a body parser method. Content is typed
via the `RawRequest` generic.
##### fastify.AddContentTypeParser<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
##### fastify.AddContentTypeParser< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/content-type-parser.d.ts#L46)
@@ -1440,7 +1575,7 @@ This interface is passed to instance of FastifyError.
#### Hooks
##### fastify.onRequestHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.onRequestHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L17)
@@ -1450,7 +1585,7 @@ no previous hook, the next hook will be `preParsing`.
Notice: in the `onRequest` hook, request.body will always be null, because the
body parsing happens before the `preHandler` hook.
##### fastify.preParsingHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.preParsingHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L35)
@@ -1465,21 +1600,21 @@ stream. This property is used to correctly match the request payload with the
`Content-Length` header value. Ideally, this property should be updated on each
received chunk.
##### fastify.preValidationHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.preValidationHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L53)
`preValidation` is the third hook to be executed in the request lifecycle. The
previous hook was `preParsing`, the next hook will be `preHandler`.
##### fastify.preHandlerHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.preHandlerHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L70)
`preHandler` is the fourth hook to be executed in the request lifecycle. The
previous hook was `preValidation`, the next hook will be `preSerialization`.
##### fastify.preSerializationHookHandler<PreSerializationPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: PreSerializationPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\<unknown\> | void
##### fastify.preSerializationHookHandler< PreSerializationPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: PreSerializationPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L94)
@@ -1489,7 +1624,7 @@ The previous hook was `preHandler`, the next hook will be `onSend`.
Note: the hook is NOT called if the payload is a string, a Buffer, a stream or
null.
##### fastify.onSendHookHandler<OnSendPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\<unknown\> | void
##### fastify.onSendHookHandler< OnSendPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L114)
@@ -1500,7 +1635,7 @@ next hook will be `onResponse`.
Note: If you change the payload, you may only change it to a string, a Buffer, a
stream, or null.
##### fastify.onResponseHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.onResponseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L134)
@@ -1511,7 +1646,7 @@ The onResponse hook is executed when a response has been sent, so you will not
be able to send more data to the client. It can however be useful for sending
data to external services, for example to gather statistics.
##### fastify.onErrorHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], error: [FastifyError][FastifyError], done: () => void): Promise\<unknown\> | void
##### fastify.onErrorHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], error: [FastifyError][FastifyError], done: () => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L154)
@@ -1521,14 +1656,12 @@ specific header in case of error.
It is not intended for changing the error, and calling reply.send will throw an
exception.
This hook will be executed only after the customErrorHandler has been executed,
and only if the customErrorHandler sends an error back to the user (Note that
the default customErrorHandler always sends the error back to the user).
This hook will be executed before the customErrorHandler.
Notice: unlike the other hooks, pass an error to the done function is not
supported.
##### fastify.onRouteHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(opts: [RouteOptions][RouteOptions] & { path: string; prefix: string }): Promise\<unknown\> | void
##### fastify.onRouteHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(opts: [RouteOptions][RouteOptions] & \{ path: string; prefix: string }): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L174)
@@ -1536,7 +1669,7 @@ Triggered when a new route is registered. Listeners are passed a routeOptions
object as the sole parameter. The interface is synchronous, and, as such, the
listener does not get passed a callback
##### fastify.onRegisterHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.onRegisterHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L191)
@@ -1548,7 +1681,7 @@ plugin context is formed, and you want to operate in that specific context.
Note: This hook will not be called if a plugin is wrapped inside fastify-plugin.
##### fastify.onCloseHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
##### fastify.onCloseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L206)

View File

@@ -1,67 +1,63 @@
<h1 align="center">Fastify</h1>
## Validation and Serialization
Fastify uses a schema-based approach, and even if it is not mandatory we
recommend using [JSON Schema](https://json-schema.org/) to validate your routes
and serialize your outputs. Internally, Fastify compiles the schema into a
highly performant function.
Fastify uses a schema-based approach. We recommend using
[JSON Schema](https://json-schema.org/) to validate routes and serialize outputs.
Fastify compiles the schema into a highly performant function.
Validation will only be attempted if the content type is `application-json`, as
described in the documentation for the [content type
parser](./ContentTypeParser.md).
Validation is only attempted if the content type is `application/json`.
All the examples in this section are using the [JSON Schema Draft
7](https://json-schema.org/specification-links.html#draft-7) specification.
All examples use the
[JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7)
specification.
> ## ⚠ Security Notice
> Treat the schema definition as application code. Validation and serialization
> features dynamically evaluate code with `new Function()`, which is not safe to
> use with user-provided schemas. See [Ajv](https://npm.im/ajv) and
> [fast-json-stringify](https://npm.im/fast-json-stringify) for more details.
> ⚠ Warning:
> Treat schema definitions as application code. Validation and serialization
> features use `new Function()`, which is unsafe with user-provided schemas. See
> [Ajv](https://npm.im/ajv) and
> [fast-json-stringify](https://npm.im/fast-json-stringify) for details.
>
> Regardless the [`$async` Ajv
> feature](https://ajv.js.org/guide/async-validation.html) is supported
> by Fastify, it should not be used as
> part of the first validation strategy. This option is used to access Databases
> and reading them during the validation process may lead to Denial of Service
> Attacks to your application. If you need to run `async` tasks, use [Fastify's
> hooks](./Hooks.md) instead after validation completes, such as `preHandler`.
> Whilst Fastify supports the
> [`$async` Ajv feature](https://ajv.js.org/guide/async-validation.html),
> it should not be used for initial validation. Accessing databases during
> validation may lead to Denial of Service attacks. Use
> [Fastify's hooks](./Hooks.md) like `preHandler` for `async` tasks after validation.
>
> When using custom validators with async `preValidation` hooks,
> validators **must return** `{error}` objects instead of throwing errors.
> Throwing errors from custom validators will cause unhandled promise rejections
> that crash the application when combined with async hooks. See the
> [custom validator examples](#using-other-validation-libraries) below for the
> correct pattern.
### Core concepts
The validation and the serialization tasks are processed by two different, and
customizable, actors:
- [Ajv v8](https://www.npmjs.com/package/ajv) for the validation of a request
Validation and serialization are handled by two customizable dependencies:
- [Ajv v8](https://www.npmjs.com/package/ajv) for request validation
- [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for
the serialization of a response's body
response body serialization
These two separate entities share only the JSON schemas added to Fastify's
instance through `.addSchema(schema)`.
These dependencies share only the JSON schemas added to Fastify's instance via
`.addSchema(schema)`.
#### Adding a shared schema
<a id="shared-schema"></a>
Thanks to the `addSchema` API, you can add multiple schemas to the Fastify
instance and then reuse them in multiple parts of your application. As usual,
this API is encapsulated.
The `addSchema` API allows adding multiple schemas to the Fastify instance for
reuse throughout the application. This API is encapsulated.
The shared schemas can be reused through the JSON Schema
Shared schemas can be reused with the JSON Schema
[**`$ref`**](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8)
keyword. Here is an overview of _how_ references work:
keyword. Here is an overview of how references work:
+ `myField: { $ref: '#foo'}` will search for field with `$id: '#foo'` inside the
+ `myField: { $ref: '#foo' }` searches for `$id: '#foo'` in the current schema
+ `myField: { $ref: '#/definitions/foo' }` searches for `definitions.foo` in the
current schema
+ `myField: { $ref: '#/definitions/foo'}` will search for field
`definitions.foo` inside the current schema
+ `myField: { $ref: 'http://url.com/sh.json#'}` will search for a shared schema
added with `$id: 'http://url.com/sh.json'`
+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo'}` will search for
a shared schema added with `$id: 'http://url.com/sh.json'` and will use the
field `definitions.foo`
+ `myField: { $ref: 'http://url.com/sh.json#foo'}` will search for a shared
schema added with `$id: 'http://url.com/sh.json'` and it will look inside of
it for object with `$id: '#foo'`
+ `myField: { $ref: 'http://url.com/sh.json#' }` searches for a shared schema
with `$id: 'http://url.com/sh.json'`
+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo' }` searches for a
shared schema with `$id: 'http://url.com/sh.json'` and uses `definitions.foo`
+ `myField: { $ref: 'http://url.com/sh.json#foo' }` searches for a shared schema
with `$id: 'http://url.com/sh.json'` and looks for `$id: '#foo'` within it
**Simple usage:**
@@ -108,9 +104,9 @@ fastify.post('/', {
#### Retrieving the shared schemas
<a id="get-shared-schema"></a>
If the validator and the serializer are customized, the `.addSchema` method will
not be useful since the actors are no longer controlled by Fastify. To access
the schemas added to the Fastify instance, you can simply use `.getSchemas()`:
If the validator and serializer are customized, `.addSchema` is not useful since
Fastify no longer controls them. To access schemas added to the Fastify instance,
use `.getSchemas()`:
```js
fastify.addSchema({
@@ -125,8 +121,8 @@ const mySchemas = fastify.getSchemas()
const mySchema = fastify.getSchema('schemaId')
```
As usual, the function `getSchemas` is encapsulated and returns the shared
schemas available in the selected scope:
The `getSchemas` function is encapsulated and returns shared schemas available
in the selected scope:
```js
fastify.addSchema({ $id: 'one', my: 'hello' })
@@ -150,25 +146,22 @@ fastify.register((instance, opts, done) => {
### Validation
The route validation internally relies upon [Ajv
v8](https://www.npmjs.com/package/ajv) which is a high-performance JSON Schema
validator. Validating the input is very easy: just add the fields that you need
inside the route schema, and you are done!
Route validation relies on [Ajv v8](https://www.npmjs.com/package/ajv), a
high-performance JSON Schema validator. To validate input, add the required
fields to the route schema.
The supported validations are:
- `body`: validates the body of the request if it is a POST, PUT, or PATCH
method.
Supported validations include:
- `body`: validates the request body for POST, PUT, or PATCH methods.
- `querystring` or `query`: validates the query string.
- `params`: validates the route params.
- `params`: validates the route parameters.
- `headers`: validates the request headers.
All the validations can be a complete JSON Schema object (with a `type` property
of `'object'` and a `'properties'` object containing parameters) or a simpler
variation in which the `type` and `properties` attributes are forgone and the
parameters are listed at the top level (see the example below).
Validations can be a complete JSON Schema object with a `type` of `'object'` and
a `'properties'` object containing parameters, or a simpler variation listing
parameters at the top level.
> If you need to use the latest version of Ajv (v8) you should read how to do
> it in the [`schemaController`](./Server.md#schema-controller) section.
> For using the latest Ajv (v8), refer to the
> [`schemaController`](./Server.md#schema-controller) section.
Example:
```js
@@ -257,9 +250,9 @@ fastify.post('/the/url', {
}, handler)
```
*Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values
to the types specified in your schema `type` keywords, both to pass the
validation and to use the correctly typed data afterwards.*
Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) values to
the types specified in the schema `type` keywords, both to pass validation and
to use the correctly typed data afterwards.
The Ajv default configuration in Fastify supports coercing array parameters in
`querystring`. Example:
@@ -294,11 +287,11 @@ curl -X GET "http://localhost:3000/?ids=1
{"params":{"ids":["1"]}}
```
You can also specify a custom schema validator for each parameter type (body,
A custom schema validator can be specified for each parameter type (body,
querystring, params, headers).
For example, the following code disable type coercion only for the `body`
parameters, changing the ajv default options:
For example, the following code disables type coercion only for the `body`
parameters, changing the Ajv default options:
```js
const schemaCompilers = {
@@ -336,16 +329,15 @@ server.setValidatorCompiler(req => {
})
```
For further information see [here](https://ajv.js.org/coercion.html)
For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html).
#### Ajv Plugins
<a id="ajv-plugins"></a>
You can provide a list of plugins you want to use with the default `ajv`
instance. Note that the plugin must be **compatible with the Ajv version shipped
within Fastify**.
A list of plugins can be provided for use with the default `ajv` instance.
Ensure the plugin is **compatible with the Ajv version shipped within Fastify**.
> Refer to [`ajv options`](./Server.md#ajv) to check plugins format
> Refer to [`ajv options`](./Server.md#ajv) to check plugins format.
```js
const fastify = require('fastify')({
@@ -406,11 +398,10 @@ fastify.post('/foo', {
#### Validator Compiler
<a id="schema-validator"></a>
The `validatorCompiler` is a function that returns a function that validates the
body, URL parameters, headers, and query string. The default
`validatorCompiler` returns a function that implements the
[ajv](https://ajv.js.org/) validation interface. Fastify uses it internally to
speed the validation up.
The `validatorCompiler` is a function that returns a function to validate the
body, URL parameters, headers, and query string. The default `validatorCompiler`
returns a function that implements the [ajv](https://ajv.js.org/) validation
interface. Fastify uses it internally to speed up validation.
Fastify's [baseline ajv
configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is:
@@ -428,11 +419,11 @@ configuration](https://github.com/fastify/ajv-compiler#ajv-configuration) is:
}
```
This baseline configuration can be modified by providing
[`ajv.customOptions`](./Server.md#factory-ajv) to your Fastify factory.
Modify the baseline configuration by providing
[`ajv.customOptions`](./Server.md#factory-ajv) to the Fastify factory.
If you want to change or set additional config options, you will need to create
your own instance and override the existing one like:
To change or set additional config options, create a custom instance and
override the existing one:
```js
const fastify = require('fastify')()
@@ -448,29 +439,39 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
return ajv.compile(schema)
})
```
_**Note:** If you use a custom instance of any validator (even Ajv), you have to
add schemas to the validator instead of Fastify, since Fastify's default
validator is no longer used, and Fastify's `addSchema` method has no idea what
validator you are using._
> Note: When using a custom validator instance, add schemas to the validator
> instead of Fastify. Fastify's `addSchema` method will not recognize the custom
> validator.
##### Using other validation libraries
<a id="using-other-validation-libraries"></a>
The `setValidatorCompiler` function makes it easy to substitute `ajv` with
almost any JavaScript validation library ([joi](https://github.com/hapijs/joi/),
[yup](https://github.com/jquense/yup/), ...) or a custom one:
The `setValidatorCompiler` function allows substituting `ajv` with other
JavaScript validation libraries like [joi](https://github.com/hapijs/joi/) or
[yup](https://github.com/jquense/yup/), or a custom one:
```js
const Joi = require('joi')
fastify.setValidatorCompiler(({ schema }) => {
return (data) => {
try {
const { error, value } = schema.validate(data)
if (error) {
return { error } // Return the error, do not throw it
}
return { value }
} catch (e) {
return { error: e } // Catch any unexpected errors too
}
}
})
fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
},
validatorCompiler: ({ schema, method, url, httpPart }) => {
return data => schema.validate(data)
}
}, handler)
```
@@ -509,10 +510,44 @@ fastify.post('/the/url', {
}, handler)
```
##### Custom Validator Best Practices
When implementing custom validators, follow these patterns to ensure compatibility
with all Fastify features:
** Always return objects, never throw:**
```js
return { value: validatedData } // On success
return { error: validationError } // On failure
```
** Use try-catch for safety:**
```js
fastify.setValidatorCompiler(({ schema }) => {
return (data) => {
try {
// Validation logic here
const result = schema.validate(data)
if (result.error) {
return { error: result.error }
}
return { value: result.value }
} catch (e) {
// Catch any unexpected errors
return { error: e }
}
}
})
```
This pattern ensures validators work correctly with both sync and async
`preValidation` hooks, preventing unhandled promise rejections that can crash
an application.
##### .statusCode property
All validation errors will be added a `.statusCode` property set to `400`. This guarantees
that the default error handler will set the status code of the response to `400`.
All validation errors have a `.statusCode` property set to `400`, ensuring the
default error handler sets the response status code to `400`.
```js
fastify.setErrorHandler(function (error, request, reply) {
@@ -525,30 +560,27 @@ fastify.setErrorHandler(function (error, request, reply) {
Fastify's validation error messages are tightly coupled to the default
validation engine: errors returned from `ajv` are eventually run through the
`schemaErrorFormatter` function which is responsible for building human-friendly
error messages. However, the `schemaErrorFormatter` function is written with
`ajv` in mind. As a result, you may run into odd or incomplete error messages
when using other validation libraries.
`schemaErrorFormatter` function which builds human-friendly error messages.
However, the `schemaErrorFormatter` function is written with `ajv` in mind.
This may result in odd or incomplete error messages when using other validation
libraries.
To circumvent this issue, you have 2 main options :
To circumvent this issue, there are two main options:
1. make sure your validation function (returned by your custom `schemaCompiler`)
returns errors in the same structure and format as `ajv` (although this could
prove to be difficult and tricky due to differences between validation
engines)
2. or use a custom `errorHandler` to intercept and format your 'custom'
validation errors
1. Ensure the validation function (returned by the custom `schemaCompiler`)
returns errors in the same structure and format as `ajv`.
2. Use a custom `errorHandler` to intercept and format custom validation errors.
To help you in writing a custom `errorHandler`, Fastify adds 2 properties to all
validation errors:
Fastify adds two properties to all validation errors to help write a custom
`errorHandler`:
* `validation`: the content of the `error` property of the object returned by
the validation function (returned by your custom `schemaCompiler`)
* `validationContext`: the 'context' (body, params, query, headers) where the
the validation function (returned by the custom `schemaCompiler`)
* `validationContext`: the context (body, params, query, headers) where the
validation error occurred
A very contrived example of such a custom `errorHandler` handling validation
errors is shown below:
A contrived example of such a custom `errorHandler` handling validation errors
is shown below:
```js
const errorHandler = (error, request, reply) => {
@@ -560,9 +592,9 @@ const errorHandler = (error, request, reply) => {
// check if we have a validation error
if (validation) {
response = {
// validationContext will be 'body' or 'params' or 'headers' or 'query'
// validationContext will be 'body', 'params', 'headers', or 'query'
message: `A validation error occurred when validating the ${validationContext}...`,
// this is the result of your validation library...
// this is the result of the validation library...
errors: validation
}
} else {
@@ -581,12 +613,10 @@ const errorHandler = (error, request, reply) => {
### Serialization
<a id="serialization"></a>
Usually, you will send your data to the clients as JSON, and Fastify has a
powerful tool to help you,
[fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify), which
is used if you have provided an output schema in the route options. We encourage
you to use an output schema, as it can drastically increase throughput and help
prevent accidental disclosure of sensitive information.
Fastify uses [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify)
to send data as JSON if an output schema is provided in the route options. Using
an output schema can drastically increase throughput and help prevent accidental
disclosure of sensitive information.
Example:
```js
@@ -605,9 +635,8 @@ const schema = {
fastify.post('/the/url', { schema }, handler)
```
As you can see, the response schema is based on the status code. If you want to
use the same schema for multiple status codes, you can use `'2xx'` or `default`,
for example:
The response schema is based on the status code. To use the same schema for
multiple status codes, use `'2xx'` or `default`, for example:
```js
const schema = {
response: {
@@ -636,41 +665,51 @@ const schema = {
fastify.post('/the/url', { schema }, handler)
```
You can even have a specific response schema for different content types.
A specific response schema can be defined for different content types.
For example:
```js
const schema = {
response: {
200: {
description: 'Response schema that support different content types'
content: {
'application/json': {
schema: {
name: { type: 'string' },
image: { type: 'string' },
address: { type: 'string' }
}
},
'application/vnd.v1+json': {
schema: {
type: 'array',
items: { $ref: 'test' }
}
}
response: {
200: {
description: 'Response schema that support different content types'
content: {
'application/json': {
schema: {
name: { type: 'string' },
image: { type: 'string' },
address: { type: 'string' }
}
},
'3xx': {
content: {
'application/vnd.v2+json': {
schema: {
fullName: { type: 'string' },
phone: { type: 'string' }
}
}
'application/vnd.v1+json': {
schema: {
type: 'array',
items: { $ref: 'test' }
}
}
}
},
'3xx': {
content: {
'application/vnd.v2+json': {
schema: {
fullName: { type: 'string' },
phone: { type: 'string' }
}
}
}
},
default: {
content: {
// */* is match-all content-type
'*/*': {
schema: {
desc: { type: 'string' }
}
}
}
}
}
}
fastify.post('/url', { schema }, handler)
```
@@ -678,10 +717,9 @@ fastify.post('/url', { schema }, handler)
#### Serializer Compiler
<a id="schema-serializer"></a>
The `serializerCompiler` is a function that returns a function that must return
a string from an input object. When you define a response JSON Schema, you can
change the default serialization method by providing a function to serialize
every route where you do.
The `serializerCompiler` returns a function that must return a string from an
input object. When defining a response JSON Schema, change the default
serialization method by providing a function to serialize each route.
```js
fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
@@ -695,21 +733,24 @@ fastify.get('/user', {
schema: {
response: {
'2xx': {
id: { type: 'number' },
name: { type: 'string' }
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' }
}
}
}
}
})
```
*If you need a custom serializer in a very specific part of your code, you can
set one with [`reply.serializer(...)`](./Reply.md#serializerfunc).*
*To set a custom serializer in a specific part of the code, use
[`reply.serializer(...)`](./Reply.md#serializerfunc).*
### Error Handling
When schema validation fails for a request, Fastify will automatically return a
status 400 response including the result from the validator in the payload. As
an example, if you have the following schema for your route
status 400 response including the result from the validator in the payload. For
example, if the following schema is used for a route:
```js
const schema = {
@@ -723,8 +764,8 @@ const schema = {
}
```
and fail to satisfy it, the route will immediately return a response with the
following payload
If the request fails to satisfy the schema, the route will return a response
with the following payload:
```js
{
@@ -734,10 +775,15 @@ following payload
}
```
If you want to handle errors inside the route, you can specify the
`attachValidation` option for your route. If there is a _validation error_, the
`validationError` property of the request will contain the `Error` object with
the raw `validation` result as shown below
> ⚠ Security Consideration: By default, validation error details from the schema
> are included in the response payload. If your organization requires sanitizing
> or customizing these error messages (e.g., to avoid exposing internal schema
> details), configure a custom error handler using
> [`setErrorHandler()`](./Server.md#seterrorhandler).
To handle errors inside the route, specify the `attachValidation` option. If
there is a validation error, the `validationError` property of the request will
contain the `Error` object with the raw validation result as shown below:
```js
const fastify = Fastify()
@@ -752,13 +798,13 @@ fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
#### `schemaErrorFormatter`
If you want to format errors yourself, you can provide a sync function that must
return an error as the `schemaErrorFormatter` option to Fastify when
instantiating. The context function will be the Fastify server instance.
To format errors, provide a sync function that returns an error as the
`schemaErrorFormatter` option when instantiating Fastify. The context function
will be the Fastify server instance.
`errors` is an array of Fastify schema errors `FastifySchemaValidationError`.
`dataVar` is the currently validated part of the schema. (params | body |
querystring | headers).
`dataVar` is the currently validated part of the schema (params, body,
querystring, headers).
```js
const fastify = Fastify({
@@ -776,8 +822,8 @@ fastify.setSchemaErrorFormatter(function (errors, dataVar) {
})
```
You can also use [setErrorHandler](./Server.md#seterrorhandler) to define a
custom response for validation errors such as
Use [setErrorHandler](./Server.md#seterrorhandler) to define a custom response
for validation errors such as:
```js
fastify.setErrorHandler(function (error, request, reply) {
@@ -787,25 +833,25 @@ fastify.setErrorHandler(function (error, request, reply) {
})
```
If you want a custom error response in the schema without headaches, and
quickly, take a look at
For custom error responses in the schema, see
[`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Check out the
[example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js)
usage.
> Make sure to install version 1.0.1 of `ajv-errors`, because later versions of
> it are not compatible with AJV v6 (the version shipped by Fastify v3).
> Install version 1.0.1 of `ajv-errors`, as later versions are not compatible
> with AJV v6 (the version shipped by Fastify v3).
Below is an example showing how to add **custom error messages for each
property** of a schema by supplying custom AJV options. Inline comments in the
schema below describe how to configure it to show a different error message for
each case:
schema describe how to configure it to show a different error message for each
case:
```js
const fastify = Fastify({
ajv: {
customOptions: {
jsonPointers: true,
// Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/
// Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/
allErrors: true
},
plugins: [
@@ -849,8 +895,8 @@ fastify.post('/', { schema, }, (request, reply) => {
})
```
If you want to return localized error messages, take a look at
[ajv-i18n](https://github.com/epoberezkin/ajv-i18n)
To return localized error messages, see
[ajv-i18n](https://github.com/epoberezkin/ajv-i18n).
```js
const localize = require('ajv-i18n')
@@ -884,8 +930,8 @@ fastify.setErrorHandler(function (error, request, reply) {
### JSON Schema support
JSON Schema provides utilities to optimize your schemas that, in conjunction
with Fastify's shared schema, let you reuse all your schemas easily.
JSON Schema provides utilities to optimize schemas. Combined with Fastify's
shared schema, all schemas can be easily reused.
| Use Case | Validator | Serializer |
|-----------------------------------|-----------|------------|
@@ -992,11 +1038,11 @@ const refToSharedSchemaDefinitions = {
- [JSON Schema](https://json-schema.org/)
- [Understanding JSON
Schema](https://spacetelescope.github.io/understanding-json-schema/)
Schema](https://json-schema.org/understanding-json-schema/about)
- [fast-json-stringify
documentation](https://github.com/fastify/fast-json-stringify)
- [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md)
- [Ajv i18n](https://github.com/epoberezkin/ajv-i18n)
- [Ajv custom errors](https://github.com/epoberezkin/ajv-errors)
- Custom error handling with core methods with error file dumping
[example](https://github.com/fastify/example/tree/master/validation-messages)
[example](https://github.com/fastify/example/tree/main/validation-messages)

View File

@@ -8,54 +8,33 @@
- [FSTWRN001](#FSTWRN001)
- [FSTWRN002](#FSTWRN002)
- [Fastify Deprecation Codes](#fastify-deprecation-codes)
- [FSTDEP005](#FSTDEP005)
- [FSTDEP006](#FSTDEP006)
- [FSTDEP007](#FSTDEP007)
- [FSTDEP008](#FSTDEP008)
- [FSTDEP009](#FSTDEP009)
- [FSTDEP010](#FSTDEP010)
- [FSTDEP011](#FSTDEP011)
- [FSTDEP012](#FSTDEP012)
- [FSTDEP013](#FSTDEP013)
- [FSTDEP014](#FSTDEP014)
- [FSTDEP015](#FSTDEP015)
- [FSTDEP016](#FSTDEP016)
- [FSTDEP017](#FSTDEP017)
- [FSTDEP018](#FSTDEP018)
- [FSTDEP019](#FSTDEP019)
- [FSTDEP020](#FSTDEP020)
- [FSTDEP021](#FSTDEP021)
- [FSTDEP022](#FSTDEP022)
## Warnings
### Warnings In Fastify
Fastify utilizes Node.js's [warning event](https://nodejs.org/api/process.html#event-warning)
API to notify users of deprecated features and known coding mistakes. Fastify's
warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes on warning
code. When encountering such a warning, it is highly recommended that the
cause of the warning be determined through use of the
[`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings) and
[`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation)
flags. These will produce stack traces pointing out where the issue occurs
in the application's code. Issues opened about warnings without including
this information may be closed due to lack of information.
Fastify uses Node.js's [warning event](https://nodejs.org/api/process.html#event-warning)
API to notify users of deprecated features and coding mistakes. Fastify's
warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes. When
encountering such a warning, it is highly recommended to determine the cause
using the [`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings)
and [`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation)
flags. These produce stack traces pointing to where the issue occurs in the
application's code. Issues opened about warnings without this information will
be closed due to lack of details.
In addition to tracing, warnings can also be disabled. It is not recommended to
disable warnings as a matter of course, but if necessary, they can be disabled
by using any of the following methods:
Warnings can also be disabled, though it is not recommended. If necessary, use
one of the following methods:
- setting the `NODE_NO_WARNINGS` environment variable to `1`
- passing the `--no-warnings` flag to the node process
- setting 'no-warnings' in the `NODE_OPTIONS` environment variable
- Set the `NODE_NO_WARNINGS` environment variable to `1`
- Pass the `--no-warnings` flag to the node process
- Set `no-warnings` in the `NODE_OPTIONS` environment variable
For more information on how to disable warnings, see [node's documentation](https://nodejs.org/api/cli.html).
For more information on disabling warnings, see [Node's documentation](https://nodejs.org/api/cli.html).
However, disabling warnings is not recommended as it may cause
potential problems when upgrading Fastify versions.
Only experienced users should consider disabling warnings.
Disabling warnings may cause issues when upgrading Fastify versions. Only
experienced users should consider disabling warnings.
### Fastify Warning Codes
@@ -67,7 +46,7 @@ Only experienced users should consider disabling warnings.
### Fastify Deprecation Codes
Deprecation codes are further supported by the Node.js CLI options:
Deprecation codes are supported by the Node.js CLI options:
- [--no-deprecation](https://nodejs.org/api/cli.html#--no-deprecation)
- [--throw-deprecation](https://nodejs.org/api/cli.html#--throw-deprecation)
@@ -76,21 +55,4 @@ Deprecation codes are further supported by the Node.js CLI options:
| Code | Description | How to solve | Discussion |
| ---- | ----------- | ------------ | ---------- |
| <a id="FSTDEP005">FSTDEP005</a> | You are accessing the deprecated `request.connection` property. | Use `request.socket`. | [#2594](https://github.com/fastify/fastify/pull/2594) |
| <a id="FSTDEP006">FSTDEP006</a> | You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. | Do not use Arrays/Objects as values when decorating Request/Reply. | [#2688](https://github.com/fastify/fastify/pull/2688) |
| <a id="FSTDEP007">FSTDEP007</a> | You are trying to set a HEAD route using `exposeHeadRoute` route flag when a sibling route is already set. | Remove `exposeHeadRoutes` or explicitly set `exposeHeadRoutes` to `false` | [#2700](https://github.com/fastify/fastify/pull/2700) |
| <a id="FSTDEP008">FSTDEP008</a> | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
| <a id="FSTDEP009">FSTDEP009</a> | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
| <a id="FSTDEP010">FSTDEP010</a> | Modifying the `reply.sent` property is deprecated. | Use the `reply.hijack()` method. | [#3140](https://github.com/fastify/fastify/pull/3140) |
| <a id="FSTDEP011">FSTDEP011</a> | Variadic listen method is deprecated. | Use `.listen(optionsObject)`. | [#3712](https://github.com/fastify/fastify/pull/3712) |
| <a id="FSTDEP012">FSTDEP012</a> | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) |
| <a id="FSTDEP013">FSTDEP013</a> | Direct return of "trailers" function is deprecated. | Use "callback" or "async-await" for return value. | [#4380](https://github.com/fastify/fastify/pull/4380) |
| <a id="FSTDEP014">FSTDEP014</a> | You are trying to set/access the default route. This property is deprecated. | Use `setNotFoundHandler` if you want to custom a 404 handler or the wildcard (`*`) to match all routes. | [#4480](https://github.com/fastify/fastify/pull/4480) |
| <a id="FSTDEP015">FSTDEP015</a> | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| <a id="FSTDEP016">FSTDEP016</a> | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| <a id="FSTDEP017">FSTDEP017</a> | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| <a id="FSTDEP018">FSTDEP018</a> | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
| <a id="FSTDEP019">FSTDEP019</a> | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) |
| <a id="FSTDEP020">FSTDEP020</a> | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) |
| <a id="FSTDEP021">FSTDEP021</a> | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) |
| <a id="FSTDEP022">FSTDEP022</a> | You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5` | [#5483](https://github.com/fastify/fastify/pull/0000) |
| <a id="FSTDEP022">FSTDEP022</a> | You are trying to access the deprecated router options on top option properties. | Use `options.routerOptions`. | [#5985](https://github.com/fastify/fastify/pull/5985)

35
backend/node_modules/fastify/eslint.config.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
'use strict'
const neostandard = require('neostandard')
module.exports = [
...neostandard({
ignores: [
'lib/config-validator.js',
'lib/error-serializer.js',
'test/same-shape.test.js',
'test/types/import.js'
],
ts: true
}),
{
rules: {
'comma-dangle': ['error', 'never'],
'max-len': ['error', {
code: 120,
tabWidth: 2,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
ignoreComments: true,
ignoreTrailingComments: true
}]
}
},
{
files: ['**/*.d.ts'],
rules: {
'max-len': 'off'
}
}
]

View File

@@ -0,0 +1,27 @@
'use strict'
const fastify = require('../../fastify')({
logger: false
})
const payload = JSON.stringify({ hello: 'world' })
fastify.get('/', function (req, reply) {
const stream = new ReadableStream({
start (controller) {
controller.enqueue(payload)
controller.close()
}
})
return new Response(stream, {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
console.log(`Server listening on ${address}`)
})

View File

@@ -10,8 +10,8 @@
* node examples/typescript-server.js
*/
import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify';
import { Server, IncomingMessage, ServerResponse } from 'http';
import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify'
import { Server, IncomingMessage, ServerResponse } from 'node:http'
// Create an http server. We pass the relevant typings for our http version used.
// By passing types we get correctly typed access to the underlying http objects in routes.
@@ -20,7 +20,7 @@ const server: FastifyInstance<
Server,
IncomingMessage,
ServerResponse
> = fastify({ logger: true });
> = fastify({ logger: true })
// Define interfaces for our request. We can create these automatically
// off our JSON Schema files (See TypeScript.md) but for the purpose of this
@@ -53,7 +53,7 @@ const opts: RouteShorthandOptions = {
}
}
}
};
}
// Add our route handler with correct types
server.post<{
@@ -62,18 +62,18 @@ server.post<{
Headers: PingHeaders;
Body: PingBody;
}>('/ping/:bar', opts, (request, reply) => {
console.log(request.query); // this is of type `PingQuerystring`
console.log(request.params); // this is of type `PingParams`
console.log(request.headers); // this is of type `PingHeaders`
console.log(request.body); // this is of type `PingBody`
reply.code(200).send({ pong: 'it worked!' });
});
console.log(request.query) // this is of type `PingQuerystring`
console.log(request.params) // this is of type `PingParams`
console.log(request.headers) // this is of type `PingHeaders`
console.log(request.body) // this is of type `PingBody`
reply.code(200).send({ pong: 'it worked!' })
})
// Start your server
server.listen({ port: 8080 }, (err, address) => {
if (err) {
console.error(err);
process.exit(1);
console.error(err)
process.exit(1)
}
console.log(`server listening on ${address}`)
});
console.log(`server listening on ${address}`)
})

View File

@@ -1,29 +1,37 @@
import * as http from 'http'
import * as http2 from 'http2'
import * as https from 'https'
import { Socket } from 'net'
import * as http from 'node:http'
import * as http2 from 'node:http2'
import * as https from 'node:https'
import { Socket } from 'node:net'
import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler'
import { BuildCompilerFromPool, ValidatorFactory } from '@fastify/ajv-compiler'
import { FastifyError } from '@fastify/error'
import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler'
import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
import { Config as FindMyWayConfig, ConstraintStrategy, HTTPVersion } from 'find-my-way'
import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request'
import { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser'
import { FastifyRequestContext, FastifyContextConfig, FastifyReplyContext } from './types/context'
import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, FastifyContentTypeParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction } from './types/content-type-parser'
import { FastifyContextConfig, FastifyReplyContext, FastifyRequestContext } from './types/context'
import { FastifyErrorCodes } from './types/errors'
import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler } from './types/hooks'
import { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance'
import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions, FastifyLogFn, LogLevel } from './types/logger'
import { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin'
import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, RequestPayload } from './types/hooks'
import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './types/instance'
import {
FastifyBaseLogger,
FastifyChildLoggerFactory,
FastifyLogFn,
FastifyLoggerInstance,
FastifyLoggerOptions,
LogLevel,
PinoLoggerOptions
} from './types/logger'
import { FastifyPlugin, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions } from './types/plugin'
import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register'
import { FastifyReply } from './types/reply'
import { FastifyRequest, RequestGenericInterface } from './types/request'
import { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route'
import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema'
import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory'
import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider'
import { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils'
import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
import { FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema'
import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/server-factory'
import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider'
import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils'
declare module '@fastify/error' {
interface FastifyError {
@@ -35,7 +43,7 @@ declare module '@fastify/error' {
type Fastify = typeof fastify
declare namespace fastify {
export const errorCodes: FastifyErrorCodes;
export const errorCodes: FastifyErrorCodes
export type FastifyHttp2SecureOptions<
Server extends http2.Http2SecureServer,
@@ -69,6 +77,7 @@ declare namespace fastify {
}
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
type FindMyWayConfigForServer<RawServer extends RawServerBase> = FindMyWayConfig<FindMyWayVersion<RawServer>>
export interface ConnectionError extends Error {
code: string,
@@ -81,6 +90,19 @@ declare namespace fastify {
type TrustProxyFunction = (address: string, hop: number) => boolean
export type FastifyRouterOptions<RawServer extends RawServerBase> = Omit<FindMyWayConfigForServer<RawServer>, 'defaultRoute' | 'onBadUrl' | 'querystringParser'> & {
defaultRoute?: (
req: RawRequestDefaultExpression<RawServer>,
res: RawReplyDefaultExpression<RawServer>
) => void,
onBadUrl?: (
path: string,
req: RawRequestDefaultExpression<RawServer>,
res: RawReplyDefaultExpression<RawServer>
) => void,
querystringParser?: (str: string) => { [key: string]: unknown }
}
/**
* Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2
*/
@@ -98,11 +120,12 @@ declare namespace fastify {
pluginTimeout?: number,
bodyLimit?: number,
maxParamLength?: number,
disableRequestLogging?: boolean,
disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
exposeHeadRoutes?: boolean,
onProtoPoisoning?: ProtoAction,
onConstructorPoisoning?: ConstructorAction,
logger?: boolean | FastifyLoggerOptions<RawServer> & PinoLoggerOptions | Logger,
logger?: boolean | FastifyLoggerOptions<RawServer> & PinoLoggerOptions,
loggerInstance?: Logger
serializerOpts?: FJSOptions | Record<string, unknown>,
serverFactory?: FastifyServerFactory<RawServer>,
caseSensitive?: boolean,
@@ -110,22 +133,9 @@ declare namespace fastify {
requestIdHeader?: string | false,
requestIdLogLabel?: string;
useSemicolonDelimiter?: boolean,
jsonShorthand?: boolean;
genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
querystringParser?: (str: string) => { [key: string]: unknown },
/**
* @deprecated Prefer using the `constraints.version` property
*/
versioning?: {
storage(): {
get(version: string): string | null,
set(version: string, store: Function): void
del(version: string): void,
empty(): void
},
deriveVersion<Context>(req: Object, ctx?: Context): string // not a fan of using Object here. Also what is Context? Can either of these be better defined?
},
constraints?: {
[name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>,
},
@@ -141,14 +151,11 @@ declare namespace fastify {
};
};
return503OnClosing?: boolean,
ajv?: {
customOptions?: AjvOptions,
plugins?: (Function | [Function, unknown])[]
},
ajv?: Parameters<BuildCompilerFromPool>[1],
frameworkErrors?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, SchemaCompiler extends FastifySchema = FastifySchema>(
error: FastifyError,
req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, FastifySchema, TypeProvider>,
res: FastifyReply<RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider>
res: FastifyReply<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, FastifyContextConfig, SchemaCompiler, TypeProvider>
) => void,
rewriteUrl?: (
// The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters
@@ -161,12 +168,15 @@ declare namespace fastify {
* listener to error events emitted by client connections
*/
clientErrorHandler?: (error: ConnectionError, socket: Socket) => void,
childLoggerFactory?: FastifyChildLoggerFactory,
allowErrorHandlerOverride?: boolean
routerOptions?: FastifyRouterOptions<RawServer>,
}
/**
* @deprecated use {@link FastifySchemaValidationError}
*/
export type ValidationResult = FastifySchemaValidationError;
export type ValidationResult = FastifySchemaValidationError
/* Export additional types */
export type {
@@ -181,12 +191,12 @@ declare namespace fastify {
FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register'
FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser'
FastifyError, // '@fastify/error'
FastifySchema, FastifySchemaCompiler, // './types/schema'
FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, // './types/schema'
HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils'
DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks'
FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory'
FastifyTypeProvider, FastifyTypeProviderDefault, // './types/type-provider'
FastifyErrorCodes, // './types/errors'
FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike, // './types/type-provider'
FastifyErrorCodes // './types/errors'
}
// named export
// import { plugin } from 'plugin'
@@ -210,32 +220,32 @@ declare function fastify<
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
Logger extends FastifyBaseLogger = FastifyBaseLogger,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
>(opts: fastify.FastifyHttp2SecureOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
> (opts: fastify.FastifyHttp2SecureOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
declare function fastify<
Server extends http2.Http2Server,
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
Logger extends FastifyBaseLogger = FastifyBaseLogger,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
>(opts: fastify.FastifyHttp2Options<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
> (opts: fastify.FastifyHttp2Options<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
declare function fastify<
Server extends https.Server,
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
Logger extends FastifyBaseLogger = FastifyBaseLogger,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
>(opts: fastify.FastifyHttpsOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
> (opts: fastify.FastifyHttpsOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
declare function fastify<
Server extends http.Server,
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
Logger extends FastifyBaseLogger = FastifyBaseLogger,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
>(opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
> (opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
// CJS export
// const fastify = require('fastify')

View File

@@ -1,9 +1,10 @@
'use strict'
const VERSION = '4.29.1'
const VERSION = '5.7.1'
const Avvio = require('avvio')
const http = require('node:http')
const diagnostics = require('node:diagnostics_channel')
let lightMyRequest
const {
@@ -11,6 +12,7 @@ const {
kChildren,
kServerBindings,
kBodyLimit,
kSupportedHTTPMethods,
kRoutePrefix,
kLogLevel,
kLogSerializers,
@@ -29,163 +31,70 @@ const {
kErrorHandler,
kKeepAliveConnections,
kChildLoggerFactory,
kGenReqId
kGenReqId,
kErrorHandlerAlreadySet
} = require('./lib/symbols.js')
const { createServer, compileValidateHTTPVersion } = require('./lib/server')
const { createServer } = require('./lib/server')
const Reply = require('./lib/reply')
const Request = require('./lib/request')
const Context = require('./lib/context.js')
const { supportedMethods } = require('./lib/httpMethods')
const decorator = require('./lib/decorate')
const ContentTypeParser = require('./lib/contentTypeParser')
const ContentTypeParser = require('./lib/content-type-parser.js')
const SchemaController = require('./lib/schema-controller')
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger')
const pluginUtils = require('./lib/pluginUtils')
const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory')
const { buildRouting, validateBodyLimitOption } = require('./lib/route')
const build404 = require('./lib/fourOhFour')
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
const override = require('./lib/pluginOverride')
const { FSTDEP009 } = require('./lib/warnings')
const noopSet = require('./lib/noop-set')
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
const pluginUtils = require('./lib/plugin-utils.js')
const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
const build404 = require('./lib/four-oh-four')
const getSecuredInitialConfig = require('./lib/initial-config-validation.js')
const override = require('./lib/plugin-override')
const {
appendStackTrace,
AVVIO_ERRORS_MAP,
...errorCodes
} = require('./lib/errors')
const PonyPromise = require('./lib/promise')
const { defaultInitOptions } = getSecuredInitialConfig
const {
FST_ERR_ASYNC_CONSTRAINT,
FST_ERR_BAD_URL,
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE,
FST_ERR_OPTIONS_NOT_OBJ,
FST_ERR_QSP_NOT_FN,
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ,
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR,
FST_ERR_VERSION_CONSTRAINT_NOT_STR,
FST_ERR_INSTANCE_ALREADY_LISTENING,
FST_ERR_REOPENED_CLOSE_SERVER,
FST_ERR_ROUTE_REWRITE_NOT_STR,
FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN,
FST_ERR_ERROR_HANDLER_NOT_FN
FST_ERR_ERROR_HANDLER_NOT_FN,
FST_ERR_ERROR_HANDLER_ALREADY_SET,
FST_ERR_ROUTE_METHOD_INVALID
} = errorCodes
const { buildErrorHandler } = require('./lib/error-handler.js')
const { FSTWRN004 } = require('./lib/warnings.js')
function defaultBuildPrettyMeta (route) {
// return a shallow copy of route's sanitized context
const cleanKeys = {}
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
allowedProps.concat(supportedHooks).forEach(k => {
cleanKeys[k] = route.store[k]
})
return Object.assign({}, cleanKeys)
}
const initChannel = diagnostics.channel('fastify.initialization')
/**
* @param {import('./fastify.js').FastifyServerOptions} options
* @param {import('./fastify.js').FastifyServerOptions} serverOptions
*/
function fastify (options) {
// Options validations
options = options || {}
if (typeof options !== 'object') {
throw new FST_ERR_OPTIONS_NOT_OBJ()
}
if (options.querystringParser && typeof options.querystringParser !== 'function') {
throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser)
}
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
}
validateBodyLimitOption(options.bodyLimit)
const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader).toLowerCase()
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
const disableRequestLogging = options.disableRequestLogging || false
const ajvOptions = Object.assign({
customOptions: {},
plugins: []
}, options.ajv)
const frameworkErrors = options.frameworkErrors
// Ajv options
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
}
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
}
// Instance Fastify components
const { logger, hasLogger } = createLogger(options)
// Update the options with the fixed values
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
options.logger = logger
options.requestIdHeader = requestIdHeader
options.requestIdLogLabel = requestIdLogLabel
options.disableRequestLogging = disableRequestLogging
options.ajv = ajvOptions
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
const initialConfig = getSecuredInitialConfig(options)
// exposeHeadRoutes have its default set from the validator
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
let constraints = options.constraints
if (options.versioning) {
FSTDEP009()
constraints = {
...constraints,
version: {
name: 'version',
mustMatchWhenDerived: true,
storage: options.versioning.storage,
deriveConstraint: options.versioning.deriveVersion,
validate (value) {
if (typeof value !== 'string') {
throw new FST_ERR_VERSION_CONSTRAINT_NOT_STR()
}
}
}
}
}
function fastify (serverOptions) {
const {
options,
genReqId,
disableRequestLogging,
hasLogger,
initialConfig
} = processOptions(serverOptions, defaultRoute, onBadUrl)
// Default router
const router = buildRouting({
config: {
defaultRoute,
onBadUrl,
constraints,
ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes,
maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
caseSensitive: options.caseSensitive,
allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex,
buildPrettyMeta: defaultBuildPrettyMeta,
querystringParser: options.querystringParser,
useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter
}
})
const router = buildRouting(options.routerOptions)
// 404 router, used for handling encapsulated 404 handlers
const fourOhFour = build404(options)
@@ -193,22 +102,14 @@ function fastify (options) {
// HTTP server and its handler
const httpHandler = wrapRouting(router, options)
// we need to set this before calling createServer
options.http2SessionTimeout = initialConfig.http2SessionTimeout
const { server, listen } = createServer(options, httpHandler)
const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
let forceCloseConnections = options.forceCloseConnections
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
} else if (typeof forceCloseConnections !== 'boolean') {
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
}
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
const {
server,
listen,
forceCloseConnections,
serverHasCloseAllConnections,
serverHasCloseHttp2Sessions,
keepAliveConnections
} = createServer(options, httpHandler)
const setupResponseListeners = Reply.setupResponseListeners
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
@@ -222,13 +123,30 @@ function fastify (options) {
started: false,
ready: false,
booting: false,
readyPromise: null
aborted: false,
readyResolver: null
},
[kKeepAliveConnections]: keepAliveConnections,
[kSupportedHTTPMethods]: {
bodyless: new Set([
// Standard
'GET',
'HEAD',
'TRACE'
]),
bodywith: new Set([
// Standard
'DELETE',
'OPTIONS',
'PATCH',
'PUT',
'POST'
])
},
[kOptions]: options,
[kChildren]: [],
[kServerBindings]: [],
[kBodyLimit]: bodyLimit,
[kBodyLimit]: options.bodyLimit,
[kRoutePrefix]: '',
[kLogLevel]: '',
[kLogSerializers]: null,
@@ -236,10 +154,11 @@ function fastify (options) {
[kSchemaController]: schemaController,
[kSchemaErrorFormatter]: null,
[kErrorHandler]: buildErrorHandler(),
[kChildLoggerFactory]: defaultChildLoggerFactory,
[kErrorHandlerAlreadySet]: false,
[kChildLoggerFactory]: options.childLoggerFactory || defaultChildLoggerFactory,
[kReplySerializerDefault]: null,
[kContentTypeParser]: new ContentTypeParser(
bodyLimit,
options.bodyLimit,
(options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
(options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
),
@@ -252,8 +171,6 @@ function fastify (options) {
[kGenReqId]: genReqId,
// routing method
routing: httpHandler,
getDefaultRoute: router.getDefaultRoute.bind(router),
setDefaultRoute: router.setDefaultRoute.bind(router),
// routes shorthand methods
delete: function _delete (url, options, handler) {
return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
@@ -264,6 +181,9 @@ function fastify (options) {
head: function _head (url, options, handler) {
return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
},
trace: function _trace (url, options, handler) {
return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler })
},
patch: function _patch (url, options, handler) {
return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
},
@@ -277,7 +197,7 @@ function fastify (options) {
return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
},
all: function _all (url, options, handler) {
return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler })
return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })
},
// extended route
route: function _route (options) {
@@ -292,7 +212,7 @@ function fastify (options) {
return router.findRoute(options)
},
// expose logger instance
log: logger,
log: options.logger,
// type provider
withTypeProvider,
// hooks
@@ -341,6 +261,8 @@ function fastify (options) {
decorateRequest: decorator.decorateRequest,
hasRequestDecorator: decorator.existRequest,
hasReplyDecorator: decorator.existReply,
getDecorator: decorator.getInstanceDecorator,
addHttpMethod,
// fake http injection
inject,
// pretty print of the registered routes
@@ -408,6 +330,15 @@ function fastify (options) {
genReqId: {
configurable: true,
get () { return this[kGenReqId] }
},
supportedMethods: {
configurable: false,
get () {
return [
...this[kSupportedHTTPMethods].bodyless,
...this[kSupportedHTTPMethods].bodywith
]
}
}
})
@@ -451,7 +382,7 @@ function fastify (options) {
if (forceCloseConnections === 'idle') {
// Not needed in Node 19
instance.server.closeIdleConnections()
/* istanbul ignore next: Cannot test this without Node.js core support */
/* istanbul ignore next: Cannot test this without Node.js core support */
} else if (serverHasCloseAllConnections && forceCloseConnections) {
instance.server.closeAllConnections()
} else if (forceCloseConnections === true) {
@@ -466,6 +397,10 @@ function fastify (options) {
}
}
if (serverHasCloseHttp2Sessions) {
instance.server.closeHttp2Sessions()
}
// No new TCP connections are accepted.
// We must call close on the server even if we are not listening
// otherwise memory will be leaked.
@@ -499,26 +434,17 @@ function fastify (options) {
router.setup(options, {
avvio,
fourOhFour,
logger,
hasLogger,
setupResponseListeners,
throwIfAlreadyStarted,
validateHTTPVersion: compileValidateHTTPVersion(options),
keepAliveConnections
})
// Delay configuring clientError handler so that it can access fastify state.
server.on('clientError', options.clientErrorHandler.bind(fastify))
try {
const dc = require('node:diagnostics_channel')
const initChannel = dc.channel('fastify.initialization')
if (initChannel.hasSubscribers) {
initChannel.publish({ fastify })
}
} catch (e) {
// This only happens if `diagnostics_channel` isn't available, i.e. earlier
// versions of Node.js. In that event, we don't care, so ignore the error.
if (initChannel.hasSubscribers) {
initChannel.publish({ fastify })
}
// Older nodejs versions may not have asyncDispose
@@ -577,18 +503,15 @@ function fastify (options) {
}
function ready (cb) {
if (this[kState].readyPromise !== null) {
if (this[kState].readyResolver !== null) {
if (cb != null) {
this[kState].readyPromise.then(() => cb(null, fastify), cb)
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
return
}
return this[kState].readyPromise
return this[kState].readyResolver.promise
}
let resolveReady
let rejectReady
// run the hooks after returning the promise
process.nextTick(runHooks)
@@ -596,15 +519,12 @@ function fastify (options) {
// It will work as a barrier for all the .ready() calls (ensuring single hook execution)
// as well as a flow control mechanism to chain cbs and further
// promises
this[kState].readyPromise = new Promise(function (resolve, reject) {
resolveReady = resolve
rejectReady = reject
})
this[kState].readyResolver = PonyPromise.withResolvers()
if (!cb) {
return this[kState].readyPromise
return this[kState].readyResolver.promise
} else {
this[kState].readyPromise.then(() => cb(null, fastify), cb)
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
}
function runHooks () {
@@ -629,13 +549,13 @@ function fastify (options) {
: err
if (err) {
return rejectReady(err)
return fastify[kState].readyResolver.reject(err)
}
resolveReady(fastify)
fastify[kState].readyResolver.resolve(fastify)
fastify[kState].booting = false
fastify[kState].ready = true
fastify[kState].promise = null
fastify[kState].readyResolver = null
}
}
@@ -676,8 +596,12 @@ function fastify (options) {
this[kHooks].add(name, fn)
} else {
this.after((err, done) => {
_addHook.call(this, name, fn)
done(err)
try {
_addHook.call(this, name, fn)
done(err)
} catch (err) {
done(err)
}
})
}
return this
@@ -696,46 +620,6 @@ function fastify (options) {
return this
}
function defaultClientErrorHandler (err, socket) {
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
// https://nodejs.org/api/http.html#http_event_clienterror
if (err.code === 'ECONNRESET' || socket.destroyed) {
return
}
let body, errorCode, errorStatus, errorLabel
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
errorCode = '408'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
errorLabel = 'timeout'
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
errorCode = '431'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
errorLabel = 'header_overflow'
} else {
errorCode = '400'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
errorLabel = 'error'
}
// Most devs do not know what to do with this error.
// In the vast majority of cases, it's a network error and/or some
// config issue on the load balancer side.
this.log.trace({ err }, `client ${errorLabel}`)
// Copying standard node behavior
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
// If the socket is not writable, there is no reason to try to send data.
if (socket.writable) {
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
}
socket.destroy(err)
}
// If the router does not match any route, every request will land here
// req and res are Node.js core objects
function defaultRoute (req, res) {
@@ -750,23 +634,29 @@ function fastify (options) {
}
function onBadUrl (path, req, res) {
if (frameworkErrors) {
if (options.frameworkErrors) {
const id = getGenReqId(onBadUrlContext.server, req)
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
}
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
const body = JSON.stringify({
error: 'Bad Request',
code: 'FST_ERR_BAD_URL',
message: `'${path}' is not a valid url component`,
statusCode: 400
})
res.writeHead(400, {
'Content-Type': 'application/json',
'Content-Length': body.length
'Content-Length': Buffer.byteLength(body)
})
res.end(body)
}
@@ -775,18 +665,19 @@ function fastify (options) {
if (isAsync === false) return undefined
return function onAsyncConstraintError (err) {
if (err) {
if (frameworkErrors) {
if (options.frameworkErrors) {
const id = getGenReqId(onBadUrlContext.server, req)
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
}
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
res.writeHead(500, {
@@ -827,7 +718,10 @@ function fastify (options) {
function setSchemaController (schemaControllerOpts) {
throwIfAlreadyStarted('Cannot call "setSchemaController"!')
const old = this[kSchemaController]
const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))
const schemaController = SchemaController.buildSchemaController(
old,
Object.assign({}, old.opts, schemaControllerOpts)
)
this[kSchemaController] = schemaController
this.getSchema = schemaController.getSchema.bind(schemaController)
this.getSchemas = schemaController.getSchemas.bind(schemaController)
@@ -849,6 +743,13 @@ function fastify (options) {
throw new FST_ERR_ERROR_HANDLER_NOT_FN()
}
if (!options.allowErrorHandlerOverride && this[kErrorHandlerAlreadySet]) {
throw new FST_ERR_ERROR_HANDLER_ALREADY_SET()
} else if (this[kErrorHandlerAlreadySet]) {
FSTWRN004("To disable this behavior, set 'allowErrorHandlerOverride' to false or ignore this message. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride")
}
this[kErrorHandlerAlreadySet] = true
this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this))
return this
}
@@ -862,7 +763,9 @@ function fastify (options) {
function printRoutes (opts = {}) {
// includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
opts.includeMeta = opts.includeHooks
? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks
: opts.includeMeta
return router.printRoutes(opts)
}
@@ -891,6 +794,168 @@ function fastify (options) {
this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func)
return this
}
function addHttpMethod (method, { hasBody = false } = {}) {
if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) {
throw new FST_ERR_ROUTE_METHOD_INVALID()
}
if (hasBody === true) {
this[kSupportedHTTPMethods].bodywith.add(method)
this[kSupportedHTTPMethods].bodyless.delete(method)
} else {
this[kSupportedHTTPMethods].bodywith.delete(method)
this[kSupportedHTTPMethods].bodyless.add(method)
}
const _method = method.toLowerCase()
if (!this.hasDecorator(_method)) {
this.decorate(_method, function (url, options, handler) {
return router.prepareRoute.call(this, { method, url, options, handler })
})
}
return this
}
}
function processOptions (options, defaultRoute, onBadUrl) {
// Options validations
if (options && typeof options !== 'object') {
throw new FST_ERR_OPTIONS_NOT_OBJ()
} else {
// Shallow copy options object to prevent mutations outside of this function
options = Object.assign({}, options)
}
if (
(options.querystringParser && typeof options.querystringParser !== 'function') ||
(
options.routerOptions?.querystringParser &&
typeof options.routerOptions.querystringParser !== 'function'
)
) {
throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser))
}
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
}
validateBodyLimitOption(options.bodyLimit)
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
const disableRequestLogging = options.disableRequestLogging || false
const ajvOptions = Object.assign({
customOptions: {},
plugins: []
}, options.ajv)
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
}
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
}
const { logger, hasLogger } = createLogger(options)
// Update the options with the fixed values
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
options.logger = logger
options.requestIdHeader = requestIdHeader
options.requestIdLogLabel = requestIdLogLabel
options.disableRequestLogging = disableRequestLogging
options.ajv = ajvOptions
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
const initialConfig = getSecuredInitialConfig(options)
// exposeHeadRoutes have its default set from the validator
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
// we need to set this before calling createServer
options.http2SessionTimeout = initialConfig.http2SessionTimeout
options.routerOptions = buildRouterOptions(options, {
defaultRoute,
onBadUrl,
ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
maxParamLength: defaultInitOptions.maxParamLength,
allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex,
buildPrettyMeta: defaultBuildPrettyMeta,
useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter
})
return {
options,
genReqId,
disableRequestLogging,
hasLogger,
initialConfig
}
}
function defaultBuildPrettyMeta (route) {
// return a shallow copy of route's sanitized context
const cleanKeys = {}
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
allowedProps.concat(supportedHooks).forEach(k => {
cleanKeys[k] = route.store[k]
})
return Object.assign({}, cleanKeys)
}
function defaultClientErrorHandler (err, socket) {
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
// https://nodejs.org/api/http.html#http_event_clienterror
if (err.code === 'ECONNRESET' || socket.destroyed) {
return
}
let body, errorCode, errorStatus, errorLabel
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
errorCode = '408'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
errorLabel = 'timeout'
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
errorCode = '431'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
errorLabel = 'header_overflow'
} else {
errorCode = '400'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
errorLabel = 'error'
}
// Most devs do not know what to do with this error.
// In the vast majority of cases, it's a network error and/or some
// config issue on the load balancer side.
this.log.trace({ err }, `client ${errorLabel}`)
// Copying standard node behavior
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
// If the socket is not writable, there is no reason to try to send data.
if (socket.writable) {
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
}
socket.destroy(err)
}
function validateSchemaErrorFormatter (schemaErrorFormatter) {
@@ -903,7 +968,7 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) {
/**
* These export configurations enable JS and TS developers
* to consumer fastify in whatever way best suits their needs.
* to consume fastify in whatever way best suits their needs.
* Some examples of supported import syntax includes:
* - `const fastify = require('fastify')`
* - `const { fastify } = require('fastify')`

View File

@@ -1,9 +1,9 @@
// This file is autogenerated by build/build-validation.js, do not edit
/* istanbul ignore file */
/* c8 ignore start */
"use strict";
module.exports = validate10;
module.exports.default = validate10;
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
const func2 = Object.prototype.hasOwnProperty;
const pattern0 = new RegExp("idle", "u");
@@ -42,9 +42,6 @@ data.ignoreDuplicateSlashes = false;
if(data.disableRequestLogging === undefined){
data.disableRequestLogging = false;
}
if(data.jsonShorthand === undefined){
data.jsonShorthand = true;
}
if(data.maxParamLength === undefined){
data.maxParamLength = 100;
}
@@ -58,7 +55,7 @@ if(data.pluginTimeout === undefined){
data.pluginTimeout = 10000;
}
if(data.requestIdHeader === undefined){
data.requestIdHeader = "request-id";
data.requestIdHeader = false;
}
if(data.requestIdLogLabel === undefined){
data.requestIdLogLabel = "reqId";
@@ -70,7 +67,7 @@ if(data.exposeHeadRoutes === undefined){
data.exposeHeadRoutes = true;
}
if(data.useSemicolonDelimiter === undefined){
data.useSemicolonDelimiter = true;
data.useSemicolonDelimiter = false;
}
const _errs1 = errors;
for(const key0 in data){
@@ -688,163 +685,122 @@ data["ignoreDuplicateSlashes"] = coerced14;
}
var valid0 = _errs43 === errors;
if(valid0){
let data13 = data.disableRequestLogging;
let data13 = data.maxParamLength;
const _errs45 = errors;
if(typeof data13 !== "boolean"){
if(!(((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13))) && (isFinite(data13)))){
let dataType15 = typeof data13;
let coerced15 = undefined;
if(!(coerced15 !== undefined)){
if(data13 === "false" || data13 === 0 || data13 === null){
coerced15 = false;
}
else if(data13 === "true" || data13 === 1){
coerced15 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/disableRequestLogging",schemaPath:"#/properties/disableRequestLogging/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced15 !== undefined){
data13 = coerced15;
if(data !== undefined){
data["disableRequestLogging"] = coerced15;
}
}
}
var valid0 = _errs45 === errors;
if(valid0){
let data14 = data.jsonShorthand;
const _errs47 = errors;
if(typeof data14 !== "boolean"){
let coerced16 = undefined;
if(!(coerced16 !== undefined)){
if(data14 === "false" || data14 === 0 || data14 === null){
coerced16 = false;
}
else if(data14 === "true" || data14 === 1){
coerced16 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/jsonShorthand",schemaPath:"#/properties/jsonShorthand/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced16 !== undefined){
data14 = coerced16;
if(data !== undefined){
data["jsonShorthand"] = coerced16;
}
}
}
var valid0 = _errs47 === errors;
if(valid0){
let data15 = data.maxParamLength;
const _errs49 = errors;
if(!(((typeof data15 == "number") && (!(data15 % 1) && !isNaN(data15))) && (isFinite(data15)))){
let dataType17 = typeof data15;
let coerced17 = undefined;
if(!(coerced17 !== undefined)){
if(dataType17 === "boolean" || data15 === null
|| (dataType17 === "string" && data15 && data15 == +data15 && !(data15 % 1))){
coerced17 = +data15;
if(dataType15 === "boolean" || data13 === null
|| (dataType15 === "string" && data13 && data13 == +data13 && !(data13 % 1))){
coerced15 = +data13;
}
else {
validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced17 !== undefined){
data15 = coerced17;
if(coerced15 !== undefined){
data13 = coerced15;
if(data !== undefined){
data["maxParamLength"] = coerced17;
data["maxParamLength"] = coerced15;
}
}
}
var valid0 = _errs49 === errors;
var valid0 = _errs45 === errors;
if(valid0){
let data16 = data.onProtoPoisoning;
const _errs51 = errors;
if(typeof data16 !== "string"){
let dataType18 = typeof data16;
let coerced18 = undefined;
if(!(coerced18 !== undefined)){
if(dataType18 == "number" || dataType18 == "boolean"){
coerced18 = "" + data16;
let data14 = data.onProtoPoisoning;
const _errs47 = errors;
if(typeof data14 !== "string"){
let dataType16 = typeof data14;
let coerced16 = undefined;
if(!(coerced16 !== undefined)){
if(dataType16 == "number" || dataType16 == "boolean"){
coerced16 = "" + data14;
}
else if(data16 === null){
coerced18 = "";
else if(data14 === null){
coerced16 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced18 !== undefined){
data16 = coerced18;
if(coerced16 !== undefined){
data14 = coerced16;
if(data !== undefined){
data["onProtoPoisoning"] = coerced18;
data["onProtoPoisoning"] = coerced16;
}
}
}
var valid0 = _errs51 === errors;
var valid0 = _errs47 === errors;
if(valid0){
let data17 = data.onConstructorPoisoning;
const _errs53 = errors;
if(typeof data17 !== "string"){
let dataType19 = typeof data17;
let coerced19 = undefined;
if(!(coerced19 !== undefined)){
if(dataType19 == "number" || dataType19 == "boolean"){
coerced19 = "" + data17;
let data15 = data.onConstructorPoisoning;
const _errs49 = errors;
if(typeof data15 !== "string"){
let dataType17 = typeof data15;
let coerced17 = undefined;
if(!(coerced17 !== undefined)){
if(dataType17 == "number" || dataType17 == "boolean"){
coerced17 = "" + data15;
}
else if(data17 === null){
coerced19 = "";
else if(data15 === null){
coerced17 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced19 !== undefined){
data17 = coerced19;
if(coerced17 !== undefined){
data15 = coerced17;
if(data !== undefined){
data["onConstructorPoisoning"] = coerced19;
data["onConstructorPoisoning"] = coerced17;
}
}
}
var valid0 = _errs53 === errors;
var valid0 = _errs49 === errors;
if(valid0){
let data18 = data.pluginTimeout;
const _errs55 = errors;
if(!(((typeof data18 == "number") && (!(data18 % 1) && !isNaN(data18))) && (isFinite(data18)))){
let dataType20 = typeof data18;
let coerced20 = undefined;
if(!(coerced20 !== undefined)){
if(dataType20 === "boolean" || data18 === null
|| (dataType20 === "string" && data18 && data18 == +data18 && !(data18 % 1))){
coerced20 = +data18;
let data16 = data.pluginTimeout;
const _errs51 = errors;
if(!(((typeof data16 == "number") && (!(data16 % 1) && !isNaN(data16))) && (isFinite(data16)))){
let dataType18 = typeof data16;
let coerced18 = undefined;
if(!(coerced18 !== undefined)){
if(dataType18 === "boolean" || data16 === null
|| (dataType18 === "string" && data16 && data16 == +data16 && !(data16 % 1))){
coerced18 = +data16;
}
else {
validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced20 !== undefined){
data18 = coerced20;
if(coerced18 !== undefined){
data16 = coerced18;
if(data !== undefined){
data["pluginTimeout"] = coerced20;
data["pluginTimeout"] = coerced18;
}
}
}
var valid0 = _errs55 === errors;
var valid0 = _errs51 === errors;
if(valid0){
let data19 = data.requestIdHeader;
const _errs57 = errors;
const _errs58 = errors;
let data17 = data.requestIdHeader;
const _errs53 = errors;
const _errs54 = errors;
let valid6 = false;
const _errs59 = errors;
if(!(data19 === false)){
const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema11.properties.requestIdHeader.anyOf[0].enum},message:"must be equal to one of the allowed values"};
const _errs55 = errors;
if(typeof data17 !== "boolean"){
let coerced19 = undefined;
if(!(coerced19 !== undefined)){
if(data17 === "false" || data17 === 0 || data17 === null){
coerced19 = false;
}
else if(data17 === "true" || data17 === 1){
coerced19 = true;
}
else {
const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err12];
}
@@ -853,19 +809,27 @@ vErrors.push(err12);
}
errors++;
}
var _valid3 = _errs59 === errors;
}
if(coerced19 !== undefined){
data17 = coerced19;
if(data !== undefined){
data["requestIdHeader"] = coerced19;
}
}
}
var _valid3 = _errs55 === errors;
valid6 = valid6 || _valid3;
if(!valid6){
const _errs60 = errors;
if(typeof data19 !== "string"){
let dataType21 = typeof data19;
let coerced21 = undefined;
if(!(coerced21 !== undefined)){
if(dataType21 == "number" || dataType21 == "boolean"){
coerced21 = "" + data19;
const _errs57 = errors;
if(typeof data17 !== "string"){
let dataType20 = typeof data17;
let coerced20 = undefined;
if(!(coerced20 !== undefined)){
if(dataType20 == "number" || dataType20 == "boolean"){
coerced20 = "" + data17;
}
else if(data19 === null){
coerced21 = "";
else if(data17 === null){
coerced20 = "";
}
else {
const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};
@@ -878,14 +842,14 @@ vErrors.push(err13);
errors++;
}
}
if(coerced21 !== undefined){
data19 = coerced21;
if(coerced20 !== undefined){
data17 = coerced20;
if(data !== undefined){
data["requestIdHeader"] = coerced21;
data["requestIdHeader"] = coerced20;
}
}
}
var _valid3 = _errs60 === errors;
var _valid3 = _errs57 === errors;
valid6 = valid6 || _valid3;
}
if(!valid6){
@@ -901,95 +865,140 @@ validate10.errors = vErrors;
return false;
}
else {
errors = _errs58;
errors = _errs54;
if(vErrors !== null){
if(_errs58){
vErrors.length = _errs58;
if(_errs54){
vErrors.length = _errs54;
}
else {
vErrors = null;
}
}
}
var valid0 = _errs57 === errors;
var valid0 = _errs53 === errors;
if(valid0){
let data20 = data.requestIdLogLabel;
const _errs62 = errors;
if(typeof data20 !== "string"){
let dataType22 = typeof data20;
let coerced22 = undefined;
if(!(coerced22 !== undefined)){
if(dataType22 == "number" || dataType22 == "boolean"){
coerced22 = "" + data20;
let data18 = data.requestIdLogLabel;
const _errs59 = errors;
if(typeof data18 !== "string"){
let dataType21 = typeof data18;
let coerced21 = undefined;
if(!(coerced21 !== undefined)){
if(dataType21 == "number" || dataType21 == "boolean"){
coerced21 = "" + data18;
}
else if(data20 === null){
coerced22 = "";
else if(data18 === null){
coerced21 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced22 !== undefined){
data20 = coerced22;
if(coerced21 !== undefined){
data18 = coerced21;
if(data !== undefined){
data["requestIdLogLabel"] = coerced22;
data["requestIdLogLabel"] = coerced21;
}
}
}
var valid0 = _errs62 === errors;
var valid0 = _errs59 === errors;
if(valid0){
let data21 = data.http2SessionTimeout;
const _errs64 = errors;
if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){
let dataType23 = typeof data21;
let coerced23 = undefined;
if(!(coerced23 !== undefined)){
if(dataType23 === "boolean" || data21 === null
|| (dataType23 === "string" && data21 && data21 == +data21 && !(data21 % 1))){
coerced23 = +data21;
let data19 = data.http2SessionTimeout;
const _errs61 = errors;
if(!(((typeof data19 == "number") && (!(data19 % 1) && !isNaN(data19))) && (isFinite(data19)))){
let dataType22 = typeof data19;
let coerced22 = undefined;
if(!(coerced22 !== undefined)){
if(dataType22 === "boolean" || data19 === null
|| (dataType22 === "string" && data19 && data19 == +data19 && !(data19 % 1))){
coerced22 = +data19;
}
else {
validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced23 !== undefined){
data21 = coerced23;
if(coerced22 !== undefined){
data19 = coerced22;
if(data !== undefined){
data["http2SessionTimeout"] = coerced23;
data["http2SessionTimeout"] = coerced22;
}
}
}
var valid0 = _errs64 === errors;
var valid0 = _errs61 === errors;
if(valid0){
let data22 = data.exposeHeadRoutes;
const _errs66 = errors;
if(typeof data22 !== "boolean"){
let coerced24 = undefined;
if(!(coerced24 !== undefined)){
if(data22 === "false" || data22 === 0 || data22 === null){
coerced24 = false;
let data20 = data.exposeHeadRoutes;
const _errs63 = errors;
if(typeof data20 !== "boolean"){
let coerced23 = undefined;
if(!(coerced23 !== undefined)){
if(data20 === "false" || data20 === 0 || data20 === null){
coerced23 = false;
}
else if(data22 === "true" || data22 === 1){
coerced24 = true;
else if(data20 === "true" || data20 === 1){
coerced23 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced24 !== undefined){
data22 = coerced24;
if(coerced23 !== undefined){
data20 = coerced23;
if(data !== undefined){
data["exposeHeadRoutes"] = coerced24;
data["exposeHeadRoutes"] = coerced23;
}
}
}
var valid0 = _errs66 === errors;
var valid0 = _errs63 === errors;
if(valid0){
let data23 = data.useSemicolonDelimiter;
const _errs68 = errors;
let data21 = data.useSemicolonDelimiter;
const _errs65 = errors;
if(typeof data21 !== "boolean"){
let coerced24 = undefined;
if(!(coerced24 !== undefined)){
if(data21 === "false" || data21 === 0 || data21 === null){
coerced24 = false;
}
else if(data21 === "true" || data21 === 1){
coerced24 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced24 !== undefined){
data21 = coerced24;
if(data !== undefined){
data["useSemicolonDelimiter"] = coerced24;
}
}
}
var valid0 = _errs65 === errors;
if(valid0){
if(data.routerOptions !== undefined){
let data22 = data.routerOptions;
const _errs67 = errors;
if(errors === _errs67){
if(data22 && typeof data22 == "object" && !Array.isArray(data22)){
if(data22.ignoreTrailingSlash === undefined){
data22.ignoreTrailingSlash = false;
}
if(data22.ignoreDuplicateSlashes === undefined){
data22.ignoreDuplicateSlashes = false;
}
if(data22.maxParamLength === undefined){
data22.maxParamLength = 100;
}
if(data22.allowUnsafeRegex === undefined){
data22.allowUnsafeRegex = false;
}
if(data22.useSemicolonDelimiter === undefined){
data22.useSemicolonDelimiter = false;
}
let data23 = data22.ignoreTrailingSlash;
const _errs70 = errors;
if(typeof data23 !== "boolean"){
let coerced25 = undefined;
if(!(coerced25 !== undefined)){
@@ -1000,78 +1009,170 @@ else if(data23 === "true" || data23 === 1){
coerced25 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced25 !== undefined){
data23 = coerced25;
if(data !== undefined){
data["useSemicolonDelimiter"] = coerced25;
if(data22 !== undefined){
data22["ignoreTrailingSlash"] = coerced25;
}
}
}
var valid0 = _errs68 === errors;
if(valid0){
if(data.versioning !== undefined){
let data24 = data.versioning;
const _errs70 = errors;
if(errors === _errs70){
if(data24 && typeof data24 == "object" && !Array.isArray(data24)){
let missing1;
if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){
validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
var valid7 = _errs70 === errors;
if(valid7){
let data24 = data22.ignoreDuplicateSlashes;
const _errs72 = errors;
if(typeof data24 !== "boolean"){
let coerced26 = undefined;
if(!(coerced26 !== undefined)){
if(data24 === "false" || data24 === 0 || data24 === null){
coerced26 = false;
}
else if(data24 === "true" || data24 === 1){
coerced26 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced26 !== undefined){
data24 = coerced26;
if(data22 !== undefined){
data22["ignoreDuplicateSlashes"] = coerced26;
}
}
}
var valid7 = _errs72 === errors;
if(valid7){
let data25 = data22.maxParamLength;
const _errs74 = errors;
if(!(((typeof data25 == "number") && (!(data25 % 1) && !isNaN(data25))) && (isFinite(data25)))){
let dataType27 = typeof data25;
let coerced27 = undefined;
if(!(coerced27 !== undefined)){
if(dataType27 === "boolean" || data25 === null
|| (dataType27 === "string" && data25 && data25 == +data25 && !(data25 % 1))){
coerced27 = +data25;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced27 !== undefined){
data25 = coerced27;
if(data22 !== undefined){
data22["maxParamLength"] = coerced27;
}
}
}
var valid7 = _errs74 === errors;
if(valid7){
let data26 = data22.allowUnsafeRegex;
const _errs76 = errors;
if(typeof data26 !== "boolean"){
let coerced28 = undefined;
if(!(coerced28 !== undefined)){
if(data26 === "false" || data26 === 0 || data26 === null){
coerced28 = false;
}
else if(data26 === "true" || data26 === 1){
coerced28 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced28 !== undefined){
data26 = coerced28;
if(data22 !== undefined){
data22["allowUnsafeRegex"] = coerced28;
}
}
}
var valid7 = _errs76 === errors;
if(valid7){
let data27 = data22.useSemicolonDelimiter;
const _errs78 = errors;
if(typeof data27 !== "boolean"){
let coerced29 = undefined;
if(!(coerced29 !== undefined)){
if(data27 === "false" || data27 === 0 || data27 === null){
coerced29 = false;
}
else if(data27 === "true" || data27 === 1){
coerced29 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced29 !== undefined){
data27 = coerced29;
if(data22 !== undefined){
data22["useSemicolonDelimiter"] = coerced29;
}
}
}
var valid7 = _errs78 === errors;
}
}
}
}
}
else {
validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/type",keyword:"type",params:{type: "object"},message:"must be object"}];
validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/properties/routerOptions/type",keyword:"type",params:{type: "object"},message:"must be object"}];
return false;
}
}
var valid0 = _errs70 === errors;
var valid0 = _errs67 === errors;
}
else {
var valid0 = true;
}
if(valid0){
if(data.constraints !== undefined){
let data25 = data.constraints;
const _errs73 = errors;
if(errors === _errs73){
if(data25 && typeof data25 == "object" && !Array.isArray(data25)){
for(const key2 in data25){
let data26 = data25[key2];
const _errs76 = errors;
if(errors === _errs76){
if(data26 && typeof data26 == "object" && !Array.isArray(data26)){
let missing2;
if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}];
let data28 = data.constraints;
const _errs80 = errors;
if(errors === _errs80){
if(data28 && typeof data28 == "object" && !Array.isArray(data28)){
for(const key2 in data28){
let data29 = data28[key2];
const _errs83 = errors;
if(errors === _errs83){
if(data29 && typeof data29 == "object" && !Array.isArray(data29)){
let missing1;
if(((((data29.name === undefined) && (missing1 = "name")) || ((data29.storage === undefined) && (missing1 = "storage"))) || ((data29.validate === undefined) && (missing1 = "validate"))) || ((data29.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
return false;
}
else {
if(data26.name !== undefined){
let data27 = data26.name;
if(typeof data27 !== "string"){
let dataType26 = typeof data27;
let coerced26 = undefined;
if(!(coerced26 !== undefined)){
if(dataType26 == "number" || dataType26 == "boolean"){
coerced26 = "" + data27;
if(data29.name !== undefined){
let data30 = data29.name;
if(typeof data30 !== "string"){
let dataType30 = typeof data30;
let coerced30 = undefined;
if(!(coerced30 !== undefined)){
if(dataType30 == "number" || dataType30 == "boolean"){
coerced30 = "" + data30;
}
else if(data27 === null){
coerced26 = "";
else if(data30 === null){
coerced30 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced26 !== undefined){
data27 = coerced26;
if(data26 !== undefined){
data26["name"] = coerced26;
if(coerced30 !== undefined){
data30 = coerced30;
if(data29 !== undefined){
data29["name"] = coerced30;
}
}
}
@@ -1083,8 +1184,8 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/
return false;
}
}
var valid7 = _errs76 === errors;
if(!valid7){
var valid8 = _errs83 === errors;
if(!valid8){
break;
}
}
@@ -1094,7 +1195,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro
return false;
}
}
var valid0 = _errs73 === errors;
var valid0 = _errs80 === errors;
}
else {
var valid0 = true;
@@ -1123,8 +1224,6 @@ var valid0 = true;
}
}
}
}
}
else {
validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];
return false;
@@ -1135,4 +1234,5 @@ return errors === 0;
}
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":true}
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true,"routerOptions":{"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"allowUnsafeRegex":false,"useSemicolonDelimiter":false}}
/* c8 ignore stop */

View File

@@ -2,8 +2,7 @@
const { AsyncResource } = require('node:async_hooks')
const { FifoMap: Fifo } = require('toad-cache')
const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse')
const secureJson = require('secure-json-parse')
const { parse: secureJsonParse } = require('secure-json-parse')
const {
kDefaultJsonParse,
kContentTypeParser,
@@ -25,8 +24,10 @@ const {
FST_ERR_CTP_INVALID_MEDIA_TYPE,
FST_ERR_CTP_INVALID_CONTENT_LENGTH,
FST_ERR_CTP_EMPTY_JSON_BODY,
FST_ERR_CTP_INSTANCE_ALREADY_STARTED
FST_ERR_CTP_INSTANCE_ALREADY_STARTED,
FST_ERR_CTP_INVALID_JSON_BODY
} = require('./errors')
const { FSTSEC001 } = require('./warnings')
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
@@ -34,7 +35,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
this.customParsers = new Map()
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')]
this.parserList = ['application/json', 'text/plain']
this.parserRegExpList = []
this.cache = new Fifo(100)
}
@@ -42,9 +43,16 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
const contentTypeIsString = typeof contentType === 'string'
if (!contentTypeIsString && !(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
if (contentTypeIsString && contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
if (typeof parserFn !== 'function') throw new FST_ERR_CTP_INVALID_HANDLER()
if (contentTypeIsString) {
contentType = contentType.trim().toLowerCase()
if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
} else if (!(contentType instanceof RegExp)) {
throw new FST_ERR_CTP_INVALID_TYPE()
}
if (typeof parserFn !== 'function') {
throw new FST_ERR_CTP_INVALID_HANDLER()
}
if (this.existingParser(contentType)) {
throw new FST_ERR_CTP_ALREADY_PRESENT(contentType)
@@ -63,21 +71,29 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
parserFn
)
if (contentTypeIsString && contentType === '*') {
if (contentType === '*') {
this.customParsers.set('', parser)
} else {
if (contentTypeIsString) {
this.parserList.unshift(new ParserListItem(contentType))
this.parserList.unshift(contentType)
this.customParsers.set(contentType, parser)
} else {
contentType.isEssence = contentType.source.indexOf(';') === -1
validateRegExp(contentType)
this.parserRegExpList.unshift(contentType)
this.customParsers.set(contentType.toString(), parser)
}
this.customParsers.set(contentType.toString(), parser)
}
}
ContentTypeParser.prototype.hasParser = function (contentType) {
return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
if (typeof contentType === 'string') {
contentType = contentType.trim().toLowerCase()
} else {
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
contentType = contentType.toString()
}
return this.customParsers.has(contentType)
}
ContentTypeParser.prototype.existingParser = function (contentType) {
@@ -92,38 +108,33 @@ ContentTypeParser.prototype.existingParser = function (contentType) {
}
ContentTypeParser.prototype.getParser = function (contentType) {
if (this.hasParser(contentType)) {
return this.customParsers.get(contentType)
}
const parser = this.cache.get(contentType)
let parser = this.customParsers.get(contentType)
if (parser !== undefined) return parser
parser = this.cache.get(contentType)
if (parser !== undefined) return parser
const parsed = safeParseContentType(contentType)
// dummyContentType always the same object
// we can use === for the comparison and return early
if (parsed === defaultContentType) {
return this.customParsers.get('')
}
// eslint-disable-next-line no-var
for (var i = 0; i !== this.parserList.length; ++i) {
const caseInsensitiveContentType = contentType.toLowerCase()
for (let i = 0; i !== this.parserList.length; ++i) {
const parserListItem = this.parserList[i]
if (compareContentType(parsed, parserListItem)) {
const parser = this.customParsers.get(parserListItem.name)
// we set request content-type in cache to reduce parsing of MIME type
if (
caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem &&
(
caseInsensitiveContentType.length === parserListItem.length ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */
)
) {
parser = this.customParsers.get(parserListItem)
this.cache.set(contentType, parser)
return parser
}
}
// eslint-disable-next-line no-var
for (var j = 0; j !== this.parserRegExpList.length; ++j) {
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
const parserRegExp = this.parserRegExpList[j]
if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) {
const parser = this.customParsers.get(parserRegExp.toString())
// we set request content-type in cache to reduce parsing of MIME type
if (parserRegExp.test(contentType)) {
parser = this.customParsers.get(parserRegExp.toString())
this.cache.set(contentType, parser)
return parser
}
@@ -140,13 +151,19 @@ ContentTypeParser.prototype.removeAll = function () {
}
ContentTypeParser.prototype.remove = function (contentType) {
if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
let parsers
const removed = this.customParsers.delete(contentType.toString())
if (typeof contentType === 'string') {
contentType = contentType.trim().toLowerCase()
parsers = this.parserList
} else {
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
contentType = contentType.toString()
parsers = this.parserRegExpList
}
const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
const idx = parsers.findIndex(ct => ct.toString() === contentType.toString())
const removed = this.customParsers.delete(contentType)
const idx = parsers.findIndex(ct => ct.toString() === contentType)
if (idx > -1) {
parsers.splice(idx, 1)
@@ -159,17 +176,18 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
const parser = this.getParser(contentType)
if (parser === undefined) {
if (request.is404) {
if (request.is404 === true) {
handler(request, reply)
} else {
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
return
}
// Early return to avoid allocating an AsyncResource if it's not needed
reply[kReplyIsError] = true
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
return
}
const resource = new AsyncResource('content-type-parser:run', request)
const done = resource.bind(onDone)
if (parser.asString === true || parser.asBuffer === true) {
rawBody(
@@ -179,50 +197,44 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
parser,
done
)
} else {
const result = parser.fn(request, request[kRequestPayloadStream], done)
if (result && typeof result.then === 'function') {
result.then(body => done(null, body), done)
}
return
}
function done (error, body) {
// We cannot use resource.bind() because it is broken in node v12 and v14
resource.runInAsyncScope(() => {
resource.emitDestroy()
if (error) {
reply[kReplyIsError] = true
reply.send(error)
} else {
request.body = body
handler(request, reply)
}
})
const result = parser.fn(request, request[kRequestPayloadStream], done)
if (result && typeof result.then === 'function') {
result.then(body => { done(null, body) }, done)
}
function onDone (error, body) {
resource.emitDestroy()
if (error != null) {
// We must close the connection as the client may
// send more data
reply.header('connection', 'close')
reply[kReplyIsError] = true
reply.send(error)
return
}
request.body = body
handler(request, reply)
}
}
function rawBody (request, reply, options, parser, done) {
const asString = parser.asString
const asString = parser.asString === true
const limit = options.limit === null ? parser.bodyLimit : options.limit
const contentLength = request.headers['content-length'] === undefined
? NaN
: Number(request.headers['content-length'])
const contentLength = Number(request.headers['content-length'])
if (contentLength > limit) {
// We must close the connection as the client is going
// to send this data anyway
reply.header('connection', 'close')
reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
return
}
let receivedLength = 0
let body = asString === true ? '' : []
let body = asString ? '' : []
const payload = request[kRequestPayloadStream] || request.raw
if (asString === true) {
if (asString) {
payload.setEncoding('utf8')
}
@@ -232,7 +244,7 @@ function rawBody (request, reply, options, parser, done) {
payload.resume()
function onData (chunk) {
receivedLength += chunk.length
receivedLength += asString ? Buffer.byteLength(chunk) : chunk.length
const { receivedEncodedLength = 0 } = payload
// The resulting body length must not exceed bodyLimit (see "zip bomb").
// The case when encoded length is larger than received length is rather theoretical,
@@ -241,11 +253,11 @@ function rawBody (request, reply, options, parser, done) {
payload.removeListener('data', onData)
payload.removeListener('end', onEnd)
payload.removeListener('error', onEnd)
reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
return
}
if (asString === true) {
if (asString) {
body += chunk
} else {
body.push(chunk)
@@ -257,51 +269,45 @@ function rawBody (request, reply, options, parser, done) {
payload.removeListener('end', onEnd)
payload.removeListener('error', onEnd)
if (err !== undefined) {
if (err != null) {
if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) {
err.statusCode = 400
}
reply[kReplyIsError] = true
reply.code(err.statusCode).send(err)
done(err, undefined)
return
}
if (asString === true) {
receivedLength = Buffer.byteLength(body)
}
if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) {
reply.header('connection', 'close')
reply.send(new FST_ERR_CTP_INVALID_CONTENT_LENGTH())
done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined)
return
}
if (asString === false) {
if (!asString) {
body = Buffer.concat(body)
}
const result = parser.fn(request, body, done)
if (result && typeof result.then === 'function') {
result.then(body => done(null, body), done)
result.then(body => { done(null, body) }, done)
}
}
}
function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
const parseOptions = { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }
return defaultJsonParser
function defaultJsonParser (req, body, done) {
if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) {
return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
if (body.length === 0) {
done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
return
}
let json
try {
json = secureJson.parse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })
} catch (err) {
err.statusCode = 400
return done(err, undefined)
done(null, secureJsonParse(body, parseOptions))
} catch {
done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined)
}
done(null, json)
}
}
@@ -373,60 +379,15 @@ function removeAllContentTypeParsers () {
this[kContentTypeParser].removeAll()
}
function compareContentType (contentType, parserListItem) {
if (parserListItem.isEssence) {
// we do essence check
return contentType.type.indexOf(parserListItem) !== -1
} else {
// when the content-type includes parameters
// we do a full-text search
// reject essence content-type before checking parameters
if (contentType.type.indexOf(parserListItem.type) === -1) return false
for (const key of parserListItem.parameterKeys) {
// reject when missing parameters
if (!(key in contentType.parameters)) return false
// reject when parameters do not match
if (contentType.parameters[key] !== parserListItem.parameters[key]) return false
}
return true
function validateRegExp (regexp) {
// RegExp should either start with ^ or include ;?
// It can ensure the user is properly detect the essence
// MIME types.
if (regexp.source[0] !== '^' && regexp.source.includes(';?') === false) {
FSTSEC001(regexp.source)
}
}
function compareRegExpContentType (contentType, essenceMIMEType, regexp) {
if (regexp.isEssence) {
// we do essence check
return regexp.test(essenceMIMEType)
} else {
// when the content-type includes parameters
// we do a full-text match
return regexp.test(contentType)
}
}
function ParserListItem (contentType) {
this.name = contentType
// we pre-calculate all the needed information
// before content-type comparison
const parsed = safeParseContentType(contentType)
this.isEssence = contentType.indexOf(';') === -1
// we should not allow empty string for parser list item
// because it would become a match-all handler
if (this.isEssence === false && parsed.type === '') {
// handle semicolon or empty string
const tmp = contentType.split(';', 1)[0]
this.type = tmp === '' ? contentType : tmp
} else {
this.type = parsed.type
}
this.parameters = parsed.parameters
this.parameterKeys = Object.keys(parsed.parameters)
}
// used in ContentTypeParser.remove
ParserListItem.prototype.toString = function () {
return this.name
}
module.exports = ContentTypeParser
module.exports.helpers = {
buildContentTypeParser,

View File

@@ -14,8 +14,7 @@ const {
kContentTypeParser,
kRouteByFastify,
kRequestCacheValidateFns,
kReplyCacheSerializeFns,
kPublicRouteContext
kReplyCacheSerializeFns
} = require('./symbols.js')
// Object that holds the context of every request
@@ -79,35 +78,14 @@ function Context ({
this.validatorCompiler = validatorCompiler || null
this.serializerCompiler = serializerCompiler || null
// Route + Userland configurations for the route
this[kPublicRouteContext] = getPublicRouteContext(this)
this.server = server
}
function getPublicRouteContext (context) {
return Object.create(null, {
schema: {
enumerable: true,
get () {
return context.schema
}
},
config: {
enumerable: true,
get () {
return context.config
}
}
})
}
function defaultSchemaErrorFormatter (errors, dataVar) {
let text = ''
const separator = ', '
// eslint-disable-next-line no-var
for (var i = 0; i !== errors.length; ++i) {
for (let i = 0; i !== errors.length; ++i) {
const e = errors[i]
text += dataVar + (e.instancePath || '') + ' ' + e.message + separator
}

View File

@@ -1,7 +1,5 @@
'use strict'
/* eslint no-prototype-builtins: 0 */
const {
kReply,
kRequest,
@@ -13,13 +11,13 @@ const {
FST_ERR_DEC_ALREADY_PRESENT,
FST_ERR_DEC_MISSING_DEPENDENCY,
FST_ERR_DEC_AFTER_START,
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE
FST_ERR_DEC_REFERENCE_TYPE,
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE,
FST_ERR_DEC_UNDECLARED
} = require('./errors')
const { FSTDEP006 } = require('./warnings')
function decorate (instance, name, fn, dependencies) {
if (Object.prototype.hasOwnProperty.call(instance, name)) {
if (Object.hasOwn(instance, name)) {
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
}
@@ -35,9 +33,21 @@ function decorate (instance, name, fn, dependencies) {
}
}
function getInstanceDecorator (name) {
if (!checkExistence(this, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
}
if (typeof this[name] === 'function') {
return this[name].bind(this)
}
return this[name]
}
function decorateConstructor (konstructor, name, fn, dependencies) {
const instance = konstructor.prototype
if (Object.prototype.hasOwnProperty.call(instance, name) || hasKey(konstructor, name)) {
if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
}
@@ -58,7 +68,7 @@ function decorateConstructor (konstructor, name, fn, dependencies) {
function checkReferenceType (name, fn) {
if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
FSTDEP006(name)
throw new FST_ERR_DEC_REFERENCE_TYPE(name, typeof fn)
}
}
@@ -102,8 +112,7 @@ function checkDependencies (instance, name, deps) {
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
}
// eslint-disable-next-line no-var
for (var i = 0; i !== deps.length; ++i) {
for (let i = 0; i !== deps.length; ++i) {
if (!checkExistence(instance, deps[i])) {
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
}
@@ -137,5 +146,7 @@ module.exports = {
existReply: checkReplyExistence,
dependencies: checkDependencies,
decorateReply,
decorateRequest
decorateRequest,
getInstanceDecorator,
hasKey
}

View File

@@ -1,12 +1,12 @@
'use strict'
const statusCodes = require('node:http').STATUS_CODES
const wrapThenable = require('./wrapThenable')
const wrapThenable = require('./wrap-thenable.js')
const { setErrorStatusCode } = require('./error-status.js')
const {
kReplyHeaders,
kReplyNextErrorHandler,
kReplyIsRunningOnErrorHook,
kReplyHasStatusCode,
kRouteContext,
kDisableRequestLogging
} = require('./symbols.js')
@@ -39,7 +39,7 @@ function handleError (reply, error, cb) {
if (!reply.log[kDisableRequestLogging]) {
reply.log.warn(
{ req: reply.request, res: reply, err: error },
error && error.message
error?.message
)
}
reply.raw.writeHead(reply.raw.statusCode)
@@ -81,22 +81,19 @@ function handleError (reply, error, cb) {
function defaultErrorHandler (error, request, reply) {
setErrorHeaders(error, reply)
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
const statusCode = error.statusCode || error.status
reply.code(statusCode >= 400 ? statusCode : 500)
}
setErrorStatusCode(reply, error)
if (reply.statusCode < 500) {
if (!reply.log[kDisableRequestLogging]) {
reply.log.info(
{ res: reply, err: error },
error && error.message
error?.message
)
}
} else {
if (!reply.log[kDisableRequestLogging]) {
reply.log.error(
{ req: request, res: reply, err: error },
error && error.message
error?.message
)
}
}
@@ -110,18 +107,20 @@ function fallbackErrorHandler (error, reply, cb) {
let payload
try {
const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
payload = (serializerFn === false)
? serializeError({
if (serializerFn === false) {
payload = serializeError({
error: statusCodes[statusCode + ''],
code: error.code,
message: error.message,
statusCode
})
: serializerFn(Object.create(error, {
} else {
payload = serializerFn(Object.create(error, {
error: { value: statusCodes[statusCode + ''] },
message: { value: error.message },
statusCode: { value: statusCode }
}))
}
} catch (err) {
if (!reply.log[kDisableRequestLogging]) {
// error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization

View File

@@ -1,5 +1,5 @@
// This file is autogenerated by build/build-error-serializer.js, do not edit
/* istanbul ignore file */
/* c8 ignore start */
'use strict'
@@ -117,3 +117,4 @@ let addComma = false
return main
}(validator, serializer)
/* c8 ignore stop */

14
backend/node_modules/fastify/lib/error-status.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
const {
kReplyHasStatusCode
} = require('./symbols')
function setErrorStatusCode (reply, err) {
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
const statusCode = err && (err.statusCode || err.status)
reply.code(statusCode >= 400 ? statusCode : 500)
}
}
module.exports = { setErrorStatusCode }

View File

@@ -47,12 +47,6 @@ const codes = {
500,
TypeError
),
FST_ERR_VERSION_CONSTRAINT_NOT_STR: createError(
'FST_ERR_VERSION_CONSTRAINT_NOT_STR',
'Version constraint should be a string.',
500,
TypeError
),
FST_ERR_VALIDATION: createError(
'FST_ERR_VALIDATION',
'%s',
@@ -70,6 +64,12 @@ const codes = {
500,
TypeError
),
FST_ERR_ERROR_HANDLER_ALREADY_SET: createError(
'FST_ERR_ERROR_HANDLER_ALREADY_SET',
"Error Handler already set in this scope. Set 'allowErrorHandlerOverride: true' to allow overriding.",
500,
TypeError
),
/**
* ContentTypeParser
@@ -124,6 +124,11 @@ const codes = {
"Body cannot be empty when content-type is set to 'application/json'",
400
),
FST_ERR_CTP_INVALID_JSON_BODY: createError(
'FST_ERR_CTP_INVALID_JSON_BODY',
"Body is not valid JSON but content-type is set to 'application/json'",
400
),
FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError(
'FST_ERR_CTP_INSTANCE_ALREADY_STARTED',
'Cannot call "%s" when fastify instance is already started!',
@@ -151,6 +156,14 @@ const codes = {
'FST_ERR_DEC_AFTER_START',
"The decorator '%s' has been added after start!"
),
FST_ERR_DEC_REFERENCE_TYPE: createError(
'FST_ERR_DEC_REFERENCE_TYPE',
"The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead."
),
FST_ERR_DEC_UNDECLARED: createError(
'FST_ERR_DEC_UNDECLARED',
"No decorator '%s' has been declared on %s."
),
/**
* hooks
@@ -191,7 +204,7 @@ const codes = {
FST_ERR_HOOK_TIMEOUT: createError(
'FST_ERR_HOOK_TIMEOUT',
"A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise"
"A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise"
),
/**
@@ -209,6 +222,27 @@ const codes = {
TypeError
),
FST_ERR_LOG_INVALID_LOGGER_INSTANCE: createError(
'FST_ERR_LOG_INVALID_LOGGER_INSTANCE',
'loggerInstance only accepts a logger instance.',
500,
TypeError
),
FST_ERR_LOG_INVALID_LOGGER_CONFIG: createError(
'FST_ERR_LOG_INVALID_LOGGER_CONFIG',
'logger options only accepts a configuration object.',
500,
TypeError
),
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED: createError(
'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED',
'You cannot provide both logger and loggerInstance. Please provide only one.',
500,
TypeError
),
/**
* reply
*/
@@ -222,6 +256,10 @@ const codes = {
'FST_ERR_REP_RESPONSE_BODY_CONSUMED',
'Response.body is already consumed.'
),
FST_ERR_REP_READABLE_STREAM_LOCKED: createError(
'FST_ERR_REP_READABLE_STREAM_LOCKED',
'ReadableStream was locked. You should call releaseLock() method on reader before sending.'
),
FST_ERR_REP_ALREADY_SENT: createError(
'FST_ERR_REP_ALREADY_SENT',
'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
@@ -301,14 +339,6 @@ const codes = {
'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }'
),
/**
* http2
*/
FST_ERR_HTTP2_INVALID_VERSION: createError(
'FST_ERR_HTTP2_INVALID_VERSION',
'HTTP2 is available only from node >= 8.8.1'
),
/**
* initialConfig
*/
@@ -339,12 +369,6 @@ const codes = {
'Unexpected error from async constraint',
500
),
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE: createError(
'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE',
'The defaultRoute type should be a function',
500,
TypeError
),
FST_ERR_INVALID_URL: createError(
'FST_ERR_INVALID_URL',
"URL must be a string. Received '%s'",
@@ -429,6 +453,12 @@ const codes = {
'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE',
"The decorator '%s'%s is not present in %s"
),
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER: createError(
'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER',
'The %s plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.',
500,
TypeError
),
/**
* Avvio Errors

View File

@@ -18,8 +18,8 @@ const { buildErrorHandler } = require('./error-handler.js')
const {
FST_ERR_NOT_FOUND
} = require('./errors')
const { createChildLogger } = require('./logger')
const { getGenReqId } = require('./reqIdGenFactory.js')
const { createChildLogger } = require('./logger-factory')
const { getGenReqId } = require('./req-id-gen-factory.js')
/**
* Each fastify instance have a:
@@ -49,7 +49,8 @@ function fourOhFour (options) {
function basic404 (request, reply) {
const { url, method } = request.raw
const message = `Route ${method}:${url} not found`
if (!disableRequestLogging) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging
if (!resolvedDisableRequestLogging) {
request.log.info(message)
}
reply.code(404).send({
@@ -150,7 +151,9 @@ function fourOhFour (options) {
.map(h => h.bind(this))
context[hook] = toSet.length ? toSet : null
}
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
context.errorHandler = opts.errorHandler
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
: this[kErrorHandler]
})
if (this[kFourOhFourContext] !== null && prefix === '/') {

187
backend/node_modules/fastify/lib/handle-request.js generated vendored Normal file
View File

@@ -0,0 +1,187 @@
'use strict'
const diagnostics = require('node:diagnostics_channel')
const { validate: validateSchema } = require('./validation')
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
const wrapThenable = require('./wrap-thenable')
const { setErrorStatusCode } = require('./error-status')
const {
kReplyIsError,
kRouteContext,
kFourOhFourContext,
kSupportedHTTPMethods
} = require('./symbols')
const channels = diagnostics.tracingChannel('fastify.request.handler')
function handleRequest (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const method = request.method
if (this[kSupportedHTTPMethods].bodyless.has(method)) {
handler(request, reply)
return
}
if (this[kSupportedHTTPMethods].bodywith.has(method)) {
const headers = request.headers
const contentType = headers['content-type']
if (contentType === undefined) {
const contentLength = headers['content-length']
const transferEncoding = headers['transfer-encoding']
const isEmptyBody = transferEncoding === undefined &&
(contentLength === undefined || contentLength === '0')
if (isEmptyBody) {
// Request has no body to parse
handler(request, reply)
return
}
request[kRouteContext].contentTypeParser.run('', handler, request, reply)
return
}
request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply)
return
}
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
handler(request, reply)
}
function handler (request, reply) {
try {
if (request[kRouteContext].preValidation !== null) {
preValidationHookRunner(
request[kRouteContext].preValidation,
request,
reply,
preValidationCallback
)
} else {
preValidationCallback(null, request, reply)
}
} catch (err) {
preValidationCallback(err, request, reply)
}
}
function preValidationCallback (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const validationErr = validateSchema(reply[kRouteContext], request)
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
if (isAsync) {
const cb = validationCompleted.bind(null, request, reply)
validationErr.then(cb, cb)
} else {
validationCompleted(request, reply, validationErr)
}
}
function validationCompleted (request, reply, validationErr) {
if (validationErr) {
if (reply[kRouteContext].attachValidation === false) {
reply.send(validationErr)
return
}
reply.request.validationError = validationErr
}
// preHandler hook
if (request[kRouteContext].preHandler !== null) {
preHandlerHookRunner(
request[kRouteContext].preHandler,
request,
reply,
preHandlerCallback
)
} else {
preHandlerCallback(null, request, reply)
}
}
function preHandlerCallback (err, request, reply) {
if (reply.sent) return
const context = request[kRouteContext]
if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
preHandlerCallbackInner(err, request, reply)
} else {
const store = {
request,
reply,
async: false,
route: {
url: context.config.url,
method: context.config.method
}
}
channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store)
}
}
function preHandlerCallbackInner (err, request, reply, store) {
const context = request[kRouteContext]
try {
if (err != null) {
reply[kReplyIsError] = true
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store)
}
reply.send(err)
return
}
let result
try {
result = context.handler(request, reply)
} catch (err) {
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store)
}
reply[kReplyIsError] = true
reply.send(err)
return
}
if (result !== undefined) {
if (result !== null && typeof result.then === 'function') {
wrapThenable(result, reply, store)
} else {
reply.send(result)
}
}
} finally {
if (store) channels.end.publish(store)
}
}
module.exports = handleRequest
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }

View File

@@ -1,156 +0,0 @@
'use strict'
const { validate: validateSchema } = require('./validation')
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
const wrapThenable = require('./wrapThenable')
const {
kReplyIsError,
kRouteContext
} = require('./symbols')
function handleRequest (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const method = request.raw.method
const headers = request.headers
const context = request[kRouteContext]
if (method === 'GET' || method === 'HEAD') {
handler(request, reply)
return
}
const contentType = headers['content-type']
if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' ||
method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'COPY' || method === 'MOVE' ||
method === 'MKCOL' || method === 'REPORT' || method === 'MKCALENDAR') {
if (contentType === undefined) {
if (
headers['transfer-encoding'] === undefined &&
(headers['content-length'] === '0' || headers['content-length'] === undefined)
) { // Request has no body to parse
handler(request, reply)
} else {
context.contentTypeParser.run('', handler, request, reply)
}
} else {
context.contentTypeParser.run(contentType, handler, request, reply)
}
return
}
if (method === 'OPTIONS' || method === 'DELETE') {
if (
contentType !== undefined &&
(
headers['transfer-encoding'] !== undefined ||
headers['content-length'] !== undefined
)
) {
context.contentTypeParser.run(contentType, handler, request, reply)
} else {
handler(request, reply)
}
return
}
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
handler(request, reply)
}
function handler (request, reply) {
try {
if (request[kRouteContext].preValidation !== null) {
preValidationHookRunner(
request[kRouteContext].preValidation,
request,
reply,
preValidationCallback
)
} else {
preValidationCallback(null, request, reply)
}
} catch (err) {
preValidationCallback(err, request, reply)
}
}
function preValidationCallback (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const validationErr = validateSchema(reply[kRouteContext], request)
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
if (isAsync) {
const cb = validationCompleted.bind(null, request, reply)
validationErr.then(cb, cb)
} else {
validationCompleted(request, reply, validationErr)
}
}
function validationCompleted (request, reply, validationErr) {
if (validationErr) {
if (reply[kRouteContext].attachValidation === false) {
reply.send(validationErr)
return
}
reply.request.validationError = validationErr
}
// preHandler hook
if (request[kRouteContext].preHandler !== null) {
preHandlerHookRunner(
request[kRouteContext].preHandler,
request,
reply,
preHandlerCallback
)
} else {
preHandlerCallback(null, request, reply)
}
}
function preHandlerCallback (err, request, reply) {
if (reply.sent) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
let result
try {
result = request[kRouteContext].handler(request, reply)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
return
}
if (result !== undefined) {
if (result !== null && typeof result.then === 'function') {
wrapThenable(result, reply)
} else {
reply.send(result)
}
}
}
module.exports = handleRequest
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }

View File

@@ -3,15 +3,27 @@ function headRouteOnSendHandler (req, reply, payload, done) {
// If payload is undefined
if (payload === undefined) {
reply.header('content-length', '0')
return done(null, null)
done(null, null)
return
}
// node:stream
if (typeof payload.resume === 'function') {
payload.on('error', (err) => {
reply.log.error({ err }, 'Error on Stream found for HEAD route')
})
payload.resume()
return done(null, null)
done(null, null)
return
}
// node:stream/web
if (typeof payload.getReader === 'function') {
payload.cancel('Stream cancelled by HEAD route').catch((err) => {
reply.log.error({ err }, 'Error on Stream found for HEAD route')
})
done(null, null)
return
}
const size = '' + Buffer.byteLength(payload)
@@ -23,7 +35,9 @@ function headRouteOnSendHandler (req, reply, payload, done) {
function parseHeadOnSendHandlers (onSendHandlers) {
if (onSendHandlers == null) return headRouteOnSendHandler
return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
return Array.isArray(onSendHandlers)
? [...onSendHandlers, headRouteOnSendHandler]
: [onSendHandlers, headRouteOnSendHandler]
}
module.exports = {

View File

@@ -98,9 +98,12 @@ function hookRunnerApplication (hookName, boot, server, cb) {
next()
function exit (err) {
const hookFnName = hooks[i - 1]?.name
const hookFnFragment = hookFnName ? ` "${hookFnName}"` : ''
if (err) {
if (err.code === 'AVV_ERR_READY_TIMEOUT') {
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName))
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName, hookFnFragment))
} else {
err = AVVIO_ERRORS_MAP[err.code] != null
? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))

View File

@@ -1,24 +0,0 @@
'use strict'
module.exports = {
supportedMethods: [
'DELETE',
'GET',
'HEAD',
'PATCH',
'POST',
'PUT',
'OPTIONS',
'PROPFIND',
'PROPPATCH',
'MKCOL',
'COPY',
'MOVE',
'LOCK',
'UNLOCK',
'TRACE',
'SEARCH',
'REPORT',
'MKCALENDAR'
]
}

View File

@@ -1,6 +1,6 @@
'use strict'
const validate = require('./configValidator')
const validate = require('./config-validator')
const deepClone = require('rfdc')({ circles: true, proto: false })
const { FST_ERR_INIT_OPTS_INVALID } = require('./errors')

View File

@@ -1,105 +1,48 @@
'use strict'
/**
* Code imported from `pino-http`
* Repo: https://github.com/pinojs/pino-http
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
*/
const nullLogger = require('abstract-logging')
const pino = require('pino')
const { serializersSym } = pino.symbols
const {
FST_ERR_LOG_INVALID_DESTINATION,
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
FST_ERR_LOG_INVALID_LOGGER
} = require('./errors')
function createPinoLogger (opts) {
if (opts.stream && opts.file) {
throw new FST_ERR_LOG_INVALID_DESTINATION()
} else if (opts.file) {
// we do not have stream
opts.stream = pino.destination(opts.file)
delete opts.file
/**
* Utility for creating a child logger with the appropriate bindings, logger factory
* and validation.
* @param {object} context
* @param {import('../fastify').FastifyBaseLogger} logger
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
* @param {string} reqId
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
*
* @returns {object} New logger instance, inheriting all parent bindings,
* with child bindings added.
*/
function createChildLogger (context, logger, req, reqId, loggerOpts) {
const loggerBindings = {
[context.requestIdLogLabel]: reqId
}
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
// Optimization: bypass validation if the factory is our own default factory
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
validateLogger(child, true) // throw if the child is not a valid logger
}
const prevLogger = opts.logger
const prevGenReqId = opts.genReqId
let logger = null
if (prevLogger) {
opts.logger = undefined
opts.genReqId = undefined
// we need to tap into pino internals because in v5 it supports
// adding serializers in child loggers
if (prevLogger[serializersSym]) {
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
}
logger = prevLogger.child({}, opts)
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
logger = pino(opts, opts.stream)
}
return logger
return child
}
const serializers = {
req: function asReqValue (req) {
return {
method: req.method,
url: req.url,
version: req.headers && req.headers['accept-version'],
hostname: req.hostname,
remoteAddress: req.ip,
remotePort: req.socket ? req.socket.remotePort : undefined
}
},
err: pino.stdSerializers.err,
res: function asResValue (reply) {
return {
statusCode: reply.statusCode
}
}
}
function now () {
const ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}
function createLogger (options) {
if (!options.logger) {
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
}
if (validateLogger(options.logger)) {
const logger = createPinoLogger({
logger: options.logger,
serializers: Object.assign({}, serializers, options.logger.serializers)
})
return { logger, hasLogger: true }
}
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
/** Default factory to create child logger instance
*
* @param {import('../fastify.js').FastifyBaseLogger} logger
* @param {import('../types/logger.js').Bindings} bindings
* @param {import('../types/logger.js').ChildLoggerOptions} opts
*
* @returns {import('../types/logger.js').FastifyBaseLogger}
*/
function defaultChildLoggerFactory (logger, bindings, opts) {
return logger.child(bindings, opts)
}
/**
@@ -129,42 +72,65 @@ function validateLogger (logger, strict) {
}
}
/**
* Utility for creating a child logger with the appropriate bindings, logger factory
* and validation.
* @param {object} context
* @param {import('../fastify').FastifyBaseLogger} logger
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
* @param {string} reqId
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
*/
function createChildLogger (context, logger, req, reqId, loggerOpts) {
const loggerBindings = {
[context.requestIdLogLabel]: reqId
}
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
// Optimization: bypass validation if the factory is our own default factory
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
validateLogger(child, true) // throw if the child is not a valid logger
function createLogger (options) {
if (options.logger && options.loggerInstance) {
throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
}
return child
if (!options.loggerInstance && !options.logger) {
const nullLogger = require('abstract-logging')
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
}
const { createPinoLogger, serializers } = require('./logger-pino.js')
// check if the logger instance has all required properties
if (validateLogger(options.loggerInstance)) {
const logger = createPinoLogger({
logger: options.loggerInstance,
serializers: Object.assign({}, serializers, options.loggerInstance.serializers)
})
return { logger, hasLogger: true }
}
// if a logger instance is passed to logger, throw an exception
if (validateLogger(options.logger)) {
throw FST_ERR_LOG_INVALID_LOGGER_CONFIG()
}
if (options.loggerInstance) {
throw FST_ERR_LOG_INVALID_LOGGER_INSTANCE()
}
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
}
/**
* @param {import('../fastify.js').FastifyBaseLogger} logger
* @param {import('../types/logger.js').Bindings} bindings
* @param {import('../types/logger.js').ChildLoggerOptions} opts
*/
function defaultChildLoggerFactory (logger, bindings, opts) {
return logger.child(bindings, opts)
function now () {
const ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}
module.exports = {
createLogger,
createChildLogger,
defaultChildLoggerFactory,
serializers,
createLogger,
validateLogger,
now
}

68
backend/node_modules/fastify/lib/logger-pino.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
'use strict'
/**
* Code imported from `pino-http`
* Repo: https://github.com/pinojs/pino-http
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
*/
const pino = require('pino')
const { serializersSym } = pino.symbols
const {
FST_ERR_LOG_INVALID_DESTINATION
} = require('./errors')
function createPinoLogger (opts) {
if (opts.stream && opts.file) {
throw new FST_ERR_LOG_INVALID_DESTINATION()
} else if (opts.file) {
// we do not have stream
opts.stream = pino.destination(opts.file)
delete opts.file
}
const prevLogger = opts.logger
const prevGenReqId = opts.genReqId
let logger = null
if (prevLogger) {
opts.logger = undefined
opts.genReqId = undefined
// we need to tap into pino internals because in v5 it supports
// adding serializers in child loggers
if (prevLogger[serializersSym]) {
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
}
logger = prevLogger.child({}, opts)
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
logger = pino(opts, opts.stream)
}
return logger
}
const serializers = {
req: function asReqValue (req) {
return {
method: req.method,
url: req.url,
version: req.headers && req.headers['accept-version'],
host: req.host,
remoteAddress: req.ip,
remotePort: req.socket ? req.socket.remotePort : undefined
}
},
err: pino.stdSerializers.err,
res: function asResValue (reply) {
return {
statusCode: reply.statusCode
}
}
}
module.exports = {
serializers,
createPinoLogger
}

View File

@@ -12,15 +12,16 @@ const {
kReply,
kRequest,
kFourOhFour,
kPluginNameChain
kPluginNameChain,
kErrorHandlerAlreadySet
} = require('./symbols.js')
const Reply = require('./reply')
const Request = require('./request')
const SchemaController = require('./schema-controller')
const ContentTypeParser = require('./contentTypeParser')
const ContentTypeParser = require('./content-type-parser.js')
const { buildHooks } = require('./hooks')
const pluginUtils = require('./pluginUtils')
const pluginUtils = require('./plugin-utils.js')
// Function that runs the encapsulation magic.
// Everything that need to be encapsulated must be handled in this function.
@@ -57,6 +58,7 @@ module.exports = function override (old, fn, opts) {
// Track the plugin chain since the root instance.
// When an non-encapsulated plugin is added, the chain will be updated.
instance[kPluginNameChain] = [fnName]
instance[kErrorHandlerAlreadySet] = false
if (instance[kLogSerializers] || opts.logSerializers) {
instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers)
@@ -66,7 +68,7 @@ module.exports = function override (old, fn, opts) {
instance[kFourOhFour].arrange404(instance)
}
for (const hook of instance[kHooks].onRegister) hook.call(this, instance, opts)
for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts)
return instance
}

View File

@@ -6,12 +6,14 @@ const kRegisteredPlugins = Symbol.for('registered-plugin')
const {
kTestInternals
} = require('./symbols.js')
const { exist, existReply, existRequest } = require('./decorate')
const { exist, existReply, existRequest } = require('./decorate.js')
const {
FST_ERR_PLUGIN_VERSION_MISMATCH,
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE
} = require('./errors')
const { FSTWRN002 } = require('./warnings.js')
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
} = require('./errors.js')
const rcRegex = /-(?:rc|pre|alpha).+$/u
function getMeta (fn) {
return fn[Symbol.for('plugin-meta')]
@@ -48,7 +50,7 @@ function getPluginName (func) {
function getFuncPreview (func) {
// takes the first two lines of the function if nothing else works
return func.toString().split('\n').slice(0, 2).map(s => s.trim()).join(' -- ')
return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')
}
function getDisplayName (fn) {
@@ -106,11 +108,11 @@ function _checkDecorators (that, instance, decorators, name) {
function checkVersion (fn) {
const meta = getMeta(fn)
if (!meta) return
if (meta?.fastify == null) return
const requiredVersion = meta.fastify
const fastifyRc = /-rc.+$/.test(this.version)
const fastifyRc = rcRegex.test(this.version)
if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
// A Fastify release candidate phase is taking place. In order to reduce
// the effort needed to test plugins with the RC, we allow plugins targeting
@@ -138,7 +140,7 @@ function registerPluginName (fn) {
function checkPluginHealthiness (fn, pluginName) {
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
FSTWRN002(pluginName || 'anonymous')
throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName)
}
}

23
backend/node_modules/fastify/lib/promise.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
'use strict'
const { kTestInternals } = require('./symbols')
function withResolvers () {
let res, rej
const promise = new Promise((resolve, reject) => {
res = resolve
rej = reject
})
return { promise, resolve: res, reject: rej }
}
module.exports = {
// TODO(20.x): remove when node@20 is not supported
withResolvers: typeof Promise.withResolvers === 'function'
? Promise.withResolvers.bind(Promise) // Promise.withResolvers must bind to itself
/* c8 ignore next */
: withResolvers, // Tested using the kTestInternals
[kTestInternals]: {
withResolvers
}
}

View File

@@ -1,11 +1,9 @@
'use strict'
const eos = require('node:stream').finished
const Readable = require('node:stream').Readable
const {
kFourOhFourContext,
kPublicRouteContext,
kReplyErrorHandlerCalled,
kReplyHijacked,
kReplyStartTime,
@@ -32,8 +30,8 @@ const {
preSerializationHookRunner
} = require('./hooks')
const internals = require('./handleRequest')[Symbol.for('internals')]
const loggerUtils = require('./logger')
const internals = require('./handle-request.js')[Symbol.for('internals')]
const loggerUtils = require('./logger-factory')
const now = loggerUtils.now
const { handleError } = require('./error-handler')
const { getSchemaSerializer } = require('./schemas')
@@ -46,16 +44,17 @@ const CONTENT_TYPE = {
const {
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
FST_ERR_REP_RESPONSE_BODY_CONSUMED,
FST_ERR_REP_READABLE_STREAM_LOCKED,
FST_ERR_REP_ALREADY_SENT,
FST_ERR_REP_SENT_VALUE,
FST_ERR_SEND_INSIDE_ONERR,
FST_ERR_BAD_STATUS_CODE,
FST_ERR_BAD_TRAILER_NAME,
FST_ERR_BAD_TRAILER_VALUE,
FST_ERR_MISSING_SERIALIZATION_FN,
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN,
FST_ERR_DEC_UNDECLARED
} = require('./errors')
const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020, FSTDEP021 } = require('./warnings')
const decorators = require('./decorate')
const toString = Object.prototype.toString
@@ -80,14 +79,6 @@ Object.defineProperties(Reply.prototype, {
return this.request[kRouteContext]
}
},
// TODO: remove once v5 is done
// Is temporary to avoid constant conflicts between `next` and `main`
context: {
get () {
FSTDEP019()
return this.request[kRouteContext]
}
},
elapsedTime: {
get () {
if (this[kReplyStartTime] === undefined) {
@@ -106,20 +97,6 @@ Object.defineProperties(Reply.prototype, {
get () {
// We are checking whether reply was hijacked or the response has ended.
return (this[kReplyHijacked] || this.raw.writableEnded) === true
},
set (value) {
FSTDEP010()
if (value !== true) {
throw new FST_ERR_REP_SENT_VALUE()
}
// We throw only if sent was overwritten from Fastify
if (this.sent && this[kReplyHijacked]) {
throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method)
}
this[kReplyHijacked] = true
}
},
statusCode: {
@@ -130,29 +107,34 @@ Object.defineProperties(Reply.prototype, {
this.code(value)
}
},
[kPublicRouteContext]: {
routeOptions: {
get () {
return this.request[kPublicRouteContext]
return this.request.routeOptions
}
}
})
Reply.prototype.writeEarlyHints = function (hints, callback) {
this.raw.writeEarlyHints(hints, callback)
return this
}
Reply.prototype.hijack = function () {
this[kReplyHijacked] = true
return this
}
Reply.prototype.send = function (payload) {
if (this[kReplyIsRunningOnErrorHook] === true) {
if (this[kReplyIsRunningOnErrorHook]) {
throw new FST_ERR_SEND_INSIDE_ONERR()
}
if (this.sent) {
if (this.sent === true) {
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
return this
}
if (payload instanceof Error || this[kReplyIsError] === true) {
if (this[kReplyIsError] || payload instanceof Error) {
this[kReplyIsError] = false
onErrorHook(this, payload, onSendHook)
return this
@@ -179,16 +161,18 @@ Reply.prototype.send = function (payload) {
return this
}
if (payload?.buffer instanceof ArrayBuffer) {
if (hasContentType === false) {
if (payload.buffer instanceof ArrayBuffer) {
if (!hasContentType) {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
}
const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
const payloadToSend = Buffer.isBuffer(payload)
? payload
: Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
onSendHook(this, payloadToSend)
return this
}
if (hasContentType === false && typeof payload === 'string') {
if (!hasContentType && typeof payload === 'string') {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN
onSendHook(this, payload)
return this
@@ -199,26 +183,24 @@ Reply.prototype.send = function (payload) {
if (typeof payload !== 'string') {
preSerializationHook(this, payload)
return this
} else {
payload = this[kReplySerializer](payload)
}
payload = this[kReplySerializer](payload)
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
} else if (hasContentType === false || contentType.indexOf('json') > -1) {
if (hasContentType === false) {
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
} else if (!hasContentType || contentType.indexOf('json') !== -1) {
if (!hasContentType) {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
} else {
} else if (contentType.indexOf('charset') === -1) {
// If user doesn't set charset, we will set charset to utf-8
if (contentType.indexOf('charset') === -1) {
const customContentType = contentType.trim()
if (customContentType.endsWith(';')) {
// custom content-type is ended with ';'
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
} else {
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
}
const customContentType = contentType.trim()
if (customContentType.endsWith(';')) {
// custom content-type is ended with ';'
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
} else {
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
}
}
if (typeof payload !== 'string') {
preSerializationHook(this, payload)
return this
@@ -232,12 +214,8 @@ Reply.prototype.send = function (payload) {
Reply.prototype.getHeader = function (key) {
key = key.toLowerCase()
const res = this.raw
let value = this[kReplyHeaders][key]
if (value === undefined && res.hasHeader(key)) {
value = res.getHeader(key)
}
return value
const value = this[kReplyHeaders][key]
return value !== undefined ? value : this.raw.getHeader(key)
}
Reply.prototype.getHeaders = function () {
@@ -283,8 +261,7 @@ Reply.prototype.header = function (key, value = '') {
Reply.prototype.headers = function (headers) {
const keys = Object.keys(headers)
/* eslint-disable no-var */
for (var i = 0; i !== keys.length; ++i) {
for (let i = 0; i !== keys.length; ++i) {
const key = keys[i]
this.header(key, headers[key])
}
@@ -333,12 +310,12 @@ Reply.prototype.removeTrailer = function (key) {
}
Reply.prototype.code = function (code) {
const intValue = Number(code)
if (isNaN(intValue) || intValue < 100 || intValue > 599) {
const statusCode = +code
if (!(statusCode >= 100 && statusCode <= 599)) {
throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
}
this.raw.statusCode = intValue
this.raw.statusCode = statusCode
this[kReplyHasStatusCode] = true
return this
}
@@ -371,13 +348,13 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
}
const serializerCompiler = this[kRouteContext].serializerCompiler ||
this.server[kSchemaController].serializerCompiler ||
(
// We compile the schemas if no custom serializerCompiler is provided
// nor set
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
this.server[kSchemaController].serializerCompiler
)
this.server[kSchemaController].serializerCompiler ||
(
// We compile the schemas if no custom serializerCompiler is provided
// nor set
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
this.server[kSchemaController].serializerCompiler
)
const serializeFn = serializerCompiler({
schema,
@@ -458,13 +435,6 @@ Reply.prototype.type = function (type) {
}
Reply.prototype.redirect = function (url, code) {
if (typeof url === 'number') {
FSTDEP021()
const temp = code
code = url
url = temp
}
if (!code) {
code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
}
@@ -477,13 +447,6 @@ Reply.prototype.callNotFound = function () {
return this
}
// TODO: should be removed in fastify@5
Reply.prototype.getResponseTime = function () {
FSTDEP020()
return this.elapsedTime
}
// Make reply a thenable, so it could be used with async/await.
// See
// - https://github.com/fastify/fastify/issues/1864 for the discussions
@@ -509,6 +472,19 @@ Reply.prototype.then = function (fulfilled, rejected) {
})
}
Reply.prototype.getDecorator = function (name) {
if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'reply')
}
const decorator = this[name]
if (typeof decorator === 'function') {
return decorator.bind(this)
}
return decorator
}
function preSerializationHook (reply, payload) {
if (reply[kRouteContext].preSerialization !== null) {
preSerializationHookRunner(
@@ -519,11 +495,11 @@ function preSerializationHook (reply, payload) {
preSerializationHookEnd
)
} else {
preSerializationHookEnd(null, reply.request, reply, payload)
preSerializationHookEnd(null, undefined, reply, payload)
}
}
function preSerializationHookEnd (err, request, reply, payload) {
function preSerializationHookEnd (err, _request, reply, payload) {
if (err != null) {
onErrorHook(reply, err)
return
@@ -679,9 +655,9 @@ function onSendEnd (reply, payload) {
if (reply[kReplyTrailers] === null) {
const contentLength = reply[kReplyHeaders]['content-length']
if (!contentLength ||
(req.raw.method !== 'HEAD' &&
Number(contentLength) !== Buffer.byteLength(payload)
)
(req.raw.method !== 'HEAD' &&
Number(contentLength) !== Buffer.byteLength(payload)
)
) {
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
}
@@ -705,8 +681,62 @@ function logStreamError (logger, err, res) {
}
function sendWebStream (payload, res, reply) {
const nodeStream = Readable.fromWeb(payload)
sendStream(nodeStream, res, reply)
if (payload.locked) {
throw new FST_ERR_REP_READABLE_STREAM_LOCKED()
}
let sourceOpen = true
let errorLogged = false
const reader = payload.getReader()
eos(res, function (err) {
if (sourceOpen) {
if (err != null && res.headersSent && !errorLogged) {
errorLogged = true
logStreamError(reply.log, err, res)
}
reader.cancel().catch(noop)
}
})
if (!res.headersSent) {
for (const key in reply[kReplyHeaders]) {
res.setHeader(key, reply[kReplyHeaders][key])
}
} else {
reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode')
}
function onRead (result) {
if (result.done) {
sourceOpen = false
sendTrailer(null, res, reply)
return
}
/* c8 ignore next 5 - race condition: eos handler typically fires first */
if (res.destroyed) {
sourceOpen = false
reader.cancel().catch(noop)
return
}
res.write(result.value)
reader.read().then(onRead, onReadError)
}
function onReadError (err) {
sourceOpen = false
if (res.headersSent || reply.request.raw.aborted === true) {
if (!errorLogged) {
errorLogged = true
logStreamError(reply.log, err, reply)
}
res.destroy()
} else {
onErrorHook(reply, err)
}
}
reader.read().then(onRead, onReadError)
}
function sendStream (payload, res, reply) {
@@ -810,10 +840,6 @@ function sendTrailer (payload, res, reply) {
const result = reply[kReplyTrailers][trailerName](reply, payload, cb)
if (typeof result === 'object' && typeof result.then === 'function') {
result.then((v) => cb(null, v), cb)
} else if (result !== null && result !== undefined) {
// TODO: should be removed in fastify@5
FSTDEP013()
cb(null, result)
}
}
@@ -906,10 +932,9 @@ function buildReply (R) {
this[kReplyEndTime] = undefined
this.log = log
// eslint-disable-next-line no-var
var prop
// eslint-disable-next-line no-var
for (var i = 0; i < props.length; i++) {
let prop
for (let i = 0; i < props.length; i++) {
prop = props[i]
this[prop.key] = prop.value
}

View File

@@ -1,15 +1,6 @@
'use strict'
const proxyAddr = require('proxy-addr')
const semver = require('semver')
const {
FSTDEP005,
FSTDEP012,
FSTDEP015,
FSTDEP016,
FSTDEP017,
FSTDEP018
} = require('./warnings')
const proxyAddr = require('@fastify/proxy-addr')
const {
kHasBeenDecorated,
kSchemaBody,
@@ -20,10 +11,10 @@ const {
kOptions,
kRequestCacheValidateFns,
kRouteContext,
kPublicRouteContext,
kRequestOriginalUrl
} = require('./symbols')
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
const decorators = require('./decorate')
const HTTP_PART_SYMBOL_MAP = {
body: kSchemaBody,
@@ -49,8 +40,8 @@ function getTrustProxyFn (tp) {
return tp
}
if (tp === true) {
// Support plain true/false
return function () { return true }
// Support trusting everything
return null
}
if (typeof tp === 'number') {
// Support trusting hop count
@@ -83,10 +74,8 @@ function buildRegularRequest (R) {
this.log = log
this.body = undefined
// eslint-disable-next-line no-var
var prop
// eslint-disable-next-line no-var
for (var i = 0; i < props.length; i++) {
let prop
for (let i = 0; i < props.length; i++) {
prop = props[i]
this[prop.key] = prop.value
}
@@ -115,7 +104,8 @@ function buildRequestWithTrustProxy (R, trustProxy) {
Object.defineProperties(_Request.prototype, {
ip: {
get () {
return proxyAddr(this.raw, proxyFn)
const addrs = proxyAddr.all(this.raw, proxyFn)
return addrs[addrs.length - 1]
}
},
ips: {
@@ -123,12 +113,18 @@ function buildRequestWithTrustProxy (R, trustProxy) {
return proxyAddr.all(this.raw, proxyFn)
}
},
hostname: {
host: {
get () {
if (this.ip !== undefined && this.headers['x-forwarded-host']) {
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
}
return this.headers.host || this.headers[':authority']
/**
* The last fallback supports the following cases:
* 1. http.requireHostHeader === false
* 2. HTTP/1.0 without a Host Header
* 3. Headers schema that may remove the Host Header
*/
return this.headers.host ?? this.headers[':authority'] ?? ''
}
},
protocol: {
@@ -146,6 +142,12 @@ function buildRequestWithTrustProxy (R, trustProxy) {
return _Request
}
function assertsRequestDecoration (request, name) {
if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'request')
}
}
Object.defineProperties(Request.prototype, {
server: {
get () {
@@ -171,18 +173,6 @@ Object.defineProperties(Request.prototype, {
return this.raw.method
}
},
context: {
get () {
FSTDEP012()
return this[kRouteContext]
}
},
routerPath: {
get () {
FSTDEP017()
return this[kRouteContext].config?.url
}
},
routeOptions: {
get () {
const context = this[kRouteContext]
@@ -198,37 +188,12 @@ Object.defineProperties(Request.prototype, {
exposeHeadRoute: context.exposeHeadRoute,
prefixTrailingSlash: context.prefixTrailingSlash,
handler: context.handler,
config: context.config,
schema: context.schema,
version
}
Object.defineProperties(options, {
config: {
get: () => context.config
},
schema: {
get: () => context.schema
}
})
return Object.freeze(options)
}
},
routerMethod: {
get () {
FSTDEP018()
return this[kRouteContext].config?.method
}
},
routeConfig: {
get () {
FSTDEP016()
return this[kRouteContext][kPublicRouteContext]?.config
}
},
routeSchema: {
get () {
FSTDEP015()
return this[kRouteContext][kPublicRouteContext].schema
return options
}
},
is404: {
@@ -236,15 +201,6 @@ Object.defineProperties(Request.prototype, {
return this[kRouteContext].config?.url === undefined
}
},
connection: {
get () {
/* istanbul ignore next */
if (semver.gte(process.versions.node, '13.0.0')) {
FSTDEP005()
}
return this.raw.connection
}
},
socket: {
get () {
return this.raw.socket
@@ -257,9 +213,42 @@ Object.defineProperties(Request.prototype, {
}
}
},
host: {
get () {
/**
* The last fallback supports the following cases:
* 1. http.requireHostHeader === false
* 2. HTTP/1.0 without a Host Header
* 3. Headers schema that may remove the Host Header
*/
return this.raw.headers.host ?? this.raw.headers[':authority'] ?? ''
}
},
hostname: {
get () {
return this.raw.headers.host || this.raw.headers[':authority']
// Check for IPV6 Host
if (this.host[0] === '[') {
return this.host.slice(0, this.host.indexOf(']') + 1)
}
return this.host.split(':', 1)[0]
}
},
port: {
get () {
// first try taking port from host
const portFromHost = parseInt(this.host.split(':').slice(-1)[0])
if (!isNaN(portFromHost)) {
return portFromHost
}
// now fall back to port from host/:authority header
const host = (this.headers.host ?? this.headers[':authority'] ?? '')
const portFromHeader = parseInt(host.split(':').slice(-1)[0])
if (!isNaN(portFromHeader)) {
return portFromHeader
}
// fall back to null
return null
}
},
protocol: {
@@ -299,13 +288,13 @@ Object.defineProperties(Request.prototype, {
}
const validatorCompiler = this[kRouteContext].validatorCompiler ||
this.server[kSchemaController].validatorCompiler ||
(
// We compile the schemas if no custom validatorCompiler is provided
// nor set
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
this.server[kSchemaController].validatorCompiler
)
this.server[kSchemaController].validatorCompiler ||
(
// We compile the schemas if no custom validatorCompiler is provided
// nor set
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
this.server[kSchemaController].validatorCompiler
)
const validateFn = validatorCompiler({
schema,
@@ -342,8 +331,8 @@ Object.defineProperties(Request.prototype, {
// We cannot compile if the schema is missed
if (validate == null && (schema == null ||
typeof schema !== 'object' ||
Array.isArray(schema))
typeof schema !== 'object' ||
Array.isArray(schema))
) {
throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
}
@@ -359,6 +348,25 @@ Object.defineProperties(Request.prototype, {
return validate(input)
}
},
getDecorator: {
value: function (name) {
assertsRequestDecoration(this, name)
const decorator = this[name]
if (typeof decorator === 'function') {
return decorator.bind(this)
}
return decorator
}
},
setDecorator: {
value: function (name, value) {
assertsRequestDecoration(this, name)
this[name] = value
}
}
})

View File

@@ -2,16 +2,10 @@
const FindMyWay = require('find-my-way')
const Context = require('./context')
const handleRequest = require('./handleRequest')
const handleRequest = require('./handle-request.js')
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
const { supportedMethods } = require('./httpMethods')
const { normalizeSchema } = require('./schemas')
const { parseHeadOnSendHandlers } = require('./headRoute')
const {
FSTDEP007,
FSTDEP008,
FSTDEP014
} = require('./warnings')
const { parseHeadOnSendHandlers } = require('./head-route.js')
const {
compileSchemasForValidation,
@@ -21,7 +15,6 @@ const {
const {
FST_ERR_SCH_VALIDATION_BUILD,
FST_ERR_SCH_SERIALIZATION_BUILD,
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
FST_ERR_DUPLICATED_ROUTE,
FST_ERR_INVALID_URL,
FST_ERR_HOOK_INVALID_HANDLER,
@@ -38,6 +31,7 @@ const {
const {
kRoutePrefix,
kSupportedHTTPMethods,
kLogLevel,
kLogSerializers,
kHooks,
@@ -55,11 +49,26 @@ const {
kRouteContext
} = require('./symbols.js')
const { buildErrorHandler } = require('./error-handler')
const { createChildLogger } = require('./logger')
const { getGenReqId } = require('./reqIdGenFactory.js')
const { createChildLogger } = require('./logger-factory.js')
const { getGenReqId } = require('./req-id-gen-factory.js')
const { FSTDEP022 } = require('./warnings')
const routerKeys = [
'allowUnsafeRegex',
'buildPrettyMeta',
'caseSensitive',
'constraints',
'defaultRoute',
'ignoreDuplicateSlashes',
'ignoreTrailingSlash',
'maxParamLength',
'onBadUrl',
'querystringParser',
'useSemicolonDelimiter'
]
function buildRouting (options) {
const router = FindMyWay(options.config)
const router = FindMyWay(options)
let avvio
let fourOhFour
@@ -68,11 +77,11 @@ function buildRouting (options) {
let setupResponseListeners
let throwIfAlreadyStarted
let disableRequestLogging
let disableRequestLoggingFn
let ignoreTrailingSlash
let ignoreDuplicateSlashes
let return503OnClosing
let globalExposeHeadRoutes
let validateHTTPVersion
let keepAliveConnections
let closing = false
@@ -85,35 +94,26 @@ function buildRouting (options) {
setup (options, fastifyArgs) {
avvio = fastifyArgs.avvio
fourOhFour = fastifyArgs.fourOhFour
logger = fastifyArgs.logger
logger = options.logger
hasLogger = fastifyArgs.hasLogger
setupResponseListeners = fastifyArgs.setupResponseListeners
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
validateHTTPVersion = fastifyArgs.validateHTTPVersion
globalExposeHeadRoutes = options.exposeHeadRoutes
disableRequestLogging = options.disableRequestLogging
ignoreTrailingSlash = options.ignoreTrailingSlash
ignoreDuplicateSlashes = options.ignoreDuplicateSlashes
return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
if (typeof disableRequestLogging === 'function') {
disableRequestLoggingFn = options.disableRequestLogging
}
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
keepAliveConnections = fastifyArgs.keepAliveConnections
},
routing: router.lookup.bind(router), // router func to find the right handler to call
route, // configure a route in the fastify instance
hasRoute,
prepareRoute,
getDefaultRoute: function () {
FSTDEP014()
return router.defaultRoute
},
setDefaultRoute: function (defaultRoute) {
FSTDEP014()
if (typeof defaultRoute !== 'function') {
throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
}
router.defaultRoute = defaultRoute
},
routeHandler,
closeRoutes: () => { closing = true },
printRoutes: router.prettyPrint.bind(router),
@@ -169,10 +169,11 @@ function buildRouting (options) {
function hasRoute ({ options }) {
const normalizedMethod = options.method?.toUpperCase() ?? ''
return findRoute({
...options,
method: normalizedMethod
}) !== null
return router.hasRoute(
normalizedMethod,
options.url || '',
options.constraints
)
}
function findRoute (options) {
@@ -200,36 +201,13 @@ function buildRouting (options) {
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
*/
function route ({ options, isFastify }) {
throwIfAlreadyStarted('Cannot add route!')
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
const opts = { ...options }
const { exposeHeadRoute } = opts
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
const isGetRoute = opts.method === 'GET' ||
(Array.isArray(opts.method) && opts.method.includes('GET'))
const isHeadRoute = opts.method === 'HEAD' ||
(Array.isArray(opts.method) && opts.method.includes('HEAD'))
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
throwIfAlreadyStarted('Cannot add route!')
const path = opts.url || opts.path || ''
if (Array.isArray(opts.method)) {
// eslint-disable-next-line no-var
for (var i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod(opts.method[i])
validateSchemaBodyOption(opts.method[i], path, opts.schema)
}
} else {
opts.method = normalizeAndValidateMethod(opts.method)
validateSchemaBodyOption(opts.method, path, opts.schema)
}
if (!opts.handler) {
throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
}
@@ -240,6 +218,30 @@ function buildRouting (options) {
validateBodyLimitOption(opts.bodyLimit)
const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
let isGetRoute = false
let isHeadRoute = false
if (Array.isArray(opts.method)) {
for (let i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
isGetRoute = opts.method.includes('GET')
isHeadRoute = opts.method.includes('HEAD')
}
} else {
opts.method = normalizeAndValidateMethod.call(this, opts.method)
validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
isGetRoute = opts.method === 'GET'
isHeadRoute = opts.method === 'HEAD'
}
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
const prefix = this[kRoutePrefix]
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
@@ -347,19 +349,9 @@ function buildRouting (options) {
isFastify
})
if (opts.version) {
FSTDEP008()
constraints.version = opts.version
}
const headHandler = router.findRoute('HEAD', opts.url, constraints)
const hasHEADHandler = headHandler !== null
// remove the head route created by fastify
if (isHeadRoute && hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
router.off('HEAD', opts.url, constraints)
}
try {
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
} catch (error) {
@@ -377,13 +369,16 @@ function buildRouting (options) {
this.after((notHandledErr, done) => {
// Send context async
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
context.errorHandler = opts.errorHandler
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
: this[kErrorHandler]
context._parserOptions.limit = opts.bodyLimit || null
context.logLevel = opts.logLevel
context.logSerializers = opts.logSerializers
context.attachValidation = opts.attachValidation
context[kReplySerializerDefault] = this[kReplySerializerDefault]
context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
context.schemaErrorFormatter =
opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
// Run hooks and more
avvio.once('preReady', () => {
@@ -407,15 +402,24 @@ function buildRouting (options) {
fourOhFour.setContext(this, context)
if (opts.schema) {
context.schema = normalizeSchema(opts, context.schema, this.initialConfig)
context.schema = normalizeSchema(context.schema, this.initialConfig)
const schemaController = this[kSchemaController]
if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
const hasValidationSchema = opts.schema.body ||
opts.schema.headers ||
opts.schema.querystring ||
opts.schema.params
if (!opts.validatorCompiler && hasValidationSchema) {
schemaController.setupValidator(this[kOptions])
}
try {
const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
const isCustom = typeof opts?.validatorCompiler === 'function' ||
schemaController.isCustomValidatorCompiler
compileSchemasForValidation(
context,
opts.validatorCompiler || schemaController.validatorCompiler,
isCustom
)
} catch (error) {
throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
}
@@ -440,8 +444,6 @@ function buildRouting (options) {
if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) {
const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
} else if (hasHEADHandler && exposeHeadRoute) {
FSTDEP007()
}
}
}
@@ -458,20 +460,8 @@ function buildRouting (options) {
loggerOpts.serializers = context.logSerializers
}
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
childLogger[kDisableRequestLogging] = disableRequestLogging
// TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
if (!validateHTTPVersion(req.httpVersion)) {
childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version')
const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}'
const headers = {
'Content-Type': 'application/json',
'Content-Length': message.length
}
res.writeHead(505, headers)
res.end(message)
return
}
// Set initial value; will be re-evaluated after FastifyRequest is constructed if it's a function
childLogger[kDisableRequestLogging] = disableRequestLoggingFn ? false : disableRequestLogging
if (closing === true) {
/* istanbul ignore next mac, windows */
@@ -515,7 +505,15 @@ function buildRouting (options) {
const request = new context.Request(id, params, req, query, childLogger, context)
const reply = new context.Reply(res, request, childLogger)
if (disableRequestLogging === false) {
// Evaluate disableRequestLogging after FastifyRequest is constructed
// so the caller has access to decorations and customizations
const resolvedDisableRequestLogging = disableRequestLoggingFn
? disableRequestLoggingFn(request)
: disableRequestLogging
childLogger[kDisableRequestLogging] = resolvedDisableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
@@ -577,7 +575,8 @@ function normalizeAndValidateMethod (method) {
throw new FST_ERR_ROUTE_METHOD_INVALID()
}
method = method.toUpperCase()
if (supportedMethods.indexOf(method) === -1) {
if (!this[kSupportedHTTPMethods].bodyless.has(method) &&
!this[kSupportedHTTPMethods].bodywith.has(method)) {
throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
}
@@ -585,7 +584,7 @@ function normalizeAndValidateMethod (method) {
}
function validateSchemaBodyOption (method, path, schema) {
if ((method === 'GET' || method === 'HEAD') && schema && schema.body) {
if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) {
throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
}
}
@@ -608,12 +607,30 @@ function runPreParsing (err, request, reply) {
request[kRequestPayloadStream] = request.raw
if (request[kRouteContext].preParsing !== null) {
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest)
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server))
} else {
handleRequest(null, request, reply)
handleRequest.call(request.server, null, request, reply)
}
}
function buildRouterOptions (options, defaultOptions) {
const routerOptions = options.routerOptions || Object.create(null)
const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
if (usedDeprecatedOptions.length > 0) {
FSTDEP022(usedDeprecatedOptions.join(', '))
}
for (const key of routerKeys) {
if (!Object.hasOwn(routerOptions, key)) {
routerOptions[key] = options[key] ?? defaultOptions[key]
}
}
return routerOptions
}
/**
* Used within the route handler as a `net.Socket.close` event handler.
* The purpose is to remove a socket from the tracked sockets collection when
@@ -625,4 +642,4 @@ function removeTrackedSocket () {
function noop () { }
module.exports = { buildRouting, validateBodyLimitOption }
module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions }

View File

@@ -1,8 +1,6 @@
'use strict'
const { buildSchemas } = require('./schemas')
const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
const ValidatorSelector = require('@fastify/ajv-compiler')
/**
* Called at every fastify context that is being created.
@@ -21,9 +19,11 @@ function buildSchemaController (parentSchemaCtrl, opts) {
}, opts?.compilersFactory)
if (!compilersFactory.buildValidator) {
const ValidatorSelector = require('@fastify/ajv-compiler')
compilersFactory.buildValidator = ValidatorSelector()
}
if (!compilersFactory.buildSerializer) {
const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
compilersFactory.buildSerializer = SerializerSelector()
}

View File

@@ -4,9 +4,6 @@ const fastClone = require('rfdc')({ circles: false, proto: true })
const { kSchemaVisited, kSchemaResponse } = require('./symbols')
const kFluentSchema = Symbol.for('fluent-schema-object')
const {
FSTDEP022
} = require('./warnings')
const {
FST_ERR_SCH_MISSING_ID,
FST_ERR_SCH_ALREADY_PRESENT,
@@ -57,7 +54,7 @@ function isCustomSchemaPrototype (schema) {
return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
}
function normalizeSchema (opts, routeSchemas, serverOptions) {
function normalizeSchema (routeSchemas, serverOptions) {
if (routeSchemas[kSchemaVisited]) {
return routeSchemas
}
@@ -85,11 +82,9 @@ function normalizeSchema (opts, routeSchemas, serverOptions) {
if (!contentSchema) {
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)
}
routeSchemas.body.content[contentType].schema = getSchemaAnyway(opts.url, contentSchema, serverOptions.jsonShorthand)
}
continue
}
routeSchemas[key] = getSchemaAnyway(opts.url, schema, serverOptions.jsonShorthand)
}
}
@@ -102,25 +97,15 @@ function normalizeSchema (opts, routeSchemas, serverOptions) {
const contentProperty = routeSchemas.response[code].content
let hasContentMultipleContentTypes = false
if (contentProperty) {
const keys = Object.keys(contentProperty)
for (let i = 0; i < keys.length; i++) {
const mediaName = keys[i]
if (!contentProperty[mediaName].schema) {
if (keys.length === 1) { break }
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
}
routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(opts.url, contentProperty[mediaName].schema, serverOptions.jsonShorthand)
if (i === keys.length - 1) {
hasContentMultipleContentTypes = true
}
}
}
if (!hasContentMultipleContentTypes) {
routeSchemas.response[code] = getSchemaAnyway(opts.url, routeSchemas.response[code], serverOptions.jsonShorthand)
}
}
}
@@ -145,18 +130,6 @@ function generateFluentSchema (schema) {
}
}
function getSchemaAnyway (url, schema, jsonShorthand) {
if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
if (!schema.type && !schema.properties) {
FSTDEP022(url)
return {
type: 'object',
properties: schema
}
}
return schema
}
/**
* Search for the right JSON schema compiled function in the request context
* setup by the route configuration `schema.response`.
@@ -180,6 +153,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef[statusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[statusCode]['*/*']) {
return responseSchemaDef[statusCode]['*/*']
}
return false
}
return responseSchemaDef[statusCode]
@@ -192,6 +170,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef[fallbackStatusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
return responseSchemaDef[fallbackStatusCode]['*/*']
}
return false
}
@@ -204,6 +187,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef.default[mediaName]
}
// fallback to match all media-type
if (responseSchemaDef.default['*/*']) {
return responseSchemaDef.default['*/*']
}
return false
}

View File

@@ -2,20 +2,23 @@
const http = require('node:http')
const https = require('node:https')
const http2 = require('node:http2')
const dns = require('node:dns')
const os = require('node:os')
const { FSTDEP011 } = require('./warnings')
const { kState, kOptions, kServerBindings } = require('./symbols')
const { kState, kOptions, kServerBindings, kHttp2ServerSessions } = require('./symbols')
const { FSTWRN003 } = require('./warnings')
const { onListenHookRunner } = require('./hooks')
const {
FST_ERR_HTTP2_INVALID_VERSION,
FST_ERR_REOPENED_CLOSE_SERVER,
FST_ERR_REOPENED_SERVER,
FST_ERR_LISTEN_OPTIONS_INVALID
FST_ERR_LISTEN_OPTIONS_INVALID,
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE
} = require('./errors')
const noopSet = require('./noop-set')
const PonyPromise = require('./promise')
module.exports.createServer = createServer
module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion
function defaultResolveServerListeningText (address) {
return `Server listening at ${address}`
@@ -25,27 +28,15 @@ function createServer (options, httpHandler) {
const server = getServerInstance(options, httpHandler)
// `this` is the Fastify object
function listen (listenOptions, ...args) {
let cb = args.slice(-1).pop()
// When the variadic signature deprecation is complete, the function
// declaration should become:
// function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined)
// Upon doing so, the `normalizeListenArgs` function is no longer needed,
// and all of this preamble to feed it correctly also no longer needed.
const firstArgType = Object.prototype.toString.call(arguments[0])
if (arguments.length === 0) {
listenOptions = normalizeListenArgs([])
} else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) {
FSTDEP011()
listenOptions = normalizeListenArgs(Array.from(arguments))
cb = listenOptions.cb
} else if (args.length > 1) {
// `.listen(obj, a, ..., n, callback )`
FSTDEP011()
// Deal with `.listen(port, host, backlog, [cb])`
const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost']
Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args]))
} else {
function listen (
listenOptions = { port: 0, host: 'localhost' },
cb = undefined
) {
if (typeof cb === 'function') {
if (cb.constructor.name === 'AsyncFunction') {
FSTWRN003('listen method')
}
listenOptions.cb = cb
}
if (listenOptions.signal) {
@@ -53,10 +44,14 @@ function createServer (options, httpHandler) {
throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal')
}
if (listenOptions.signal.aborted) {
this.close()
// copy the current signal state
this[kState].aborted = listenOptions.signal.aborted
if (this[kState].aborted) {
return this.close()
} else {
const onAborted = () => {
this[kState].aborted = true
this.close()
}
listenOptions.signal.addEventListener('abort', onAborted, { once: true })
@@ -72,7 +67,7 @@ function createServer (options, httpHandler) {
} else {
host = listenOptions.host
}
if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false ||
if (!Object.hasOwn(listenOptions, 'host') ||
listenOptions.host == null) {
listenOptions.host = host
}
@@ -110,27 +105,47 @@ function createServer (options, httpHandler) {
if (cb === undefined) {
const listening = listenPromise.call(this, server, listenOptions)
/* istanbul ignore else */
return listening.then(address => {
return new Promise((resolve, reject) => {
if (host === 'localhost') {
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
this[kState].listening = true
resolve(address)
onListenHookRunner(this)
})
} else {
const { promise, resolve } = PonyPromise.withResolvers()
if (host === 'localhost') {
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
this[kState].listening = true
resolve(address)
onListenHookRunner(this)
}
})
})
} else {
resolve(address)
onListenHookRunner(this)
}
return promise
})
}
this.ready(listenCallback.call(this, server, listenOptions))
}
return { server, listen }
const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function'
let forceCloseConnections = options.forceCloseConnections
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
} else if (typeof forceCloseConnections !== 'boolean') {
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
}
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
return {
server,
listen,
forceCloseConnections,
serverHasCloseAllConnections,
serverHasCloseHttp2Sessions,
keepAliveConnections
}
}
function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) {
@@ -139,7 +154,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
// let's check if we need to bind additional addresses
dns.lookup(listenOptions.host, { all: true }, (dnsErr, addresses) => {
if (dnsErr) {
if (dnsErr || this[kState].aborted) {
// not blocking the main server listening
// this.log.warn('dns.lookup error:', dnsErr)
onListen()
@@ -161,7 +176,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
cb: (_ignoreErr) => {
bound++
/* istanbul ignore next: the else won't be taken unless listening fails */
if (!_ignoreErr) {
this[kServerBindings].push(secondaryServer)
}
@@ -175,20 +189,18 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
const secondaryServer = getServerInstance(serverOpts, httpHandler)
const closeSecondary = () => {
// To avoid fall into situations where the close of the
// To avoid falling into situations where the close of the
// secondary server is triggered before the preClose hook
// is done running, we better wait until the main server
// is closed.
// is done running, we better wait until the main server is closed.
// No new TCP connections are accepted
// We swallow any error from the secondary
// server
// We swallow any error from the secondary server
secondaryServer.close(() => {})
if (serverOpts.forceCloseConnections === 'idle') {
// Not needed in Node 19
secondaryServer.closeIdleConnections()
} else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) {
if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) {
secondaryServer.closeAllConnections()
}
if (typeof secondaryServer.closeHttp2Sessions === 'function') {
secondaryServer.closeHttp2Sessions()
}
}
secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
@@ -210,7 +222,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
// to the secondary servers. It is valid only when the user is
// listening on localhost
const originUnref = mainServer.unref
/* c8 ignore next 4 */
mainServer.unref = function () {
originUnref.call(mainServer)
mainServer.emit('unref')
@@ -223,7 +234,11 @@ function listenCallback (server, listenOptions) {
server.removeListener('error', wrap)
server.removeListener('listening', wrap)
if (!err) {
const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)
const address = logServerAddress.call(
this,
server,
listenOptions.listenTextResolver || defaultResolveServerListeningText
)
listenOptions.cb(null, address)
} else {
this[kState].listening = false
@@ -236,7 +251,8 @@ function listenCallback (server, listenOptions) {
if (this[kState].listening && this[kState].closing) {
return listenOptions.cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null)
} else if (this[kState].listening) {
}
if (this[kState].listening) {
return listenOptions.cb(new FST_ERR_REOPENED_SERVER(), null)
}
@@ -252,196 +268,174 @@ function listenCallback (server, listenOptions) {
function listenPromise (server, listenOptions) {
if (this[kState].listening && this[kState].closing) {
return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER())
} else if (this[kState].listening) {
}
if (this[kState].listening) {
return Promise.reject(new FST_ERR_REOPENED_SERVER())
}
return this.ready().then(() => {
let errEventHandler
let listeningEventHandler
// skip listen when aborted during ready
if (this[kState].aborted) return
const { promise, resolve, reject } = PonyPromise.withResolvers()
const errEventHandler = (err) => {
cleanup()
this[kState].listening = false
reject(err)
}
const listeningEventHandler = () => {
cleanup()
this[kState].listening = true
resolve(logServerAddress.call(
this,
server,
listenOptions.listenTextResolver || defaultResolveServerListeningText
))
}
function cleanup () {
server.removeListener('error', errEventHandler)
server.removeListener('listening', listeningEventHandler)
}
const errEvent = new Promise((resolve, reject) => {
errEventHandler = (err) => {
cleanup()
this[kState].listening = false
reject(err)
}
server.once('error', errEventHandler)
})
const listeningEvent = new Promise((resolve, reject) => {
listeningEventHandler = () => {
cleanup()
this[kState].listening = true
resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText))
}
server.once('listening', listeningEventHandler)
})
server.once('error', errEventHandler)
server.once('listening', listeningEventHandler)
server.listen(listenOptions)
return Promise.race([
errEvent, // e.g invalid port range error is always emitted before the server listening
listeningEvent
])
return promise
})
}
/**
* Creates a function that, based upon initial configuration, will
* verify that every incoming request conforms to allowed
* HTTP versions for the Fastify instance, e.g. a Fastify HTTP/1.1
* server will not serve HTTP/2 requests upon the result of the
* verification function.
*
* @param {object} options fastify option
* @param {function} [options.serverFactory] If present, the
* validator function will skip all checks.
* @param {boolean} [options.http2 = false] If true, the validator
* function will allow HTTP/2 requests.
* @param {object} [options.https = null] https server options
* @param {boolean} [options.https.allowHTTP1] If true and use
* with options.http2 the validator function will allow HTTP/1
* request to http2 server.
*
* @returns {function} HTTP version validator function.
*/
function compileValidateHTTPVersion (options) {
let bypass = false
// key-value map to store valid http version
const map = new Map()
if (options.serverFactory) {
// When serverFactory is passed, we cannot identify how to check http version reliably
// So, we should skip the http version check
bypass = true
}
if (options.http2) {
// HTTP2 must serve HTTP/2.0
map.set('2.0', true)
if (options.https && options.https.allowHTTP1 === true) {
// HTTP2 with HTTPS.allowHTTP1 allow fallback to HTTP/1.1 and HTTP/1.0
map.set('1.1', true)
map.set('1.0', true)
}
} else {
// HTTP must server HTTP/1.1 and HTTP/1.0
map.set('1.1', true)
map.set('1.0', true)
}
// The compiled function here placed in one of the hottest path inside fastify
// the implementation here must be as performant as possible
return function validateHTTPVersion (httpVersion) {
// `bypass` skip the check when custom server factory provided
// `httpVersion in obj` check for the valid http version we should support
return bypass || map.has(httpVersion)
}
}
function getServerInstance (options, httpHandler) {
let server = null
// node@20 do not accepts options as boolean
// we need to provide proper https option
const httpsOptions = options.https === true ? {} : options.https
if (options.serverFactory) {
server = options.serverFactory(httpHandler, options)
} else if (options.http2) {
if (typeof httpsOptions === 'object') {
server = http2().createSecureServer(httpsOptions, httpHandler)
} else {
server = http2().createServer(httpHandler)
}
server.on('session', sessionTimeout(options.http2SessionTimeout))
} else {
// this is http1
if (httpsOptions) {
server = https.createServer(httpsOptions, httpHandler)
} else {
server = http.createServer(options.http, httpHandler)
}
server.keepAliveTimeout = options.keepAliveTimeout
server.requestTimeout = options.requestTimeout
// we treat zero as null
// and null is the default setting from nodejs
// so we do not pass the option to server
if (options.maxRequestsPerSocket > 0) {
server.maxRequestsPerSocket = options.maxRequestsPerSocket
}
// User provided server instance
return options.serverFactory(httpHandler, options)
}
if (!options.serverFactory) {
// We have accepted true as a valid way to init https but node requires an options obj
const httpsOptions = options.https === true ? {} : options.https
if (options.http2) {
const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler)
server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, () => {
session.close()
}))
// This is only needed for Node.js versions < 24.0.0 since Node.js added native
// closeAllSessions() on server.close() support for HTTP/2 servers in v24.0.0
if (options.forceCloseConnections === true) {
server.closeHttp2Sessions = createCloseHttp2SessionsByHttp2Server(server)
}
server.setTimeout(options.connectionTimeout)
return server
}
// HTTP1 server instance
const server = httpsOptions
? https.createServer(httpsOptions, httpHandler)
: http.createServer(options.http, httpHandler)
server.keepAliveTimeout = options.keepAliveTimeout
server.requestTimeout = options.requestTimeout
server.setTimeout(options.connectionTimeout)
// We treat zero as null(node default) so we do not pass zero to the server instance
if (options.maxRequestsPerSocket > 0) {
server.maxRequestsPerSocket = options.maxRequestsPerSocket
}
return server
}
function normalizeListenArgs (args) {
if (args.length === 0) {
return { port: 0, host: 'localhost' }
/**
* Inspects the provided `server.address` object and returns a
* normalized list of IP address strings. Normalization in this
* case refers to mapping wildcard `0.0.0.0` to the list of IP
* addresses the wildcard refers to.
*
* @see https://nodejs.org/docs/latest/api/net.html#serveraddress
*
* @param {object} A server address object as described in the
* linked docs.
*
* @returns {string[]}
*/
function getAddresses (address) {
if (address.address === '0.0.0.0') {
return Object.values(os.networkInterfaces()).flatMap((iface) => {
return iface.filter((iface) => iface.family === 'IPv4')
}).sort((iface) => {
/* c8 ignore next 2 */
// Order the interfaces so that internal ones come first
return iface.internal ? -1 : 1
}).map((iface) => { return iface.address })
}
const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined
const options = { cb }
const firstArg = args[0]
const argsLength = args.length
const lastArg = args[argsLength - 1]
if (typeof firstArg === 'string' && isNaN(firstArg)) {
/* Deal with listen (pipe[, backlog]) */
options.path = firstArg
options.backlog = argsLength > 1 ? lastArg : undefined
} else {
/* Deal with listen ([port[, host[, backlog]]]) */
options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg)
// This will listen to what localhost is.
// It can be 127.0.0.1 or ::1, depending on the operating system.
// Fixes https://github.com/fastify/fastify/issues/1022.
options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost'
options.backlog = argsLength >= 3 ? args[2] : undefined
}
return options
}
function normalizePort (firstArg) {
const port = Number(firstArg)
return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0
return [address.address]
}
function logServerAddress (server, listenTextResolver) {
let address = server.address()
const isUnixSocket = typeof address === 'string'
/* istanbul ignore next */
let addresses
const isUnixSocket = typeof server.address() === 'string'
if (!isUnixSocket) {
if (address.address.indexOf(':') === -1) {
address = address.address + ':' + address.port
if (server.address().address.indexOf(':') === -1) {
// IPv4
addresses = getAddresses(server.address()).map((address) => address + ':' + server.address().port)
} else {
address = '[' + address.address + ']:' + address.port
// IPv6
addresses = ['[' + server.address().address + ']:' + server.address().port]
}
addresses = addresses.map((address) => ('http' + (this[kOptions].https ? 's' : '') + '://') + address)
} else {
addresses = [server.address()]
}
for (const address of addresses) {
this.log.info(listenTextResolver(address))
}
return addresses[0]
}
/**
* @param {http2.Http2Server} http2Server
* @returns {() => void}
*/
function createCloseHttp2SessionsByHttp2Server (http2Server) {
/**
* @type {Set<http2.Http2Session>}
*/
http2Server[kHttp2ServerSessions] = new Set()
http2Server.on('session', function (session) {
session.once('connect', function () {
http2Server[kHttp2ServerSessions].add(session)
})
session.once('close', function () {
http2Server[kHttp2ServerSessions].delete(session)
})
session.once('frameError', function (type, code, streamId) {
if (streamId === 0) {
// The stream ID is 0, which means that the error is related to the session itself.
// If the event is not associated with a stream, the Http2Session will be shut down immediately
http2Server[kHttp2ServerSessions].delete(session)
}
})
session.once('goaway', function () {
// The Http2Session instance will be shut down automatically when the 'goaway' event is emitted.
http2Server[kHttp2ServerSessions].delete(session)
})
})
return function closeHttp2Sessions () {
if (http2Server[kHttp2ServerSessions].size === 0) {
return
}
for (const session of http2Server[kHttp2ServerSessions]) {
session.close()
}
}
/* istanbul ignore next */
address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address
const serverListeningText = listenTextResolver(address)
this.log.info(serverListeningText)
return address
}
function http2 () {
try {
return require('node:http2')
} catch (err) {
throw new FST_ERR_HTTP2_INVALID_VERSION()
}
}
function sessionTimeout (timeout) {
return function (session) {
session.setTimeout(timeout, close)
}
}
function close () {
this.close()
}

View File

@@ -5,6 +5,7 @@ const keys = {
kChildren: Symbol('fastify.children'),
kServerBindings: Symbol('fastify.serverBindings'),
kBodyLimit: Symbol('fastify.bodyLimit'),
kSupportedHTTPMethods: Symbol('fastify.acceptedHTTPMethods'),
kRoutePrefix: Symbol('fastify.routePrefix'),
kLogLevel: Symbol('fastify.logLevel'),
kLogSerializers: Symbol('fastify.logSerializers'),
@@ -15,8 +16,8 @@ const keys = {
kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
kPluginNameChain: Symbol('fastify.pluginNameChain'),
kRouteContext: Symbol('fastify.context'),
kPublicRouteContext: Symbol('fastify.routeOptions'),
kGenReqId: Symbol('fastify.genReqId'),
kHttp2ServerSessions: Symbol('fastify.http2ServerSessions'),
// Schema
kSchemaController: Symbol('fastify.schemaController'),
kSchemaHeaders: Symbol('headers-schema'),
@@ -56,6 +57,7 @@ const keys = {
// This symbol is only meant to be used for fastify tests and should not be used for any other purpose
kTestInternals: Symbol('fastify.testInternals'),
kErrorHandler: Symbol('fastify.errorHandler'),
kErrorHandlerAlreadySet: Symbol('fastify.errorHandlerAlreadySet'),
kChildLoggerFactory: Symbol('fastify.childLoggerFactory'),
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),

View File

@@ -7,7 +7,7 @@ const {
kSchemaBody: bodySchema,
kSchemaResponse: responseSchema
} = require('./symbols')
const scChecker = /^[1-5]{1}[0-9]{2}$|^[1-5]xx$|^default$/
const scChecker = /^[1-5](?:\d{2}|xx)$|^default$/
const {
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX
@@ -24,7 +24,7 @@ function compileSchemasForSerialization (context, compile) {
.reduce(function (acc, statusCode) {
const schema = context.schema.response[statusCode]
statusCode = statusCode.toLowerCase()
if (!scChecker.exec(statusCode)) {
if (!scChecker.test(statusCode)) {
throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX()
}
@@ -82,7 +82,7 @@ function compileSchemasForValidation (context, compile, isCustom) {
})
}
context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'headers')) {
} else if (Object.hasOwn(schema, 'headers')) {
FSTWRN001('headers', method, url)
}
@@ -98,28 +98,36 @@ function compileSchemasForValidation (context, compile, isCustom) {
} else {
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
}
} else if (Object.prototype.hasOwnProperty.call(schema, 'body')) {
} else if (Object.hasOwn(schema, 'body')) {
FSTWRN001('body', method, url)
}
if (schema.querystring) {
context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'querystring')) {
} else if (Object.hasOwn(schema, 'querystring')) {
FSTWRN001('querystring', method, url)
}
if (schema.params) {
context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'params')) {
} else if (Object.hasOwn(schema, 'params')) {
FSTWRN001('params', method, url)
}
}
function validateParam (validatorFunction, request, paramName) {
const isUndefined = request[paramName] === undefined
const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName])
let ret
if (ret?.then) {
try {
ret = validatorFunction?.(isUndefined ? null : request[paramName])
} catch (err) {
// If validator throws synchronously, ensure it propagates as an internal error
err.statusCode = 500
return err
}
if (ret && typeof ret.then === 'function') {
return ret
.then((res) => { return answer(res) })
.catch(err => { return err }) // return as simple error (not throw)

View File

@@ -1,96 +1,17 @@
'use strict'
const { createDeprecation, createWarning } = require('process-warning')
const { createWarning } = require('process-warning')
const FSTDEP005 = createDeprecation({
code: 'FSTDEP005',
message: 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.'
})
const FSTDEP006 = createDeprecation({
code: 'FSTDEP006',
message: 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s'
})
const FSTDEP007 = createDeprecation({
code: 'FSTDEP007',
message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.'
})
const FSTDEP008 = createDeprecation({
code: 'FSTDEP008',
message: 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.'
})
const FSTDEP009 = createDeprecation({
code: 'FSTDEP009',
message: 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.'
})
const FSTDEP010 = createDeprecation({
code: 'FSTDEP010',
message: 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.'
})
const FSTDEP011 = createDeprecation({
code: 'FSTDEP011',
message: 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.'
})
const FSTDEP012 = createDeprecation({
code: 'FSTDEP012',
message: 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.'
})
const FSTDEP013 = createDeprecation({
code: 'FSTDEP013',
message: 'Direct return of "trailers" function is deprecated. Please use "callback" or "async-await" for return value. The support of direct return will removed in `fastify@5`.'
})
const FSTDEP014 = createDeprecation({
code: 'FSTDEP014',
message: 'You are trying to set/access the default route. This property is deprecated. Please, use setNotFoundHandler if you want to custom a 404 handler or the wildcard (*) to match all routes.'
})
const FSTDEP015 = createDeprecation({
code: 'FSTDEP015',
message: 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.'
})
const FSTDEP016 = createDeprecation({
code: 'FSTDEP016',
message: 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.'
})
const FSTDEP017 = createDeprecation({
code: 'FSTDEP017',
message: 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.'
})
const FSTDEP018 = createDeprecation({
code: 'FSTDEP018',
message: 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.'
})
const FSTDEP019 = createDeprecation({
code: 'FSTDEP019',
message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.'
})
const FSTDEP020 = createDeprecation({
code: 'FSTDEP020',
message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.'
})
const FSTDEP021 = createDeprecation({
code: 'FSTDEP021',
message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'
})
const FSTDEP022 = createDeprecation({
code: 'FSTDEP021',
message: 'You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5`'
})
/**
* Deprecation codes:
* - FSTWRN001
* - FSTSEC001
* - FSTDEP022
*
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused.
* - FSTDEP022 is used by v5 and MUST NOT be reused.
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused.
*/
const FSTWRN001 = createWarning({
name: 'FastifyWarning',
@@ -99,32 +20,38 @@ const FSTWRN001 = createWarning({
unlimited: true
})
const FSTWRN002 = createWarning({
const FSTWRN003 = createWarning({
name: 'FastifyWarning',
code: 'FSTWRN002',
message: 'The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`',
code: 'FSTWRN003',
message: 'The %s mixes async and callback styles that may lead to unhandled rejections. Please use only one of them.',
unlimited: true
})
const FSTWRN004 = createWarning({
name: 'FastifyWarning',
code: 'FSTWRN004',
message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs.',
unlimited: true
})
const FSTSEC001 = createWarning({
name: 'FastifySecurity',
code: 'FSTSEC001',
message: 'You are using /%s/ Content-Type which may be vulnerable to CORS attack. Please make sure your RegExp start with "^" or include ";?" to proper detection of the essence MIME type.',
unlimited: true
})
const FSTDEP022 = createWarning({
name: 'FastifyWarning',
code: 'FSTDEP022',
message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.',
unlimited: true
})
module.exports = {
FSTDEP005,
FSTDEP006,
FSTDEP007,
FSTDEP008,
FSTDEP009,
FSTDEP010,
FSTDEP011,
FSTDEP012,
FSTDEP013,
FSTDEP014,
FSTDEP015,
FSTDEP016,
FSTDEP017,
FSTDEP018,
FSTDEP019,
FSTDEP020,
FSTDEP021,
FSTDEP022,
FSTWRN001,
FSTWRN002
FSTWRN003,
FSTWRN004,
FSTSEC001,
FSTDEP022
}

84
backend/node_modules/fastify/lib/wrap-thenable.js generated vendored Normal file
View File

@@ -0,0 +1,84 @@
'use strict'
const {
kReplyIsError,
kReplyHijacked
} = require('./symbols')
const { setErrorStatusCode } = require('./error-status')
const diagnostics = require('node:diagnostics_channel')
const channels = diagnostics.tracingChannel('fastify.request.handler')
function wrapThenable (thenable, reply, store) {
if (store) store.async = true
thenable.then(function (payload) {
if (reply[kReplyHijacked] === true) {
return
}
if (store) {
channels.asyncStart.publish(store)
}
try {
// this is for async functions that are using reply.send directly
//
// since wrap-thenable will be called when using reply.send directly
// without actual return. the response can be sent already or
// the request may be terminated during the reply. in this situation,
// it require an extra checking of request.aborted to see whether
// the request is killed by client.
if (payload !== undefined || //
(reply.sent === false && //
reply.raw.headersSent === false &&
reply.request.raw.aborted === false &&
reply.request.socket &&
!reply.request.socket.destroyed
)
) {
// we use a try-catch internally to avoid adding a catch to another
// promise, increase promise perf by 10%
try {
reply.send(payload)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
}
}
} finally {
if (store) {
channels.asyncEnd.publish(store)
}
}
}, function (err) {
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store) // note that error happens before asyncStart
channels.asyncStart.publish(store)
}
try {
if (reply.sent === true) {
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
return
}
reply[kReplyIsError] = true
reply.send(err)
// The following should not happen
/* c8 ignore next 3 */
} catch (err) {
// try-catch allow to re-throw error in error handler for async handler
reply.send(err)
} finally {
if (store) {
channels.asyncEnd.publish(store)
}
}
})
}
module.exports = wrapThenable

View File

@@ -1,50 +0,0 @@
'use strict'
const {
kReplyIsError,
kReplyHijacked
} = require('./symbols')
function wrapThenable (thenable, reply) {
thenable.then(function (payload) {
if (reply[kReplyHijacked] === true) {
return
}
// this is for async functions that are using reply.send directly
//
// since wrap-thenable will be called when using reply.send directly
// without actual return. the response can be sent already or
// the request may be terminated during the reply. in this situation,
// it require an extra checking of request.aborted to see whether
// the request is killed by client.
if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
// we use a try-catch internally to avoid adding a catch to another
// promise, increase promise perf by 10%
try {
reply.send(payload)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
}
}
}, function (err) {
if (reply.sent === true) {
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
return
}
reply[kReplyIsError] = true
// try-catch allow to re-throw error in error handler for async handler
try {
reply.send(err)
// The following should not happen
/* c8 ignore next 3 */
} catch (err) {
reply.send(err)
}
})
}
module.exports = wrapThenable

1
backend/node_modules/fastify/node_modules/.bin/pino generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../pino/bin.js

View File

@@ -1,23 +0,0 @@
name: CI
on:
push:
branches:
- main
- master
- next
- 'v*'
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
jobs:
test:
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
with:
license-check: true
lint: true

View File

@@ -1,2 +0,0 @@
files:
- test/**/*.test.js

View File

@@ -1,62 +0,0 @@
# @fastify/error
![CI](https://github.com/fastify/fastify-error/workflows/CI/badge.svg)
[![NPM version](https://img.shields.io/npm/v/@fastify/error.svg?style=flat)](https://www.npmjs.com/package/@fastify/error)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
A small utility, used by Fastify itself, for generating consistent error objects across your codebase and plugins.
### Install
```
npm i @fastify/error
```
### Usage
The module exports a function that you can use for consistent error objects, it takes 4 parameters:
```
createError(code, message [, statusCode [, Base]])
```
- `code` (`string`, required) - The error code, you can access it later with `error.code`. For consistency, we recommend prefixing plugin error codes with `FST_`
- `message` (`string`, required) - The error message. You can also use interpolated strings for formatting the message.
- `statusCode` (`number`, optional) - The status code that Fastify will use if the error is sent via HTTP.
- `Base` (`ErrorConstructor`, optional) - The base error object that will be used. (eg `TypeError`, `RangeError`)
```js
const createError = require('@fastify/error')
const CustomError = createError('ERROR_CODE', 'Hello')
console.log(new CustomError()) // error.message => 'Hello'
```
How to use an interpolated string:
```js
const createError = require('@fastify/error')
const CustomError = createError('ERROR_CODE', 'Hello %s')
console.log(new CustomError('world')) // error.message => 'Hello world'
```
How to add cause:
```js
const createError = require('@fastify/error')
const CustomError = createError('ERROR_CODE', 'Hello %s')
console.log(new CustomError('world', {cause: new Error('cause')}))
// error.message => 'Hello world'
// error.cause => Error('cause')
```
### TypeScript
It is possible to limit your error constructor with a generic type using TypeScript:
```ts
const CustomError = createError<[string]>('ERROR_CODE', 'Hello %s')
new CustomError('world')
//@ts-expect-error
new CustomError(1)
```
## License
Licensed under [MIT](./LICENSE).

View File

@@ -1,9 +0,0 @@
'use strict'
const benchmark = require('benchmark')
const createError = require('..')
new benchmark.Suite()
.add('create FastifyError', function () { createError('CODE', 'Not available') }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -1,18 +0,0 @@
'use strict'
const benchmark = require('benchmark')
const createError = require('..')
const FastifyError = createError('CODE', 'Not available')
const FastifyError1 = createError('CODE', 'Not %s available')
const FastifyError2 = createError('CODE', 'Not %s available %s')
const cause = new Error('cause')
new benchmark.Suite()
.add('instantiate Error', function () { new Error() }, { minSamples: 100 }) // eslint-disable-line no-new
.add('instantiate FastifyError', function () { new FastifyError() }, { minSamples: 100 }) // eslint-disable-line no-new
.add('instantiate FastifyError arg 1', function () { new FastifyError1('q') }, { minSamples: 100 }) // eslint-disable-line no-new
.add('instantiate FastifyError arg 2', function () { new FastifyError2('qq', 'ss') }, { minSamples: 100 }) // eslint-disable-line no-new
.add('instantiate FastifyError cause', function () { new FastifyError2({ cause }) }, { minSamples: 100 }) // eslint-disable-line no-new
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -1,13 +0,0 @@
'use strict'
const benchmark = require('benchmark')
const createError = require('..')
const FastifyError = createError('CODE', 'Not available')
Error.stackTraceLimit = 0
new benchmark.Suite()
.add('no-stack instantiate Error', function () { new Error() }, { minSamples: 100 }) // eslint-disable-line no-new
.add('no-stack instantiate FastifyError', function () { new FastifyError() }, { minSamples: 100 }) // eslint-disable-line no-new
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -1,11 +0,0 @@
'use strict'
const benchmark = require('benchmark')
const createError = require('..')
const FastifyError = createError('CODE', 'Not available')
new benchmark.Suite()
.add('FastifyError toString', function () { new FastifyError().toString() }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -1,53 +0,0 @@
'use strict'
const { format } = require('node:util')
function toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
function createError (code, message, statusCode = 500, Base = Error) {
if (!code) throw new Error('Fastify error code must not be empty')
if (!message) throw new Error('Fastify error message must not be empty')
code = code.toUpperCase()
!statusCode && (statusCode = undefined)
function FastifyError (...args) {
if (!new.target) {
return new FastifyError(...args)
}
this.code = code
this.name = 'FastifyError'
this.statusCode = statusCode
const lastElement = args.length - 1
if (lastElement !== -1 && args[lastElement] && typeof args[lastElement] === 'object' && 'cause' in args[lastElement]) {
this.cause = args.pop().cause
}
this.message = format(message, ...args)
Error.stackTraceLimit !== 0 && Error.captureStackTrace(this, FastifyError)
}
FastifyError.prototype = Object.create(Base.prototype, {
constructor: {
value: FastifyError,
enumerable: false,
writable: true,
configurable: true
}
})
FastifyError.prototype[Symbol.toStringTag] = 'Error'
FastifyError.prototype.toString = toString
return FastifyError
}
module.exports = createError
module.exports.default = createError
module.exports.createError = createError

View File

@@ -1,45 +0,0 @@
{
"name": "@fastify/error",
"version": "3.4.1",
"description": "A small utility, used by Fastify itself, for generating consistent error objects across your codebase and plugins.",
"main": "index.js",
"type": "commonjs",
"types": "types/index.d.ts",
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"test": "npm run test:unit && npm run test:typescript",
"test:unit": "tap",
"test:typescript": "tsd"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fastify/fastify-error.git"
},
"keywords": [
"fastify",
"error",
"utility",
"plugin"
],
"author": "Tomas Della Vedova",
"license": "MIT",
"bugs": {
"url": "https://github.com/fastify/fastify-error/issues"
},
"homepage": "https://github.com/fastify/fastify-error#readme",
"devDependencies": {
"benchmark": "^2.1.4",
"standard": "^17.0.0",
"tap": "^16.0.0",
"tsd": "^0.29.0"
},
"tsd": {
"compilerOptions": {
"esModuleInterop": true
}
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,184 +0,0 @@
'use strict'
const { test } = require('tap')
const createError = require('..')
test('Create error with zero parameter', t => {
t.plan(6)
const NewError = createError('CODE', 'Not available')
const err = new NewError()
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'Not available')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with 1 parameter', t => {
t.plan(6)
const NewError = createError('CODE', 'hey %s')
const err = new NewError('alice')
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'hey alice')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with 1 parameter set to undefined', t => {
t.plan(1)
const NewError = createError('CODE', 'hey %s')
const err = new NewError(undefined)
t.equal(err.message, 'hey undefined')
})
test('Create error with 2 parameters', (t) => {
t.plan(6)
const NewError = createError('CODE', 'hey %s, I like your %s')
const err = new NewError('alice', 'attitude')
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'hey alice, I like your attitude')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with 2 parameters set to undefined', t => {
t.plan(1)
const NewError = createError('CODE', 'hey %s, I like your %s')
const err = new NewError(undefined, undefined)
t.equal(err.message, 'hey undefined, I like your undefined')
})
test('Create error with 3 parameters', t => {
t.plan(6)
const NewError = createError('CODE', 'hey %s, I like your %s %s')
const err = new NewError('alice', 'attitude', 'see you')
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'hey alice, I like your attitude see you')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with 3 parameters set to undefined', t => {
t.plan(4)
const NewError = createError('CODE', 'hey %s, I like your %s %s')
const err = new NewError(undefined, undefined, undefined)
t.equal(err.message, 'hey undefined, I like your undefined undefined')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with 4 parameters set to undefined', t => {
t.plan(4)
const NewError = createError('CODE', 'hey %s, I like your %s %s and %s')
const err = new NewError(undefined, undefined, undefined, undefined)
t.equal(err.message, 'hey undefined, I like your undefined undefined and undefined')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create error with no statusCode property', t => {
t.plan(6)
const NewError = createError('CODE', 'hey %s', 0)
const err = new NewError('dude')
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'hey dude')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, undefined)
t.ok(err.stack)
})
test('Should throw when error code has no fastify code', t => {
t.plan(1)
t.throws(() => createError(), new Error('Fastify error code must not be empty'))
})
test('Should throw when error code has no message', t => {
t.plan(1)
t.throws(() => createError('code'), new Error('Fastify error message must not be empty'))
})
test('Create error with different base', t => {
t.plan(7)
const NewError = createError('CODE', 'hey %s', 500, TypeError)
const err = new NewError('dude')
t.ok(err instanceof Error)
t.ok(err instanceof TypeError)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'hey dude')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('FastifyError.toString returns code', t => {
t.plan(1)
const NewError = createError('CODE', 'foo')
const err = new NewError()
t.equal(err.toString(), 'FastifyError [CODE]: foo')
})
test('Create the error without the new keyword', t => {
t.plan(6)
const NewError = createError('CODE', 'Not available')
const err = NewError()
t.ok(err instanceof Error)
t.equal(err.name, 'FastifyError')
t.equal(err.message, 'Not available')
t.equal(err.code, 'CODE')
t.equal(err.statusCode, 500)
t.ok(err.stack)
})
test('Create an error with cause', t => {
t.plan(2)
const cause = new Error('HEY')
const NewError = createError('CODE', 'Not available')
const err = NewError({ cause })
t.ok(err instanceof Error)
t.equal(err.cause, cause)
})
test('Create an error with cause and message', t => {
t.plan(2)
const cause = new Error('HEY')
const NewError = createError('CODE', 'Not available: %s')
const err = NewError('foo', { cause })
t.ok(err instanceof Error)
t.equal(err.cause, cause)
})
test('Create an error with last argument null', t => {
t.plan(2)
const cause = new Error('HEY')
const NewError = createError('CODE', 'Not available')
const err = NewError({ cause }, null)
t.ok(err instanceof Error)
t.notOk(err.cause)
})

View File

@@ -1,44 +0,0 @@
declare function createError<C extends string, SC extends number, Arg extends unknown[] = [any?, any?, any?]> (
code: C,
message: string,
statusCode: SC,
Base?: ErrorConstructor
): createError.FastifyErrorConstructor<{ code: C, statusCode: SC }, Arg>
declare function createError<C extends string, Arg extends unknown[] = [any?, any?, any?]> (
code: C,
message: string,
statusCode?: number,
Base?: ErrorConstructor
): createError.FastifyErrorConstructor<{ code: C }, Arg>
declare function createError<Arg extends unknown[] = [any?, any?, any?]> (
code: string,
message: string,
statusCode?: number,
Base?: ErrorConstructor
): createError.FastifyErrorConstructor<{ code: string }, Arg>
type CreateError = typeof createError
declare namespace createError {
export interface FastifyError extends Error {
code: string
name: string
statusCode?: number
}
export interface FastifyErrorConstructor<
E extends { code: string, statusCode?: number } = { code: string, statusCode?: number },
T extends unknown[] = [any?, any?, any?]
> {
new(...arg: T): FastifyError & E
(...arg: T): FastifyError & E
readonly prototype: FastifyError & E
}
export const createError: CreateError
export { createError as default }
}
export = createError

View File

@@ -1,72 +0,0 @@
import createError, { FastifyError, FastifyErrorConstructor } from '..'
import { expectType, expectError } from 'tsd'
const CustomError = createError('ERROR_CODE', 'message')
expectType<FastifyErrorConstructor<{ code: 'ERROR_CODE' }>>(CustomError)
const err = new CustomError()
expectType<FastifyError & { code: 'ERROR_CODE' }>(err)
expectType<'ERROR_CODE'>(err.code)
expectType<string>(err.message)
expectType<number | undefined>(err.statusCode)
const CustomTypedError = createError('OTHER_CODE', 'message', 400)
expectType<FastifyErrorConstructor<{ code: 'OTHER_CODE', statusCode: 400 }>>(CustomTypedError)
const typed = new CustomTypedError()
expectType<FastifyError & { code: 'OTHER_CODE', statusCode: 400 }>(typed)
expectType<'OTHER_CODE'>(typed.code)
expectType<string>(typed.message)
expectType<400>(typed.statusCode)
/* eslint-disable no-new */
const CustomTypedArgError = createError<[string]>('OTHER_CODE', 'expect %s message', 400)
CustomTypedArgError('a')
expectError(CustomTypedArgError('a', 'b'))
expectError(new CustomTypedArgError('a', 'b'))
expectError(CustomTypedArgError(1))
expectError(new CustomTypedArgError(1))
const CustomTypedArgError2 = createError<string, number, [string]>('OTHER_CODE', 'expect %s message', 400)
CustomTypedArgError2('a')
expectError(CustomTypedArgError2('a', 'b'))
expectError(new CustomTypedArgError2('a', 'b'))
expectError(CustomTypedArgError2(1))
expectError(new CustomTypedArgError2(1))
const CustomTypedArgError3 = createError<string, number, [string, string]>('OTHER_CODE', 'expect %s message but got %s', 400)
expectError(CustomTypedArgError3('a'))
CustomTypedArgError3('a', 'b')
new CustomTypedArgError3('a', 'b')
expectError(CustomTypedArgError3(1))
expectError(new CustomTypedArgError3(1))
expectError(new CustomTypedArgError3(1, 2))
expectError(new CustomTypedArgError3('1', 2))
expectError(new CustomTypedArgError3(1, '2'))
const CustomTypedArgError4 = createError<string, number, [string, string]>('OTHER_CODE', 'expect %s message but got %s', 400)
expectError(CustomTypedArgError4('a'))
CustomTypedArgError4('a', 'b')
new CustomTypedArgError4('a', 'b')
expectError(CustomTypedArgError4(1))
expectError(new CustomTypedArgError4(1))
expectError(new CustomTypedArgError4(1, 2))
expectError(new CustomTypedArgError4('1', 2))
expectError(new CustomTypedArgError4(1, '2'))
const CustomTypedArgError5 = createError<[string, string, string, string]>('OTHER_CODE', 'expect %s message but got %s. Please contact %s by emailing to %s', 400)
expectError(CustomTypedArgError5('a'))
expectError(new CustomTypedArgError5('a', 'b'))
expectError(new CustomTypedArgError5('a', 'b', 'c'))
CustomTypedArgError5('a', 'b', 'c', 'd')
expectError(new CustomTypedArgError5('a', 'b', 'c', 'd', 'e'))
const CustomTypedArgError6 = createError<string, number, [string, string, string, string]>('OTHER_CODE', 'expect %s message but got %s. Please contact %s by emailing to %s', 400)
expectError(CustomTypedArgError6('a'))
expectError(new CustomTypedArgError6('a', 'b'))
expectError(new CustomTypedArgError6('a', 'b', 'c'))
CustomTypedArgError6('a', 'b', 'c', 'd')
expectError(new CustomTypedArgError6('a', 'b', 'c', 'd', 'e'))
const CustomErrorWithErrorConstructor = createError('ERROR_CODE', 'message', 500, TypeError)
expectType<FastifyErrorConstructor<{ code: 'ERROR_CODE', statusCode: 500 }>>(CustomErrorWithErrorConstructor)
CustomErrorWithErrorConstructor({cause: new Error('Error')})

Some files were not shown because too many files have changed in this diff Show More