diff --git a/.borp.yaml b/.borp.yaml
new file mode 100644
index 00000000000..8d6f28179b5
--- /dev/null
+++ b/.borp.yaml
@@ -0,0 +1,3 @@
+files:
+ - 'test/**/*.test.js'
+ - 'test/**/*.test.mjs'
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index 7c7b9ae4c12..00000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "root": true,
- "extends": "standard"
-}
diff --git a/.gitattributes b/.gitattributes
index cad1c32e3de..a0e7df931f9 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,5 +1,2 @@
-# Set the default behavior, in case people don't have core.autocrlf set
-* text=auto
-
-# Require Unix line endings
-* text eol=lf
+# Set default behavior to automatically convert line endings
+* text=auto eol=lf
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index dfa7fa6cba8..871f58a50e7 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -2,12 +2,47 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
+ commit-message:
+ # Prefix all commit messages with "chore: "
+ prefix: "chore"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/"
+ commit-message:
+ # Prefix all commit messages with "chore: "
+ prefix: "chore"
schedule:
- interval: "weekly"
+ interval: "monthly"
open-pull-requests-limit: 10
+ groups:
+ # Production dependencies without breaking changes
+ dependencies:
+ dependency-type: "production"
+ update-types:
+ - "minor"
+ - "patch"
+ # Production dependencies with breaking changes
+ dependencies-major:
+ dependency-type: "production"
+ update-types:
+ - "major"
+ # ESLint related dependencies
+ dev-dependencies-eslint:
+ patterns:
+ - "eslint"
+ - "neostandard"
+ - "@stylistic/*"
+ # TypeScript related dependencies
+ dev-dependencies-typescript:
+ patterns:
+ - "@types/*"
+ - "tsd"
+ - "typescript"
+ # Ajv related dependencies
+ dev-dependencies-ajv:
+ patterns:
+ - "ajv"
+ - "ajv-*"
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 71d7124fbb9..701f3ba18ee 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,10 +1,16 @@
# PRs that only touch the docs folder
documentation:
-- all: ["docs/**/*"]
+- changed-files:
+ - any-glob-to-any-file: docs/**
+
+"github actions":
+- changed-files:
+ - any-glob-to-any-file: ".github/workflows/*"
# PRs that only touch type files
typescript:
-- all: ["**/*[.|-]d.ts"]
+- changed-files:
+ - any-glob-to-any-file: "**/*[.|-]d.ts"
plugin:
- all: ["docs/Guides/Ecosystem.md"]
diff --git a/.github/scripts/lint-ecosystem.js b/.github/scripts/lint-ecosystem.js
index f81546e9b77..217839a2d3f 100644
--- a/.github/scripts/lint-ecosystem.js
+++ b/.github/scripts/lint-ecosystem.js
@@ -1,59 +1,140 @@
'use strict'
-const path = require('path')
-const fs = require('fs')
-const readline = require('readline')
+const path = require('node:path')
+const fs = require('node:fs')
+const readline = require('node:readline')
-const ecosystemDocFile = path.join(__dirname, '..', '..', 'docs', 'Guides', 'Ecosystem.md')
+const basePathEcosystemDocFile = path.join('docs', 'Guides', 'Ecosystem.md')
+const ecosystemDocFile = path.join(__dirname, '..', '..', basePathEcosystemDocFile)
+const failureTypes = {
+ improperFormat: 'improperFormat',
+ outOfOrderItem: 'outOfOrderItem'
+}
module.exports = async function ({ core }) {
+ const results = await runCheck()
+ await handleResults({ core }, results)
+}
+
+async function runCheck () {
const stream = await fs.createReadStream(ecosystemDocFile)
const rl = readline.createInterface({
input: stream,
crlfDelay: Infinity
- });
+ })
- const moduleNameRegex = /^\- \[\`(.+)\`\]/
- let hasOutOfOrderItem = false
+ const failures = []
+ const successes = []
+ const moduleNameRegex = /^- \[`(.+)`\]/
let lineNumber = 0
- let inCommmunitySection = false
let modules = []
+ let grouping = 'core'
for await (const line of rl) {
lineNumber += 1
if (line.startsWith('#### [Community]')) {
- inCommmunitySection = true
+ grouping = 'community'
+ modules = []
}
+
if (line.startsWith('#### [Community Tools]')) {
- inCommmunitySection = false
+ grouping = 'community-tools'
+ modules = []
}
- if (inCommmunitySection === false) {
+
+ if (line.startsWith('- [') !== true) {
continue
}
- if (line.startsWith('- [`') !== true) {
+ const moduleNameTest = moduleNameRegex.exec(line)
+
+ if (moduleNameTest === null) {
+ failures.push({
+ lineNumber,
+ grouping,
+ moduleName: 'unknown',
+ type: failureTypes.improperFormat
+ })
continue
}
- const moduleName = moduleNameRegex.exec(line)[1]
+ const moduleName = moduleNameTest[1]
if (modules.length > 0) {
if (compare(moduleName, modules.at(-1)) > 0) {
- core.error(`line ${lineNumber}: ${moduleName} not listed in alphabetical order`)
- hasOutOfOrderItem = true
+ failures.push({
+ lineNumber,
+ moduleName,
+ grouping,
+ type: failureTypes.outOfOrderItem
+ })
+ } else {
+ successes.push({ moduleName, lineNumber, grouping })
}
+ } else {
+ // We have to push the first item found or we are missing items from the list
+ successes.push({ moduleName, lineNumber, grouping })
}
modules.push(moduleName)
}
- if (hasOutOfOrderItem === true) {
- core.setFailed('Some ecosystem modules are not in alphabetical order.')
+ return { failures, successes }
+}
+
+async function handleResults (scriptLibs, results) {
+ const { core } = scriptLibs
+ const { failures, successes } = results
+ const isError = !!failures.length
+
+ await core.summary
+ .addHeading(isError ? `❌ Ecosystem.md Lint (${failures.length} error${failures.length === 1 ? '' : 's'})` : '✅ Ecosystem Lint (no errors found)')
+ .addTable([
+ [
+ { data: 'Status', header: true },
+ { data: 'Section', header: true },
+ { data: 'Module', header: true },
+ { data: 'Details', header: true }],
+ ...failures.map((failure) => [
+ '❌',
+ failure.grouping,
+ failure.moduleName,
+ `Line Number: ${failure.lineNumber.toString()} - ${failure.type}`
+ ]),
+ ...successes.map((success) => [
+ '✅',
+ success.grouping,
+ success.moduleName,
+ '-'
+ ])
+ ])
+ .write()
+
+ if (isError) {
+ failures.forEach((failure) => {
+ if (failure.type === failureTypes.improperFormat) {
+ core.error('The module name should be enclosed with backticks', {
+ title: 'Improper format',
+ file: basePathEcosystemDocFile,
+ startLine: failure.lineNumber
+ })
+ } else if (failure.type === failureTypes.outOfOrderItem) {
+ core.error(`${failure.moduleName} not listed in alphabetical order`, {
+ title: 'Out of Order',
+ file: basePathEcosystemDocFile,
+ startLine: failure.lineNumber
+ })
+ } else {
+ core.error('Unknown error')
+ }
+ })
+
+ core.setFailed('Failed when linting Ecosystem.md')
}
}
-function compare(current, previous) {
+function compare (current, previous) {
return previous.localeCompare(
current,
'en',
- {sensitivity: 'base'}
+ { sensitivity: 'base' }
)
}
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index 5130bd817b1..00000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 15
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 7
-# Issues with these labels will never be considered stale
-exemptLabels:
- - "discussion"
- - "feature request"
- - "bug"
- - "help wanted"
- - "plugin suggestion"
- - "good first issue"
- - "never stale"
-# Label to use when marking an issue as stale
-staleLabel: stale
-# Comment to post when marking an issue as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs. Thank you
- for your contributions.
-# Comment to post when closing a stale issue. Set to `false` to disable
-closeComment: false
diff --git a/.github/tests_checker.yml b/.github/tests_checker.yml
deleted file mode 100644
index 7d96d55bf97..00000000000
--- a/.github/tests_checker.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-comment: |
- Hello! Thank you for contributing!
- It appears that you have changed the framework code, but the tests that verify your change are missing. Could you please add them?
-
-fileExtensions:
- - '.ts'
- - '.js'
-
-testDir: 'test'
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 4621cf7e510..52b66aab703 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -6,12 +6,14 @@ on:
- labeled
permissions:
- pull-requests: write
- contents: write
+ contents: read
jobs:
backport:
runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
if: >
github.event.pull_request.merged
&& (
@@ -24,6 +26,6 @@ jobs:
name: Backport
steps:
- name: Backport
- uses: tibdex/backport@v2
+ uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
deleted file mode 100644
index d80490baeb6..00000000000
--- a/.github/workflows/benchmark.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-name: Benchmark
-
-on:
- pull_request_target:
- types: [labeled]
-
-jobs:
- benchmark:
- if: ${{ github.event.label.name == 'benchmark' }}
- runs-on: ubuntu-latest
- permissions:
- contents: read
- outputs:
- PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }}
- PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }}
- PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }}
- MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }}
- MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }}
- MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }}
- strategy:
- matrix:
- node-version: [14, 16, 18]
- steps:
- - uses: actions/checkout@v3
- with:
- persist-credentials: false
- ref: ${{github.event.pull_request.head.sha}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
-
- - uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: Install
- run: |
- npm install --only=production --ignore-scripts
-
- - name: Run benchmark
- id: benchmark-pr
- run: |
- npm run --silent benchmark > ./bench-result.md
- result=$(awk '/requests in/' ./bench-result.md)
- echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result"
-
- # main benchmark
- - uses: actions/checkout@v3
- with:
- persist-credentials: false
- ref: 'main'
-
- - name: Install
- run: |
- npm install --only=production --ignore-scripts
-
- - name: Run benchmark
- id: benchmark-main
- run: |
- npm run --silent benchmark > ./bench-result.md
- result=$(awk '/requests in/' ./bench-result.md)
- echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result"
-
- output-benchmark:
- needs: [benchmark]
- runs-on: ubuntu-latest
- permissions:
- pull-requests: write
- steps:
- - name: Comment PR
- uses: thollander/actions-comment-pull-request@v1
- with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- message: |
- **Node**: 14
- **PR**: ${{ needs.benchmark.outputs.PR-BENCH-14 }}
- **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-14 }}
-
- ---
-
- **Node**: 16
- **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }}
- **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }}
-
- ---
-
- **Node**: 18
- **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }}
- **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }}
-
- - uses: actions-ecosystem/action-remove-labels@v1
- with:
- labels: |
- benchmark
- github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ci-alternative-runtime.yml b/.github/workflows/ci-alternative-runtime.yml
new file mode 100644
index 00000000000..9100941f37a
--- /dev/null
+++ b/.github/workflows/ci-alternative-runtime.yml
@@ -0,0 +1,111 @@
+name: ci Alternative Runtimes
+
+on:
+ push:
+ branches:
+ - main
+ - next
+ - 'v*'
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ test-unit:
+ runs-on: ${{ matrix.os }}
+ continue-on-error: true
+ permissions:
+ contents: read
+ strategy:
+ matrix:
+ node-version: [20]
+ os: [macos-latest, ubuntu-latest, windows-latest]
+ include:
+ - runtime: nsolid
+ node-version: 20
+ nsolid-version: 5
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1
+ if: ${{ matrix.runtime == 'nsolid' }}
+ with:
+ node-version: ${{ matrix.node-version }}
+ nsolid-version: ${{ matrix.nsolid-version }}
+
+ - name: Install
+ run: |
+ npm install --ignore-scripts
+
+ - name: Run tests
+ run: |
+ npm run unit
+
+ test-typescript:
+ runs-on: 'ubuntu-latest'
+ permissions:
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1
+ with:
+ node-version: 20
+ nsolid-version: 5
+
+ - name: Install
+ run: |
+ npm install --ignore-scripts
+
+ - name: Run typescript tests
+ run: |
+ npm run test:typescript
+ env:
+ NODE_OPTIONS: no-network-family-autoselection
+
+ package:
+ needs:
+ - test-typescript
+ - test-unit
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1
+ with:
+ nsolid-version: 5
+ - name: install fastify
+ run: |
+ npm install --ignore-scripts
+ - name: install webpack stack
+ run: |
+ cd test/bundler/webpack && npm install
+ - name: Test webpack bundle
+ run: |
+ cd test/bundler/webpack && npm run test
+ - name: install esbuild stack
+ run: |
+ cd test/bundler/esbuild && npm install
+ - name: Test esbuild bundle
+ run: |
+ cd test/bundler/esbuild && npm run test
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cd7afc16a59..5f13d0c0a81 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,10 @@ name: ci
on:
push:
+ branches:
+ - main
+ - next
+ - '*.x'
paths-ignore:
- 'docs/**'
- '*.md'
@@ -9,12 +13,21 @@ on:
paths-ignore:
- 'docs/**'
- '*.md'
+ workflow_dispatch:
+ inputs:
+ nodejs-version:
+ description: 'Node.js version to use (e.g., 24.0.0-rc.1)'
+ required: true
+ type: string
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
- group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
+ group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
+permissions:
+ contents: read
+
jobs:
dependency-review:
name: Dependency Review
@@ -24,26 +37,56 @@ jobs:
contents: read
steps:
- name: Check out repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
persist-credentials: false
- name: Dependency review
- uses: actions/dependency-review-action@v2
+ uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
- linter:
+ check-licenses:
+ name: Check licenses
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: 'lts/*'
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
+
+ - name: Install
+ run: |
+ npm install --ignore-scripts
+
+ - name: Check licenses
+ run: |
+ npx license-checker --production --summary --onlyAllow="0BSD;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;MIT;"
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Use Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 'lts/*'
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- name: Install
run: |
@@ -54,101 +97,205 @@ jobs:
npm run lint
coverage-nix:
+ needs:
+ - lint
permissions:
contents: read
uses: ./.github/workflows/coverage-nix.yml
+
coverage-win:
+ needs:
+ - lint
permissions:
contents: read
uses: ./.github/workflows/coverage-win.yml
- test:
- needs: [linter, coverage-nix, coverage-win]
+ test-unit:
+ needs:
+ - lint
+ - coverage-nix
+ - coverage-win
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
- node-version: [14, 16, 18]
+ node-version: [20, 22, 24]
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- - uses: actions/cache@v3
- id: check-cache
+ - name: Install
+ run: |
+ npm install --ignore-scripts
+
+ - name: Run tests
+ run: |
+ npm run unit
+
+ # Useful for testing Release Candidates of Node.js
+ test-unit-custom:
+ if: github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Use Custom Node.js Version
+ uses: actions/setup-node@v6
with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }}
- restore-keys: |
- ${{ runner.os }}-node-${{ matrix.node-version }}-
+ node-version: ${{ github.event.inputs.nodejs-version }}
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- - name: Check licenses
+ - name: Run tests
run: |
- npm run license-checker
+ npm run unit
- - name: Run tests
+ test-typescript:
+ needs:
+ - lint
+ - coverage-nix
+ - coverage-win
+ runs-on: 'ubuntu-latest'
+ permissions:
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
+
+ - name: Install
run: |
- npm run test:ci
+ npm install --ignore-scripts
- automerge:
- if: >
- github.event_name == 'pull_request' &&
- github.event.pull_request.user.login == 'dependabot[bot]'
- needs: test
+ - name: Run typescript tests
+ run: |
+ npm run test:typescript
+ env:
+ NODE_OPTIONS: no-network-family-autoselection
+
+ test-pino-compat:
+ name: Test pino compatibility
+ needs:
+ - lint
+ - coverage-nix
+ - coverage-win
runs-on: ubuntu-latest
permissions:
- pull-requests: write
- contents: write
+ contents: read
+ strategy:
+ matrix:
+ pino-version: ['^9', '^10']
+
steps:
- - uses: fastify/github-action-merge-dependabot@v3
+ - uses: actions/checkout@v6
with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
+ persist-credentials: false
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
+
+ - name: Install dependencies
+ run: |
+ npm install --ignore-scripts
+
+ - name: Install pino ${{ matrix.pino-version }}
+ run: |
+ npm install --ignore-scripts --no-save pino@${{ matrix.pino-version }}
+
+ - name: Run unit tests
+ run: |
+ npm run unit
+
+ - name: Run typescript tests
+ run: |
+ npm run test:typescript
+ env:
+ NODE_OPTIONS: no-network-family-autoselection
package:
- needs: test
+ needs:
+ - test-typescript
+ - test-unit
+ - test-pino-compat
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: 'lts/*'
- - uses: actions/cache@v3
- id: check-cache
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- name: install fastify
run: |
npm install --ignore-scripts
- name: install webpack stack
run: |
- cd test/bundler/webpack && npm install
+ cd test/bundler/webpack && npm install --ignore-scripts
- name: Test webpack bundle
run: |
cd test/bundler/webpack && npm run test
- name: install esbuild stack
run: |
- cd test/bundler/esbuild && npm install
+ cd test/bundler/esbuild && npm install --ignore-scripts
- name: Test esbuild bundle
run: |
cd test/bundler/esbuild && npm run test
+
+ automerge:
+ if: >
+ github.event_name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name == github.repository &&
+ github.event.pull_request.user.login == 'dependabot[bot]'
+ needs:
+ - test-typescript
+ - test-unit
+ - package
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ contents: write
+ steps:
+ - uses: fastify/github-action-merge-dependabot@v3
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/citgm-package.yml b/.github/workflows/citgm-package.yml
new file mode 100644
index 00000000000..4658431c2dc
--- /dev/null
+++ b/.github/workflows/citgm-package.yml
@@ -0,0 +1,106 @@
+name: CITGM Package
+
+on:
+ workflow_dispatch:
+ inputs:
+ package:
+ description: 'Package to test'
+ required: true
+ type: string
+
+ node-version:
+ description: 'Node version to test'
+ required: true
+ type: choice
+ options:
+ - '20'
+ - '22'
+ - '24'
+ - 'lts/*'
+ - 'nightly'
+ - 'current'
+ default: '24'
+ os:
+ description: 'Operating System'
+ required: false
+ type: choice
+ default: 'ubuntu-latest'
+ options:
+ - 'ubuntu-latest'
+ - 'windows-latest'
+ - 'macos-latest'
+ workflow_call:
+ inputs:
+ package:
+ description: 'Package to test'
+ required: true
+ type: string
+ node-version:
+ description: 'Node version to test'
+ required: true
+ type: string
+ default: '20'
+ os:
+ description: 'Operating System'
+ required: false
+ type: string
+ default: 'ubuntu-latest'
+
+permissions:
+ contents: read
+
+jobs:
+ core-plugins:
+ name: CITGM
+ runs-on: ${{inputs.os}}
+ permissions:
+ contents: read
+ steps:
+ - name: Check out Fastify
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Use Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: ${{ inputs.node-version }}
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
+
+ - name: Install Dependencies for Fastify
+ run: |
+ npm install --production --ignore-scripts
+ - name: Npm Link Fastify
+ run: |
+ npm link
+ - name: Determine repository URL of ${{inputs.package}}
+ uses: actions/github-script@v8
+ id: repository-url
+ with:
+ result-encoding: string
+ script: |
+ const response = await fetch('https://registry.npmjs.org/${{inputs.package}}')
+ const data = await response.json()
+ const repositoryUrl = data.repository.url
+ const result = repositoryUrl.match( /.*\/([a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)\.git/)[1]
+ return result
+ - name: Check out ${{inputs.package}}
+ uses: actions/checkout@v6
+ with:
+ repository: ${{ steps.repository-url.outputs.result }}
+ path: package
+ persist-credentials: false
+ - name: Install Dependencies for ${{inputs.package}}
+ working-directory: package
+ run: |
+ npm install --ignore-scripts
+ - name: Sym Link Fastify
+ working-directory: package
+ run: |
+ npm link fastify
+ - name: Run Tests of ${{inputs.package}}
+ working-directory: package
+ run: |
+ npm test
diff --git a/.github/workflows/citgm.yml b/.github/workflows/citgm.yml
new file mode 100644
index 00000000000..535fdcc7194
--- /dev/null
+++ b/.github/workflows/citgm.yml
@@ -0,0 +1,132 @@
+name: CITGM
+
+on:
+ pull_request:
+ types: [labeled]
+
+ workflow_dispatch:
+ inputs:
+ node-version:
+ description: 'Node version'
+ required: true
+ type: choice
+ options:
+ - '20'
+ - '22'
+ - '24'
+ - 'lts/*'
+ - 'nightly'
+ - 'current'
+ - 'latest'
+ default: '24'
+ os:
+ description: 'Operating System'
+ required: false
+ type: choice
+ default: 'ubuntu-latest'
+ options:
+ - 'ubuntu-latest'
+ - 'windows-latest'
+ - 'macos-latest'
+
+permissions:
+ contents: read
+
+jobs:
+ core-plugins:
+ name: CITGM
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'citgm-core-plugins' }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ package:
+ - '@fastify/accepts'
+ - '@fastify/accepts-serializer'
+ - '@fastify/auth'
+ - '@fastify/autoload'
+ - '@fastify/awilix'
+ - '@fastify/aws-lambda'
+ - '@fastify/basic-auth'
+ - '@fastify/bearer-auth'
+ - '@fastify/caching'
+ - '@fastify/circuit-breaker'
+ - '@fastify/compress'
+ - '@fastify/cookie'
+ - '@fastify/cors'
+ - '@fastify/csrf-protection'
+ # - '@fastify/elasticsearch'
+ - '@fastify/env'
+ - '@fastify/etag'
+ - '@fastify/express'
+ - '@fastify/flash'
+ - '@fastify/formbody'
+ - '@fastify/funky'
+ - '@fastify/helmet'
+ - '@fastify/hotwire'
+ - '@fastify/http-proxy'
+ - '@fastify/jwt'
+ # - '@fastify/kafka'
+ - '@fastify/leveldb'
+ - '@fastify/middie'
+ # - '@fastify/mongodb'
+ - '@fastify/multipart'
+ # - '@fastify/mysql'
+ - '@fastify/nextjs'
+ - '@fastify/oauth2'
+ - '@fastify/one-line-logger'
+ - '@fastify/passport'
+ # - '@fastify/postgres'
+ # - '@fastify/rate-limit'
+ # - '@fastify/redis'
+ - '@fastify/reply-from'
+ - '@fastify/request-context'
+ - '@fastify/response-validation'
+ - '@fastify/routes'
+ - '@fastify/schedule'
+ - '@fastify/secure-session'
+ - '@fastify/sensible'
+ - '@fastify/session'
+ - '@fastify/sse'
+ - '@fastify/static'
+ - '@fastify/swagger'
+ - '@fastify/swagger-ui'
+ - '@fastify/throttle'
+ - '@fastify/type-provider-json-schema-to-ts'
+ - '@fastify/type-provider-typebox'
+ - '@fastify/under-pressure'
+ - '@fastify/url-data'
+ - '@fastify/view'
+ # - '@fastify/vite'
+ - '@fastify/websocket'
+ - '@fastify/zipkin'
+ uses: './.github/workflows/citgm-package.yml'
+ with:
+ os: ${{ github.event_name == 'workflow_dispatch' && inputs.os || 'ubuntu-latest' }}
+ package: ${{ matrix.package }}
+ node-version: ${{ github.event_name == 'workflow_dispatch' && inputs.node-version || '24' }}
+
+ remove-label:
+ if: ${{ always() && github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'citgm-core-plugins' }}
+ needs:
+ - core-plugins
+ continue-on-error: true
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - name: Remove citgm-core-plugins label
+ uses: octokit/request-action@v2.x
+ id: remove-label
+ with:
+ route: DELETE /repos/{repo}/issues/{issue_number}/labels/{name}
+ repo: ${{ github.repository }}
+ issue_number: ${{ github.event.pull_request.number }}
+ name: citgm-core-plugins
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - run: "echo Successfully removed label"
+ if: ${{ success() }}
+ - run: "echo Could not remove label"
+ if: ${{ failure() }}
diff --git a/.github/workflows/coverage-nix.yml b/.github/workflows/coverage-nix.yml
index 9945372d71d..2814e1dc80a 100644
--- a/.github/workflows/coverage-nix.yml
+++ b/.github/workflows/coverage-nix.yml
@@ -3,44 +3,30 @@ name: Code Coverage (*nix)
on:
workflow_call:
+permissions:
+ contents: read
+
jobs:
check-coverage:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v6
with:
node-version: 'lts/*'
-
- - uses: actions/cache@v3
- id: check-cache
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- # We do not check coverage requirements here because the goal is to
- # generate a report to upload as an artifact.
- - name: Generate coverage report
- run: |
- npm run coverage:ci
-
- - uses: actions/upload-artifact@v3
- if: ${{ success() }}
- with:
- name: coverage-report-nix
- path: ./coverage/lcov-report/
-
# Here, we verify the coverage thresholds so that this workflow can pass
# or fail and stop further workflows if this one fails.
- name: Verify coverage meets thresholds
diff --git a/.github/workflows/coverage-win.yml b/.github/workflows/coverage-win.yml
index 176b8792aa5..85bd44370d9 100644
--- a/.github/workflows/coverage-win.yml
+++ b/.github/workflows/coverage-win.yml
@@ -3,44 +3,30 @@ name: Code Coverage (win)
on:
workflow_call:
+permissions:
+ contents: read
+
jobs:
check-coverage:
runs-on: windows-latest
permissions:
contents: read
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v6
with:
node-version: 'lts/*'
-
- - uses: actions/cache@v3
- id: check-cache
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
+ cache: 'npm'
+ cache-dependency-path: package.json
+ check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- # We do not check coverage requirements here because the goal is to
- # generate a report to upload as an artifact.
- - name: Generate coverage report
- run: |
- npm run coverage:ci
-
- - uses: actions/upload-artifact@v3
- if: ${{ success() }}
- with:
- name: coverage-report-win
- path: ./coverage/lcov-report/
-
# Here, we verify the coverage thresholds so that this workflow can pass
# or fail and stop further workflows if this one fails.
- name: Verify coverage meets thresholds
diff --git a/.github/workflows/integration-alternative-runtimes.yml b/.github/workflows/integration-alternative-runtimes.yml
new file mode 100644
index 00000000000..bfba5975a89
--- /dev/null
+++ b/.github/workflows/integration-alternative-runtimes.yml
@@ -0,0 +1,63 @@
+name: integration Alternative Runtimes
+
+on:
+ push:
+ branches:
+ - main
+ - next
+ - 'v*'
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+
+permissions:
+ contents: read
+
+jobs:
+ install-production:
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runtime: [nsolid]
+ node-version: [20]
+ pnpm-version: [8]
+ include:
+ - nsolid-version: 5
+ node-version: 20
+ runtime: nsolid
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - uses: nodesource/setup-nsolid@1ca68d2589d3d56ecd3881dfe6ffa87eeda9c939 # v1.0.1
+ if: ${{ matrix.runtime == 'nsolid' }}
+ with:
+ node-version: ${{ matrix.node-version }}
+ nsolid-version: ${{ matrix.nsolid-version }}
+
+ - name: Install Pnpm
+ uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
+ with:
+ version: ${{ matrix.pnpm-version }}
+
+ - name: Install Production
+ run: |
+ pnpm install --ignore-scripts --prod
+
+ - name: Run server
+ run: |
+ node integration/server.js &
+
+ - name: Test
+ if: ${{ success() }}
+ run: |
+ bash integration/test.sh
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index ee1f6f22bef..c77b04bdf6e 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -2,6 +2,10 @@ name: integration
on:
push:
+ branches:
+ - main
+ - next
+ - 'v*'
paths-ignore:
- 'docs/**'
- '*.md'
@@ -10,34 +14,40 @@ on:
- 'docs/**'
- '*.md'
+permissions:
+ contents: read
+
jobs:
install-production:
runs-on: ${{ matrix.os }}
permissions:
contents: read
-
+
strategy:
matrix:
- node-version: [14, 16, 18]
+ node-version: [20, 22, 24]
os: [ubuntu-latest]
+ pnpm-version: [8]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
-
+
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
+ check-latest: true
- name: Install Pnpm
- run: |
- npm i -g pnpm
-
+ uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
+ with:
+ version: ${{ matrix.pnpm-version }}
+
- name: Install Production
run: |
- pnpm install --prod
+ pnpm install --ignore-scripts --prod
- name: Run server
run: |
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index e0fdabd7396..6f8e7a924f6 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -3,12 +3,12 @@ on: pull_request_target
permissions:
contents: read
- pull-requests: write
jobs:
label:
+ permissions:
+ contents: read
+ pull-requests: write
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v4
- with:
- repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ - uses: actions/labeler@v6
diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml
index 618df1305df..5123940808a 100644
--- a/.github/workflows/links-check.yml
+++ b/.github/workflows/links-check.yml
@@ -4,27 +4,59 @@ on:
pull_request:
paths:
- 'docs/**'
- - '*.md'
+ - '**/*.md'
+ - '.github/workflows/links-check.yml'
+
+permissions:
+ contents: read
jobs:
linkChecker:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- name: Check out repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
persist-credentials: false
+ fetch-depth: 0
# It will be possible to check only for the links in the changed files
# See: https://github.com/lycheeverse/lychee-action/issues/17
- name: Link Checker
id: lychee
- uses: lycheeverse/lychee-action@76ab977fedbeaeb32029313724a2e56a8a393548
+ uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # 2.8.0
with:
fail: true
- # As external links behaviour is not predictable, we check only internal links
+ # As external links behavior is not predictable, we check only internal links
# to ensure consistency.
# See: https://github.com/lycheeverse/lychee-action/issues/17#issuecomment-1162586751
args: --offline --verbose --no-progress './**/*.md'
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+
+ - name: Collect changed markdown files
+ id: changed-md
+ run: |
+ # docs/Guides/Ecosystem.md is checked by scripts/validate-ecosystem-links.js
+ changed_files=$(git diff --name-only --diff-filter=ACMRT "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | grep -E '\.md$' | grep -v '^docs/Guides/Ecosystem\.md$' || true)
+
+ if [ -z "$changed_files" ]; then
+ echo "files=" >> "$GITHUB_OUTPUT"
+ echo "No markdown files to check with linkinator."
+ exit 0
+ fi
+
+ files_as_space_list=$(echo "$changed_files" | tr '\n' ' ' | xargs)
+ echo "files=$files_as_space_list" >> "$GITHUB_OUTPUT"
+ echo "Checking markdown files: $files_as_space_list"
+
+ - name: External Link Checker
+ if: steps.changed-md.outputs.files != ''
+ uses: JustinBeckwith/linkinator-action@f62ba0c110a76effb2ee6022cc6ce4ab161085e3 # 2.4.0
+ with:
+ paths: ${{ steps.changed-md.outputs.files }}
+ retry: true
+ redirects: error
+ linksToSkip: "https://github.com/orgs/fastify/.*"
diff --git a/.github/workflows/lint-ecosystem-order.yml b/.github/workflows/lint-ecosystem-order.yml
index e93e37422b2..866f0e09f6a 100644
--- a/.github/workflows/lint-ecosystem-order.yml
+++ b/.github/workflows/lint-ecosystem-order.yml
@@ -1,34 +1,32 @@
name: Lint Ecosystem Order
on:
- push:
- branches-ignore:
- - master
- - main
- paths:
- - "**/Ecosystem.md"
pull_request:
branches:
- - master
- main
paths:
- "**/Ecosystem.md"
+permissions:
+ contents: read
+
jobs:
build:
name: Lint Ecosystem Order
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
persist-credentials: false
- name: Lint Doc
- uses: actions/github-script@v6
+ uses: actions/github-script@v8
with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const script = require('./.github/scripts/lint-ecosystem.js')
await script({ core })
-
diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml
index 416efa98817..883286d8476 100644
--- a/.github/workflows/lock-threads.yml
+++ b/.github/workflows/lock-threads.yml
@@ -6,8 +6,7 @@ on:
workflow_dispatch:
permissions:
- issues: write
- pull-requests: write
+ contents: read
concurrency:
group: lock
@@ -15,8 +14,11 @@ concurrency:
jobs:
action:
runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
steps:
- - uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321
+ - uses: jsumners/lock-threads@e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6 # v3.0.0
with:
issue-inactive-days: '90'
exclude-any-issue-labels: 'discussion,good first issue,help wanted'
diff --git a/.github/workflows/md-lint.yml b/.github/workflows/md-lint.yml
index ec0047c9f24..f10d39589e0 100644
--- a/.github/workflows/md-lint.yml
+++ b/.github/workflows/md-lint.yml
@@ -3,38 +3,42 @@ name: Lint Docs
on:
push:
branches-ignore:
- - master
- main
paths:
- "**/*.md"
pull_request:
branches:
- - master
- main
paths:
- "**/*.md"
+permissions:
+ contents: read
+
jobs:
build:
name: Lint Markdown
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
persist-credentials: false
-
+
- name: Setup Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: 'lts/*'
+ check-latest: true
- name: Install Linter
run: npm install --ignore-scripts
- name: Add Matcher
- uses: xt0rted/markdownlint-problem-matcher@b643b0751c371f357690337d4549221347c0e1bc
+ uses: xt0rted/markdownlint-problem-matcher@1a5fabfb577370cfdf5af944d418e4be3ea06f27 # v3.0.0
- name: Run Linter
run: ./node_modules/.bin/markdownlint-cli2
diff --git a/.github/workflows/missing_types.yml b/.github/workflows/missing_types.yml
index b6d77acb2f5..a182f14bd45 100644
--- a/.github/workflows/missing_types.yml
+++ b/.github/workflows/missing_types.yml
@@ -5,12 +5,14 @@ on:
types: [closed]
permissions:
- issues: write
- pull-requests: read
+ contents: read
jobs:
create_issue:
runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: read
if: |
github.event.pull_request.merged &&
contains(github.event.pull_request.labels.*.name, 'missing-types')
diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml
index 47c207a2d74..d0fa9d61932 100644
--- a/.github/workflows/package-manager-ci.yml
+++ b/.github/workflows/package-manager-ci.yml
@@ -11,56 +11,71 @@ permissions:
jobs:
pnpm:
runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
strategy:
matrix:
# Maintenance and active LTS
- node-version: [14, 16]
- os: [ubuntu-18.04]
+ node-version: [20, 22, 24]
+ os: [ubuntu-latest]
+ pnpm-version: [8]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
+ check-latest: true
- name: Install with pnpm
- run: |
- curl -L https://unpkg.com/@pnpm/self-installer | node
- pnpm install
+ uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
+ with:
+ version: ${{ matrix.pnpm-version }}
+
+ - run: pnpm install --ignore-scripts
- name: Run tests
run: |
pnpm run test:ci
+ env:
+ NODE_OPTIONS: no-network-family-autoselection
yarn:
runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
strategy:
matrix:
# Maintenance and active LTS
- node-version: [14, 16]
- os: [ubuntu-18.04]
+ node-version: [20, 22, 24]
+ os: [ubuntu-latest]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
+ check-latest: true
- name: Install with yarn
run: |
curl -o- -L https://yarnpkg.com/install.sh | bash
- yarn install --ignore-engines
+ yarn install --ignore-engines --ignore-scripts
+
+ - run: yarn
- name: Run tests
run: |
yarn run test:ci
+ env:
+ NODE_OPTIONS: no-network-family-autoselection
diff --git a/.github/workflows/pull-request-title.yml b/.github/workflows/pull-request-title.yml
new file mode 100644
index 00000000000..bfa000db945
--- /dev/null
+++ b/.github/workflows/pull-request-title.yml
@@ -0,0 +1,18 @@
+name: pull request title check
+on:
+ pull_request:
+ types: [opened, edited, synchronize, reopened]
+
+permissions:
+ contents: read
+
+jobs:
+ pull-request-title-check:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
+ steps:
+ - uses: fastify/action-pr-title@v0
+ with:
+ regex: '/^(build|chore|ci|docs|feat|types|fix|perf|refactor|style|test)(?:\([^\):]*\))?!?:\s/'
+ github-token: ${{ github.token }}
diff --git a/.github/workflows/test-compare.yml b/.github/workflows/test-compare.yml
new file mode 100644
index 00000000000..c8689e237b6
--- /dev/null
+++ b/.github/workflows/test-compare.yml
@@ -0,0 +1,21 @@
+name: Test compare
+on:
+ pull_request:
+ types: [opened, reopened, synchronize, labeled]
+
+permissions:
+ contents: read
+
+jobs:
+ run:
+ if: contains(github.event.pull_request.labels.*.name, 'test-compare')
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Test compare
+ uses: nearform-actions/github-action-test-compare@d50bc37a05e736bb40db0eebc8fdad3e33ece136 # v1.0.26
+ with:
+ label: test-compare
+ testCommand: 'npm run test:ci'
diff --git a/.github/workflows/validate-ecosystem-links.yml b/.github/workflows/validate-ecosystem-links.yml
new file mode 100644
index 00000000000..a841914dfa5
--- /dev/null
+++ b/.github/workflows/validate-ecosystem-links.yml
@@ -0,0 +1,31 @@
+name: Validate Ecosystem Links
+
+on:
+ schedule:
+ # Run every Sunday at 00:00 UTC
+ - cron: '0 0 * * 0'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ validate-links:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 22
+
+ - name: Validate ecosystem links
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: node scripts/validate-ecosystem-links.js
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index 1ff589d3ba6..8b79fd67318 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -6,6 +6,7 @@ on:
- main
paths:
- 'docs/**'
+ - '**.md'
release:
types:
- released
@@ -19,4 +20,6 @@ jobs:
steps:
- name: Build website
run: |
- curl -X POST --header 'Content-Type: application/json' "https://circleci.com/api/v1.1/project/github/fastify/website?circle-token=${{ secrets.circleci_token }}"
+ gh workflow run deploy-website.yml -R fastify/website
+ env:
+ GH_TOKEN: ${{ secrets.GHA_WEBSITE_FINE_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 875f42e09fa..84ba3d43f0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,6 +136,7 @@ dist
.DS_Store
# lock files
+bun.lockb
package-lock.json
pnpm-lock.yaml
yarn.lock
@@ -144,6 +145,9 @@ yarn.lock
.vscode
.idea
+# tap files
+.tap/
+
# Optional compressed files (npm generated package, zip, etc)
/*.zip
@@ -166,3 +170,11 @@ out.tap
test/https/fastify.cert
test/https/fastify.key
/test/types/import.js
+
+# Agents files
+CLAUDE.md
+AGENTS.md
+.agents/
+.agent
+.claude
+.pi
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000000..93bdc1a22b8
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,10 @@
+# Gitpod Configuration File
+# SEE https://www.gitpod.io/docs/references/gitpod-yml
+
+tasks:
+ - init: npm install
+ name: Install Dependencies
+
+vscode:
+ extensions:
+ - "dbaeumer.vscode-eslint"
diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml
index 5c56986701f..408be45225f 100644
--- a/.markdownlint-cli2.yaml
+++ b/.markdownlint-cli2.yaml
@@ -7,7 +7,7 @@ config:
MD013:
line_length: 80
code_block_line_length: 120
- headers: false
+ headings: false
tables: false
strict: false
stern: false
diff --git a/.npmignore b/.npmignore
index 33caf9bf839..c2ed9cfe1f3 100644
--- a/.npmignore
+++ b/.npmignore
@@ -3,11 +3,21 @@
.gitignore
.github
.nyc_output
+.DS_Store
coverage/
tools/
+.tap/
CODE_OF_CONDUCT.md
CONTRIBUTING.md
+EXPENSE_POLICY.md
.clinic
+.gitpod.yml
+.vscode/
+*.log
+
+# AI files
+.claude/
+CLAUDE.md
# test certification
test/https/fastify.cert
diff --git a/.npmrc b/.npmrc
index 43c97e719a5..3757b3046ec 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
+ignore-scripts=true
package-lock=false
diff --git a/.taprc b/.taprc
deleted file mode 100644
index dd0fbcce796..00000000000
--- a/.taprc
+++ /dev/null
@@ -1,9 +0,0 @@
-ts: false
-jsx: false
-flow: false
-check-coverage: true
-coverage: true
-node-arg: --allow-natives-syntax
-
-files:
- - 'test/**/*.test.js'
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 80320a25a5d..9c322c23b55 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,178 +1,4 @@
# Code of Conduct
-Fastify, as member project of the OpenJS Foundation, use [Contributor Covenant
-v2.0](https://contributor-covenant.org/version/2/0/code_of_conduct) as their
-code of conduct. The full text is included
-[below](#contributor-covenant-code-of-conduct) in English, and translations are
-available from the Contributor Covenant organisation:
-
-- [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations)
-- [github.com/ContributorCovenant](https://github.com/ContributorCovenant/contributor_covenant/tree/release/content/version/2/0)
-
-Refer to the sections on reporting and escalation in this document for the
-specific emails that can be used to report and escalate issues.
-
-## Reporting
-
-### Project Spaces
-
-For reporting issues in spaces related to Fastify please use the email
-`hello@matteocollina.com` or `tommydelved@gmail.com`. Fastify handles CoC issues
-related to the spaces that it maintains. Projects maintainers commit to:
-
-- maintain the confidentiality with regard to the reporter of an incident
-- to participate in the path for escalation as outlined in the section on
- Escalation when required.
-
-### Foundation Spaces
-
-For reporting issues in spaces managed by the OpenJS Foundation, for example,
-repositories within the OpenJS organization, use the email
-`report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for
-managing these reports and commits to:
-
-- maintain the confidentiality with regard to the reporter of an incident
-- to participate in the path for escalation as outlined in the section on
- Escalation when required.
-
-## Escalation
-
-The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a
-foundation-wide team established to manage escalation when a reporter believes
-that a report to a member project or the CPC has not been properly handled. In
-order to escalate to the CoCP send an email to
-`coc-escalation@lists.openjsf.org`.
-
-For more information, refer to the full [Code of Conduct governance
-document](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/CODE_OF_CONDUCT.md).
-
----
-
-## Contributor Covenant Code of Conduct v2.0
-
-### Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, religion, or sexual identity and
-orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-### Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
- community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or advances of
- any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
- without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-### Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-### Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-### Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at the email
-addresses listed above in the [Reporting](#reporting) and
-[Escalation](#escalation) sections. All complaints will be reviewed and
-investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-### Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-#### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-#### 2. Warning
-
-**Community Impact**: A violation through a single incident or series of
-actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or permanent
-ban.
-
-#### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-#### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within the
-project community.
-
-### Attribution
-
-This Code of Conduct is adapted from the [Contributor
-Covenant](https://www.contributor-covenant.org), version 2.0, available at
-[contributor-covenant.org/version/2/0/code_of_conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct).
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct
-enforcement ladder](https://github.com/mozilla/diversity).
-
-For answers to common questions about this code of conduct, see the FAQ at
-[contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
-Translations are available at
-[contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
+Please see Fastify's [organization-wide code of conduct
+](https://github.com/fastify/.github/blob/main/CODE_OF_CONDUCT.md).
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2a8dd4552b6..6f1e68b702c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,41 +12,44 @@ details on contributing to this project.
### I want to be a collaborator!
If you think you meet the above criteria and we have not invited you yet, we are
-sorry! Feel free reach out to a [Lead
+sorry! Feel free to reach out to a [Lead
Maintainer](https://github.com/fastify/fastify#team) privately with a few links
to your valuable contributions. Read the [GOVERNANCE](GOVERNANCE.md) to get more
information.
## Rules
-There are a few basic ground-rules for contributors:
+There are a few basic ground rules for contributors:
1. **No `--force` pushes** on `main` or modifying the Git history in any way
after a PR has been merged.
1. **Non-main branches** ought to be used for ongoing work.
1. **External API changes and significant modifications** ought to be subject to
- an **internal pull-request** to solicit feedback from other contributors.
-1. Internal pull-requests to solicit feedback are *encouraged* for any other
- non-trivial contribution but left to the discretion of the contributor.
-1. Contributors should attempt to adhere to the prevailing code-style.
-1. At least two contributors, or one core member, must approve pull-requests
- prior to merging.
-1. All integrated CI services must be green before a pull-request can be merged.
+ an **internal pull request** to solicit feedback from other contributors.
+1. Internal pull requests to solicit feedback are *encouraged* for any other
+ non-trivial contribution but are left to the discretion of the contributor.
+1. Contributors should attempt to adhere to the prevailing code style.
+1. At least two contributors, or one core member, must approve pull requests
+ before merging.
+1. All integrated CI services must be green before a pull request can be merged.
1. A lead maintainer must merge SemVer-major changes in this repository.
-1. In case it is not possible to reach consensus in a pull-request, the decision
+1. If it is not possible to reach a consensus in a pull request, the decision
is left to the lead maintainer's team.
-### Fastify v1.x
+### Fastify previous versions
-Code for Fastify's **v1.x** is in [branch
-1.x](https://github.com/fastify/fastify/tree/1.x), so all Fastify 1.x related
-changes should be based on **`branch 1.x`**.
+Every version of Fastify has its own branch. All Fastify related
+changes should be based on the corresponding branch.
-### Fastify v2.x
+We have a [Long Term Support](./docs/Reference/LTS.md) policy that defines
+the organization's efforts for each Fastify's version.
-Code for Fastify's **v2.x** is in [branch
-2.x](https://github.com/fastify/fastify/tree/2.x), so all Fastify 2.x related
-changes should be based on **`branch 2.x`**.
+|Version|Branch|
+|-------|------|
+**v1.x**|[branch 1.x](https://github.com/fastify/fastify/tree/1.x)|
+**v2.x**|[branch 2.x](https://github.com/fastify/fastify/tree/2.x)|
+**v3.x**|[branch 3.x](https://github.com/fastify/fastify/tree/3.x)|
+**v4.x**|[branch 4.x](https://github.com/fastify/fastify/tree/4.x)|
## Releases
@@ -55,23 +58,23 @@ not bump version numbers in pull requests.
## Plugins
-The contributors to the Fastify's plugins must attend the same rules of the
-Fastify repository with a few adjustments:
+Contributors to Fastify plugins must follow the same rules as the main Fastify repository,
+ with a few adjustments:
1. Any member can publish a release.
1. The plugin version must follow the [semver](https://semver.org/)
specification.
-1. The Node.js compatibility must match with the Fastify's main branch.
+1. The Node.js compatibility must match with Fastify's main branch.
1. The new release must have the changelog information stored in the GitHub
- release. For this scope we suggest to adopt a tool like
- [`releasify`](https://github.com/fastify/releasify) to archive this.
+ release. For this we suggest adopting a tool like
+ [`releasify`](https://github.com/fastify/releasify) to achieve this.
1. PR opened by bots (like Dependabot) can be merged if the CI is green and the
- Node.js versions supported are the same of the plugin.
+ Node.js versions supported are the same as the plugin.
## Changes to this arrangement
This is an experiment and feedback is welcome! This document may also be subject
-to pull-requests or changes by contributors where you believe you have something
+to pull requests or changes by contributors where you believe you have something
valuable to add or change.
# Fastify Organization Structure
@@ -83,30 +86,38 @@ The Fastify structure is detailed in the [GOVERNANCE](GOVERNANCE.md) document.
Welcome to the team! We are happy to have you. Before you start, please complete
the following tasks:
1. Set up 2 factor authentication for GitHub and NPM
- - [GitHub
+ - [GitHub
2FA](https://help.github.com/en/articles/securing-your-account-with-two-factor-authentication-2fa)
- - [NPM 2FA](https://docs.npmjs.com/about-two-factor-authentication)
+ - [NPM 2FA](https://docs.npmjs.com/about-two-factor-authentication)
2. Choose which team to join *(more than one is ok!)* based on how you want to
help.
+ - Core team: maintains core Fastify and its documentation
+ - Plugins team: maintains Fastify's plugins and its ecosystem
3. Open a pull request to
[`fastify/fastify:HEAD`](https://github.com/fastify/fastify/pulls) that adds
- your name, username, and email to the team you have choosen in the
+ your name, username, and email to the team you have chosen in the
[README.md](./README.md) and [package.json](./package.json) *(if you are part
- of the core team)* files. The members lists are sorted alphabetically; make
- sure to add your name in the proper order.
+ of the core team)* files. The member lists are sorted alphabetically by last
+ name; make sure to add your name in the proper order.
4. Open a pull request to
[`fastify/website:HEAD`](https://github.com/fastify/website/pulls) adding
yourself to the
- [team.yml](https://github.com/fastify/website/blob/HEAD/src/website/data/team.yml)
+ [team.yml](https://github.com/fastify/website/blob/HEAD/static/data/team.yml)
file. This list is also sorted alphabetically so make sure to add your name
in the proper order. Use your GitHub profile icon for the `picture:` field.
-5. The person that does the onboarding must add you to the [npm
- org](https://www.npmjs.com/org/fastify), so that you can help maintaining the
- official plugins.
+5. Read the [pinned announcements](https://github.com/orgs/fastify/discussions/categories/announcements)
+ to be updated with the organization’s news.
+6. The person who does the onboarding must open a pull request to
+ [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin)
+ so an admin can add the new member to the
+ [npm org](https://www.npmjs.com/org/fastify) and the GitHub Team,
+ so that the new joiner can help maintain the official plugins.
+8. Optionally, the person can be added as an Open Collective member
+ by the lead team.
### Offboarding Collaborators
-We are thankful to you and we are really glad to have worked with you. We'll be
+We are thankful to you and we are really glad to have worked with you. We'd be
really happy to see you here again if you want to come back, but for now the
person that did the onboarding must:
1. Ask the collaborator if they want to stay or not.
@@ -117,16 +128,20 @@ person that did the onboarding must:
2. Open a pull request to
[`fastify/website:HEAD`](https://github.com/fastify/website/pulls) and move
themselves to the *Past Collaborators* section in the
- [team.yml](https://github.com/fastify/website/blob/HEAD/src/website/data/team.yml)
+ [team.yml](https://github.com/fastify/website/blob/HEAD/static/data/team.yml)
file.
The person that did the onboarding must:
-1. If the collaborator doesn't reply to the ping in reasonable time, open the
+1. If the collaborator does not reply to the ping in a reasonable time, open the
pull requests described above.
-2. Remove the collaborator from the Fastify teams on GitHub.
-3. Remove the collaborator from the [npm
+2. Open a pull request to [`fastify/org-admin`](https://github.com/fastify/org-admin?tab=readme-ov-file#org-admin)
+ so an admin will:
+ 1. Remove the collaborator from the Fastify teams on GitHub.
+ 2. Remove the collaborator from the [npm
org](https://www.npmjs.com/org/fastify).
-4. Remove the collaborator from the Azure team.
+ 3. Remove the collaborator from the Azure team.
+ 4. Remove the collaborator from the Open Collective members.
+
-----------------------------------------
diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md
new file mode 100644
index 00000000000..3baa6884267
--- /dev/null
+++ b/EXPENSE_POLICY.md
@@ -0,0 +1,105 @@
+# Expense Policy
+
+Fastify collaborators accept donations through the [Open Collective](https://opencollective.com/fastify/)
+platform and [GitHub Sponsors](https://github.com/sponsors/fastify)
+to enhance the project and support the community.
+
+This Collective is run by and for the benefit of the independent contributors to
+the Fastify open source software project.
+This Collective is not endorsed or administered by OpenJS Foundation, Inc.
+(the “OpenJS Foundation”). The OpenJS Foundation does not receive or have
+control over any funds contributed. The OpenJS Foundation does not direct or
+otherwise supervise the actions of any contributor to the Fastify project,
+and all donations made will be expended for the private benefit of or otherwise
+to reimburse individuals that do not have an employer/employee, contractor, or
+other agency relationship with the OpenJS Foundation.
+The Fastify marks used herein are used under license from the OpenJS Foundation
+for the benefit of the open source software community.
+
+The admins of the Fastify Collective are the [lead maintainers](./GOVERNANCE.md)
+of the project.
+
+This document outlines the process for requesting reimbursement or an invoice
+for expenses.
+
+## Reimbursement
+
+Reimbursement is applicable for expenses already paid, such as:
+
+- Stickers
+- Gadgets
+- Hosting
+
+**Before making any purchases**, initiate a [new discussion](https://github.com/orgs/fastify/discussions)
+in the `fastify` organization with the following information:
+
+- What is needed
+- Why it is needed
+- Cost
+- Deadline
+
+Once the discussion is approved by a lead maintainer and with no unresolved objections,
+the purchase can proceed, and an expense can be submitted to the [Open Collective][submit].
+This process takes a minimum of 3 business days from the request to allow time for
+discussion approval.
+
+The discussion helps prevent misunderstandings and ensures the expense is not rejected.
+As a project under the OpenJS Foundation, Fastify benefits from the Foundation's
+resources, including servers, domains, and [travel funds](https://github.com/openjs-foundation/community-fund/tree/main/programs/travel-fund).
+
+Always seek approval first.
+
+## Invoice
+
+Invoices are for services provided to the Fastify project, such as PR reviews,
+documentation, etc.
+A VAT number is not required to submit an invoice.
+Refer to the [Open Collective documentation][openc_docs] for details.
+
+### Adding a bounty to an issue
+
+Issues become eligible for a bounty when the core team adds the `bounty` label,
+with the amount determined by the core team based on `estimated hours * rate`
+(suggested $50 per hour).
+
+> Example: If the estimated time to fix the issue is 2 hours,
+> the bounty will be $100.
+
+To add a bounty:
+
+- Apply the `bounty` label to the issue
+- Comment on the issue with the bounty amount
+- Edit the first comment of the issue using this template:
+
+```
+## 💰 Bounty
+
+This issue has a bounty of [$AMOUNT](LINK TO THE BOUNTY COMMENT).
+_Read more about [the bounty program](./EXPENSE_POLICY.md)_
+```
+
+For discussions on bounties or determining amounts, open a [new discussion](https://github.com/orgs/fastify/discussions/new?category=bounty).
+
+### Outstanding contributions
+
+The lead team can decide to add a bounty to an issue or PR not labeled as `bounty`
+if the contribution is outstanding.
+
+### Claiming a bounty
+
+To claim a bounty:
+
+- Submit a PR that fixes the issue
+- If multiple submissions exist, a core member will choose the best solution
+- Once merged, the PR author can claim the bounty by:
+ - Submitting an expense to the [Open Collective][submit] with the PR link
+ - Adding a comment on the PR with a link to their Open Collective expense to
+ ensure the claimant is the issue resolver
+- The expense will be validated by a lead maintainer and then the payment will be
+ processed by Open Collective
+
+If the Open Collective budget is insufficient, the expense will be rejected.
+Unclaimed bounties are available for other issues.
+
+[submit]: https://opencollective.com/fastify/expenses/new
+[openc_docs]: https://docs.oscollective.org/how-it-works/basics/invoice-and-reimbursement-examples
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index c21c1bf15b2..c1d8fab6dbe 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -1,105 +1,4 @@
# Fastify Project Governance
-
-
-* [Lead Maintainers](#lead-maintainers)
-* [Collaborators](#collaborators)
- * [Collaborator activities](#collaborator-activities)
-* [Great Contributors](#great-contributors)
-* [Collaborator nominations](#collaborator-maintainers-nominations)
-* [Lead Maintainers nominations](#lead-maintainers-nominations)
-* [Consensus seeking process](#consensus-seeking-process)
-
-
-
-## Lead Maintainers
-
-Fastify Lead Maintainers are the founder of the project and the organization
-owners. They are the only members of the `@fastify/leads` team. The Lead
-Maintainers are the curator of the Fastify project and their key responsibility
-is to issue releases of Fastify and its dependencies.
-
-## Collaborators
-
-Fastify Collaborators maintain the projects of the Fastify organization.
-
-They are split into the following teams:
-
-| Team | Responsibility | Repository |
-|---|---|---|
-| `@fastify/leads` | Fastify Lead Maintainers | GitHub organization owners |
-| `@fastify/core` | Fastify Core development | `fastify`, `fast-json-stringify`, `light-my-request`, `fastify-plugin`, `middie` |
-| `@fastify/plugins` | Build, maintain and release Fastify plugins | All plugins repositories |
-| `@fastify/benchmarks` | Build and maintain our benchmarks suite | `benchmarks` |
-| `@fastify/docs-chinese` | Translate the Fastify documentation in Chinese | `docs-chinese` |
-
-Every member of the org is also part of `@fastify/fastify`.
-
-Collaborators have:
-
-* Commit access to the projects repository of the team they belong
- * Grant to release new versions of the project
-
-Both Collaborators and non-Collaborators may propose changes to the source code
-of the projects of the organization. The mechanism to propose such a change is a
-GitHub pull request. Collaborators review and merge (_land_) pull requests
-following the [CONTRIBUTING](CONTRIBUTING.md#rules) guidelines.
-
-### Collaborator activities
-
-* Helping users and novice contributors
-* Contributing code and documentation changes that improve the project
-* Reviewing and commenting on issues and pull requests
-* Participation in working groups
-* Merging pull requests
-* Release plugins
-
-The Lead Maintainers can remove inactive Collaborators or provide them with
-_Past Collaborators_ status. Past Collaborators may request that the Lead
-Maintainers restore them to active status.
-
-
-## Great Contributors
-
-Great contributors on a specific area in the Fastify ecosystem will be invited
-to join this group by Lead Maintainers. This group has the same permissions of a
-contributor.
-
-## Collaborator nominations
-
-Individuals making significant and valuable contributions to the project may be
-a candidate to join the Fastify organization.
-
-A Collaborator needs to open a private team discussion on GitHub and list the
-candidates they want to sponsor with a link to the user's contributions. For
-example:
-
-* Activities in the Fastify organization
- `[USERNAME](https://github.com/search?q=author:USERNAME+org:fastify)`
-
-Otherwise, a Contributor may self-apply if they believe they meet the above
-criteria by reaching out to a Lead Maintainer privately with the links to their
-valuable contributions. The Lead Maintainers will reply to the Contributor and
-will decide if candidate it to be made a collaborator.
-
-The consensus to grant a new candidate Collaborator status is reached when:
-
-- at least one of the Lead Maintainers approve
-- at least two of the Team Members approve
-
-After these conditions are satisfied, the [onboarding
-process](CONTRIBUTING.md#onboarding-collaborators) may start.
-
-
-## Lead Maintainers nominations
-
-A Team Member may be promoted to a Lead Maintainers only through nomination by a
-Lead maintainer and with agreement from the rest of Lead Maintainers.
-
-
-## Consensus seeking process
-
-The Fastify organization follows a [Consensus Seeking][] decision-making model.
-
-[Consensus Seeking]:
- https://en.wikipedia.org/wiki/Consensus-seeking_decision-making
+Please see Fastify's [organization-wide governance
+](https://github.com/fastify/.github/blob/main/GOVERNANCE.md) document.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index a5b4e2a259f..266e719722c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,9 +1,6 @@
MIT License
-Copyright (c) 2016-2022 The Fastify Team
-
-The Fastify team members are listed at https://github.com/fastify/fastify#team
-and in the README file.
+Copyright (c) 2016-present The Fastify team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/PROJECT_CHARTER.md b/PROJECT_CHARTER.md
index 79262c8d1d7..a841a165ba1 100644
--- a/PROJECT_CHARTER.md
+++ b/PROJECT_CHARTER.md
@@ -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.
diff --git a/README.md b/README.md
index 90af4385471..403563ce97c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
-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
+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 `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)
@@ -51,13 +63,6 @@ development?
- [Hosted by](#hosted-by)
- [License](#license)
-Enter Fastify. Fastify is a web framework highly focused on providing the best
-developer experience with the least overhead and a powerful plugin architecture.
-It is inspired by Hapi and Express and as far as we know, it is one of the
-fastest web frameworks in town.
-
-This branch refers to the Fastify v4 release. Check out the
-[v3.x](https://github.com/fastify/fastify/tree/v3.x) branch for v3.
### Quick start
@@ -99,17 +104,11 @@ generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli).
### Install
-If installing in an existing project, then Fastify can be installed into the
-project as a dependency:
+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
@@ -118,6 +117,7 @@ yarn add fastify
// ESM
import Fastify from 'fastify'
+
const fastify = Fastify({
logger: true
})
@@ -138,11 +138,12 @@ fastify.listen({ port: 3000 }, (err, address) => {
})
```
-with async-await:
+With async-await:
```js
// ESM
import Fastify from 'fastify'
+
const fastify = Fastify({
logger: true
})
@@ -164,22 +165,14 @@ fastify.listen({ port: 3000 }, (err, address) => {
Do you want to know more? Head to the Getting Started.
-
-
-### Fastify v1.x and v2.x
-
-Code for Fastify's **v1.x** is in [**`branch
-1.x`**](https://github.com/fastify/fastify/tree/1.x), so all Fastify 1.x related
-changes should be based on **`branch 1.x`**. In a similar way, all Fastify
-**v2.x** related changes should be based on [**`branch
-2.x`**](https://github.com/fastify/fastify/tree/2.x).
+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,23 +183,23 @@ changes should be based on **`branch 1.x`**. In a similar way, all Fastify
- **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.
-- **Extendible:** 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
__Machine:__ EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD.
-__Method:__: `autocannon -c 100 -d 40 -p 10 localhost:3000` * 2, taking the
+__Method__: `autocannon -c 100 -d 40 -p 10 localhost:3000` * 2, taking the
second average
| Framework | Version | Router? | Requests/sec |
@@ -219,53 +212,44 @@ second average
| - | | | |
| `http.Server` | 16.14.2 | ✗ | 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
-* Guides
-* Server
-* Routes
-* Encapsulation
-* Logging
-* Middleware
-* Hooks
-* Decorators
-* Validation
- and Serialization
-* Fluent Schema
-* Lifecycle
-* Reply
-* Request
-* Errors
-* Content Type
- Parser
-* Plugins
-* Testing
-* Benchmarking
-* How to write a good
- plugin
-* Plugins Guide
-* HTTP2
-* Long Term Support
-* TypeScript and types
- support
-* Serverless
-* Recommendations
-
-中文文档[地址](https://github.com/fastify/docs-chinese/blob/HEAD/README.md)
+* [__`Getting Started`__](./docs/Guides/Getting-Started.md)
+* [__`Guides`__](./docs/Guides/Index.md)
+* [__`Server`__](./docs/Reference/Server.md)
+* [__`Routes`__](./docs/Reference/Routes.md)
+* [__`Encapsulation`__](./docs/Reference/Encapsulation.md)
+* [__`Logging`__](./docs/Reference/Logging.md)
+* [__`Middleware`__](./docs/Reference/Middleware.md)
+* [__`Hooks`__](./docs/Reference/Hooks.md)
+* [__`Decorators`__](./docs/Reference/Decorators.md)
+* [__`Validation and Serialization`__](./docs/Reference/Validation-and-Serialization.md)
+* [__`Fluent Schema`__](./docs/Guides/Fluent-Schema.md)
+* [__`Lifecycle`__](./docs/Reference/Lifecycle.md)
+* [__`Reply`__](./docs/Reference/Reply.md)
+* [__`Request`__](./docs/Reference/Request.md)
+* [__`Errors`__](./docs/Reference/Errors.md)
+* [__`Content Type Parser`__](./docs/Reference/ContentTypeParser.md)
+* [__`Plugins`__](./docs/Reference/Plugins.md)
+* [__`Testing`__](./docs/Guides/Testing.md)
+* [__`Benchmarking`__](./docs/Guides/Benchmarking.md)
+* [__`How to write a good plugin`__](./docs/Guides/Write-Plugin.md)
+* [__`Plugins Guide`__](./docs/Guides/Plugins-Guide.md)
+* [__`HTTP2`__](./docs/Reference/HTTP2.md)
+* [__`Long Term Support`__](./docs/Reference/LTS.md)
+* [__`TypeScript and types support`__](./docs/Reference/TypeScript.md)
+* [__`Serverless`__](./docs/Guides/Serverless.md)
+* [__`Recommendations`__](./docs/Guides/Recommendations.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.
@@ -276,6 +260,20 @@ 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,
+we welcome contributions from anyone and everyone. Please read the [CONTRIBUTING](./CONTRIBUTING.md)
+guidelines before submitting pull requests.
+
## Team
_Fastify_ is the result of the work of a great community. Team members are
@@ -283,103 +281,133 @@ listed in alphabetical order.
**Lead Maintainers:**
* [__Matteo Collina__](https://github.com/mcollina),
- ,
+ ,
* [__Tomas Della Vedova__](https://github.com/delvedor),
- ,
+ ,
+* [__KaKa Ng__](https://github.com/climba03003),
+
+* [__Manuel Spigolon__](https://github.com/eomm),
+ ,
+* [__James Sumners__](https://github.com/jsumners),
+ ,
### Fastify Core team
-* [__Tommaso Allevi__](https://github.com/allevo),
- ,
-* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
- ,
+* [__Aras Abbasi__](https://github.com/uzlopak),
+
* [__Harry Brundage__](https://github.com/airhorns/),
- ,
-* [__David Mark Clements__](https://github.com/davidmarkclements),
- ,
-
+ ,
* [__Matteo Collina__](https://github.com/mcollina),
- ,
+ ,
+* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
+
* [__Tomas Della Vedova__](https://github.com/delvedor),
- ,
-* [__Dustin Deus__](https://github.com/StarpTech),
- ,
-* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
- ,
-* [__Denis Fäcke__](https://github.com/SerayaEryn),
- ,
-* [__Rafael Gonzaga__](https://github.com/rafaelgss),
- ,
+ ,
+* [__Carlos Fuentes__](https://github.com/metcoder95),
+ ,
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Luciano Mammino__](https://github.com/lmammino),
- ,
+ ,
+* [__Jean Michelet__](https://github.com/jean-michelet),
+
+* [__KaKa Ng__](https://github.com/climba03003),
+
* [__Luis Orbaiceta__](https://github.com/luisorbaiceta),
- ,
+ ,
* [__Maksim Sinik__](https://github.com/fox1t),
- ,
+ ,
* [__Manuel Spigolon__](https://github.com/eomm),
- ,
+ ,
* [__James Sumners__](https://github.com/jsumners),
- ,
+ ,
### Fastify Plugins team
-* [__Matteo Collina__](https://github.com/mcollina),
- ,
* [__Harry Brundage__](https://github.com/airhorns/),
- ,
+ ,
+* [__Simone Busoli__](https://github.com/simoneb),
+ ,
+* [__Dan Castillo__](https://github.com/dancastillo),
+
+* [__Matteo Collina__](https://github.com/mcollina),
+ ,
+* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
+
* [__Tomas Della Vedova__](https://github.com/delvedor),
- ,
-* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
- ,
+ ,
+* [__Carlos Fuentes__](https://github.com/metcoder95),
+ ,
* [__Vincent Le Goff__](https://github.com/zekth)
-* [__Salman Mitha__](https://github.com/salmanm),
-
+* [__Jean Michelet__](https://github.com/jean-michelet),
+
+* [__KaKa Ng__](https://github.com/climba03003),
+
* [__Maksim Sinik__](https://github.com/fox1t),
- ,
+ ,
* [__Frazer Smith__](https://github.com/Fdawgs),
* [__Manuel Spigolon__](https://github.com/eomm),
- ,
-* [__Rafael Gonzaga__](https://github.com/rafaelgss),
- ,
+ ,
+* [__Antonio Tripodi__](https://github.com/Tony133),
-### 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), ,
-
-* [__Luciano Mammino__](https://github.com/lmammino),
- ,
-* [__Evan Shortiss__](https://github.com/evanshortiss),
- ,
-
-**Past Collaborators**
+* [__Tommaso Allevi__](https://github.com/allevo),
+ ,
+* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
+ ,
* [__Çağatay Çalı__](https://github.com/cagataycali),
- ,
+ ,
+* [__David Mark Clements__](https://github.com/davidmarkclements),
+ ,
+
+* [__dalisoft__](https://github.com/dalisoft), ,
+
+* [__Dustin Deus__](https://github.com/StarpTech),
+ ,
+* [__Denis Fäcke__](https://github.com/SerayaEryn),
+ ,
+* [__Rafael Gonzaga__](https://github.com/rafaelgss),
+ ,
* [__Trivikram Kamat__](https://github.com/trivikr),
- ,
+ ,
+* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
+ ,
* [__Cemre Mengu__](https://github.com/cemremengu),
- ,
+ ,
+* [__Salman Mitha__](https://github.com/salmanm),
+
* [__Nathan Woltman__](https://github.com/nwoltman),
- ,
+ ,
## Hosted by
[
](https://openjsf.org/projects/#growth)
+width="250px;"/>](https://openjsf.org/projects)
-We are a [Growth
-Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#growth-stage)
+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/).
-## Acknowledgements
+## Sponsors
+
+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.
+
+## Acknowledgments
This project is kindly sponsored by:
-- [nearForm](https://nearform.com)
+- [NearForm](https://nearform.com)
+- [Platformatic](https://platformatic.dev)
Past Sponsors:
- [LetzDoIt](https://www.letzdoitapp.com/)
+This list includes all companies that support one or more team members
+in maintaining this project.
+
## License
Licensed under [MIT](./LICENSE).
@@ -390,3 +418,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/
diff --git a/SECURITY.md b/SECURITY.md
index 23ea944aa5d..f4802b4e6c0 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -3,11 +3,66 @@
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 security policy](https://github.com/nodejs/node/blob/main/SECURITY.md).
+
+**Trusted:** Application code (plugins, handlers, hooks, schemas), configuration,
+and the runtime environment.
+
+**Untrusted:** All network input (HTTP headers, body, query strings, URL
+parameters).
+
+Fastify assumes Node.js is running with `insecureHTTPParser: false` (the
+secure default). Deployments that enable `insecureHTTPParser: true` are
+outside Fastify's threat model.
+
+### 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)
+- **`insecureHTTPParser: true` deployments**: Reports that rely on enabling
+Node.js `insecureHTTPParser` are out of scope; Fastify assumes this flag is
+`false`
+- **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 HackerOne page:
-[https://hackerone.com/fastify](https://hackerone.com/fastify).
+complete a vulnerability report via the
+[GitHub Security page](https://github.com/fastify/fastify/security/advisories/new).
+
+Do not assign or request a CVE directly.
+CVE assignment is handled by the Fastify Security Team.
+Fastify falls under the [OpenJS CNA](https://cna.openjsf.org/).
+A CVE will be assigned as part of our responsible disclosure process.
+
+> ℹ️ Note:
+> Fastify's [HackerOne](https://hackerone.com/fastify) program is now closed.
### Strict measures when reporting vulnerabilities
@@ -15,23 +70,32 @@ 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 on HackerOne. Only create new
- HackerOne reports on a vulnerability if you are absolutely sure this should be
+* 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 and will flag them as
- such for their customers (think about snyk, npm audit, ...).
-* HackerOne reports should never be created and triaged by the same person. If
- you are creating a HackerOne report for a vulnerability that you found, or on
+ tracking any new vulnerabilities reported on 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 HackerOne
-process.
+⚠ The Fastify project does not support any reporting outside the process mentioned
+in this document.
## Handling vulnerability reports
@@ -45,18 +109,15 @@ 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
+* **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
@@ -84,25 +145,28 @@ The report's vulnerable versions upper limit should be set to:
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)
+v.3](https://www.first.org/cvss/user-guide).
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 HackerOne platform through
-the UI, which should include the Report ID and a summary.
+### Secondary Contact
-Within HackerOne, this is handled through a "public disclosure request".
+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 (or
+`security@lists.openjsf.org`) for assistance.
-Reference: [HackerOne:
-Disclosure](https://docs.hackerone.com/hackers/disclosure.html)
+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 HackerOne program and this
-policy and process.
+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
@@ -114,10 +178,34 @@ work as a member of the Fastify Core team.
### Members
* [__Matteo Collina__](https://github.com/mcollina),
- ,
+ ,
* [__Tomas Della Vedova__](https://github.com/delvedor),
- ,
+ ,
* [__Vincent Le Goff__](https://github.com/zekth)
* [__KaKa Ng__](https://github.com/climba03003)
* [__James Sumners__](https://github.com/jsumners),
- ,
+ ,
+
+## OpenSSF CII Best Practices
+
+[](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.
diff --git a/SPONSORS.md b/SPONSORS.md
new file mode 100644
index 00000000000..36346e172d3
--- /dev/null
+++ b/SPONSORS.md
@@ -0,0 +1,24 @@
+# Sponsors
+
+All active sponsors of Fastify are listed here, in order of contribution!
+Our sponsors are the reason why we can work on some issues or features
+that otherwise would be impossible to do.
+
+If you want to become a sponsor, please check out our [Open Collective page](https://opencollective.com/fastify)
+or [GitHub Sponsors](https://github.com/sponsors/fastify)!
+
+## Tier 4
+
+- [SerpApi](https://serpapi.com/?utm_source=fastify)
+
+## 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)
+- [TestMu AI](https://www.testmu.ai/)
+
+## Tier 2
+
+_Be the first!_
diff --git a/build/build-error-serializer.js b/build/build-error-serializer.js
index d666bd38ea1..d7a0cd2e3c7 100644
--- a/build/build-error-serializer.js
+++ b/build/build-error-serializer.js
@@ -2,8 +2,8 @@
'use strict'
const FJS = require('fast-json-stringify')
-const path = require('path')
-const fs = require('fs')
+const path = require('node:path')
+const fs = require('node:fs')
const code = FJS({
type: 'object',
@@ -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 */
diff --git a/build/build-validation.js b/build/build-validation.js
index 5439355ffe8..d20213a79bc 100644
--- a/build/build-validation.js
+++ b/build/build-validation.js
@@ -2,20 +2,21 @@
const AjvStandaloneCompiler = require('@fastify/ajv-compiler/standalone')
const { _ } = require('ajv')
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+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`)
}
@@ -27,21 +28,30 @@ const defaultInitOptions = {
forceCloseConnections: undefined, // keep-alive connections
maxRequestsPerSocket: 0, // no limit
requestTimeout: 0, // no limit
+ handlerTimeout: 0, // no timeout (disabled by default)
bodyLimit: 1024 * 1024, // 1 MiB
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
+ exposeHeadRoutes: true,
+ useSemicolonDelimiter: false,
+ allowErrorHandlerOverride: true, // TODO: set to false in v6
+ routerOptions: {
+ ignoreTrailingSlash: false,
+ ignoreDuplicateSlashes: false,
+ maxParamLength: 100,
+ allowUnsafeRegex: false,
+ useSemicolonDelimiter: false
+ }
}
const schema = {
@@ -63,6 +73,7 @@ const schema = {
},
maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true },
requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout },
+ handlerTimeout: { type: 'integer', default: defaultInitOptions.handlerTimeout },
bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit },
caseSensitive: { type: 'boolean', default: defaultInitOptions.caseSensitive },
allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.allowUnsafeRegex },
@@ -89,26 +100,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: { 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 },
- // deprecated style of passing the versioning constraint
- versioning: {
+ useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter },
+ 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: {
@@ -119,9 +130,9 @@ const schema = {
additionalProperties: true,
properties: {
name: { type: 'string' },
- storage: { },
- validate: { },
- deriveConstraint: { }
+ storage: {},
+ validate: {},
+ deriveConstraint: {}
}
}
}
diff --git a/build/sync-version.js b/build/sync-version.js
index 5d00f216ed9..b26cced3834 100644
--- a/build/sync-version.js
+++ b/build/sync-version.js
@@ -1,7 +1,7 @@
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
// package.json:version -> fastify.js:VERSION
const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8'))
diff --git a/docs/Guides/Benchmarking.md b/docs/Guides/Benchmarking.md
index 5e2e990ba95..2aeac0a2736 100644
--- a/docs/Guides/Benchmarking.md
+++ b/docs/Guides/Benchmarking.md
@@ -1,18 +1,18 @@
Fastify
## 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
diff --git a/docs/Guides/Contributing.md b/docs/Guides/Contributing.md
index 3cd8be41d0e..d2df29e935d 100644
--- a/docs/Guides/Contributing.md
+++ b/docs/Guides/Contributing.md
@@ -6,9 +6,10 @@ receive your support and knowledge. This guide is our attempt to help you help
us.
> ## Note
-> This is an informal guide. Please review the formal [CONTRIBUTING
-> document](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md) for
-> full details and our [Developer Certificate of
+> This is an informal guide. For full details, please review the formal
+> [CONTRIBUTING
+> document](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md)
+> our [Developer Certificate of
> Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin).
## Table Of Contents
@@ -50,7 +51,7 @@ should expect from others):
* We have a [Code of
Conduct](https://github.com/fastify/fastify/blob/main/CODE_OF_CONDUCT.md). You
must adhere to it to participate in this project.
-* If you open a pull request, please ensure that your contribution passes all
+* If you open a pull request, please ensure your contribution passes all
tests. If there are test failures, you will need to address them before we can
merge your contribution.
@@ -79,9 +80,11 @@ https://github.com/github/opensource.guide/blob/2868efbf0c14aec821909c19e210c360
Please adhere to the project's code and documentation style. Some popular tools
that automatically "correct" code and documentation do not follow a style that
-conforms to the styles this project uses. Notably, this project uses
+conforms to this project's styles. Notably, this project uses
[StandardJS](https://standardjs.com) for code formatting.
+[](https://gitpod.io/#https://github.com/fastify/fastify)
+
### Using Visual Studio Code
@@ -114,7 +117,7 @@ mkdir -p /Applications/VSCodeFastify/code-portable-data/{user-data,extensions}
Before continuing, we need to add the `code` command to your terminal's `PATH`.
To do so, we will [manually add VSCode to the
-`PATH`](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line).
+`PATH`](https://code.visualstudio.com/docs/setup/mac#_launch-vs-code-from-the-command-line).
As outlined in that document, the instructions vary depending on your default
shell, so you should follow the instructions in that guide as relates to your
preferred shell. However, we will tweak them slightly by defining an alias
@@ -162,9 +165,11 @@ the left sidebar. But wait! We are not quite done yet. There are a few more
baseline settings that should be set before VSCode is ready.
Press `cmd+shift+p` to bring up the VSCode command input prompt. Type `open
-settings (json)` and then choose the same item from the filtered menu. This will
-open a document that is the settings for the editor. Paste the following JSON
-into this document, overwriting any text already present, and save it:
+settings (json)`. Three [VSCode Setting](https://code.visualstudio.com/docs/getstarted/settings)
+options will appear in the dropdown: Workspace, Default,
+and User settings. We recommend selecting Default. This will open a document
+that is the settings for the editor. Paste the following JSON into this
+document, overwriting any text already present, and save it:
```json
{
diff --git a/docs/Guides/Database.md b/docs/Guides/Database.md
index d9d9b09754d..f63b7bdfb84 100644
--- a/docs/Guides/Database.md
+++ b/docs/Guides/Database.md
@@ -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,23 +126,22 @@ 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'
})
-fastify.get('/user/:id', function (req, reply) {
+fastify.get('/user/:id', async function (req, reply) {
// Or this.mongo.client.db('mydb').collection('users')
const users = this.mongo.db.collection('users')
// if the id is an ObjectId format, you need to create a new ObjectId
const id = this.mongo.ObjectId(req.params.id)
- users.findOne({ id }, (err, user) => {
- if (err) {
- reply.send(err)
- return
- }
- reply.send(user)
- })
+ try {
+ const user = await users.findOne({ id })
+ return user
+ } catch (err) {
+ return err
+ }
})
fastify.listen({ port: 3000 }, err => {
@@ -179,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
@@ -238,15 +237,15 @@ development. Migrations provide a repeatable and testable way to modify a
database's schema and prevent data loss.
As stated at the beginning of the guide, Fastify is database agnostic and any
-NodeJS database migration tool can be used with it. We will give an example of
+Node.js database migration tool can be used with it. We will give an example of
using [Postgrator](https://www.npmjs.com/package/postgrator) which has support
for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check
[migrate-mongo](https://www.npmjs.com/package/migrate-mongo).
#### [Postgrator](https://www.npmjs.com/package/postgrator)
-Postgrator is NodeJS SQL migration tool that uses a directory of SQL scripts to
-alter the database schema. Each file an migrations folder need to follow the
+Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to
+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).
@@ -276,18 +275,20 @@ CREATE TABLE IF NOT EXISTS users (
```javascript
const pg = require('pg')
const Postgrator = require('postgrator')
-const path = require('path')
+const path = require('node:path')
async function migrate() {
const client = new pg.Client({
host: 'localhost',
port: 5432,
- database: 'example',
+ database: 'example',
user: 'example',
password: 'example',
});
try {
+ await client.connect();
+
const postgrator = new Postgrator({
migrationPattern: path.join(__dirname, '/migrations/*'),
driver: 'pg',
@@ -309,10 +310,10 @@ async function migrate() {
process.exitCode = 0
} catch(err) {
- console.error(error)
+ console.error(err)
process.exitCode = 1
}
-
+
await client.end()
}
diff --git a/docs/Guides/Delay-Accepting-Requests.md b/docs/Guides/Delay-Accepting-Requests.md
index 9a45ec26db5..acb2fdaa952 100644
--- a/docs/Guides/Delay-Accepting-Requests.md
+++ b/docs/Guides/Delay-Accepting-Requests.md
@@ -49,7 +49,7 @@ That will be achieved by wrapping into a custom plugin two main features:
1. the mechanism for authenticating with the provider
[decorating](../Reference/Decorators.md) the `fastify` object with the
-authentication key (`magicKey` from here onwards)
+authentication key (`magicKey` from here onward)
1. the mechanism for denying requests that would, otherwise, fail
### Hands-on
@@ -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)
@@ -135,7 +135,7 @@ follows:
```js
const { fetch } = require('undici')
-const { setTimeout } = require('timers/promises')
+const { setTimeout } = require('node:timers/promises')
const MAGIC_KEY = '12345'
@@ -249,7 +249,7 @@ server.listen({ port: '1234' })
```js
const { fetch } = require('undici')
-const { setTimeout } = require('timers/promises')
+const { setTimeout } = require('node:timers/promises')
const MAGIC_KEY = '12345'
@@ -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:
```sh
diff --git a/docs/Guides/Detecting-When-Clients-Abort.md b/docs/Guides/Detecting-When-Clients-Abort.md
new file mode 100644
index 00000000000..b2dee213636
--- /dev/null
+++ b/docs/Guides/Detecting-When-Clients-Abort.md
@@ -0,0 +1,172 @@
+Fastify
+
+# Detecting When Clients Abort
+
+## 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
+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
+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
+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
+logging purposes or halting business logic.
+
+### Hands-on
+
+Say we have the following base server set up:
+
+```js
+import Fastify from 'fastify';
+
+const sleep = async (time) => {
+ return await new Promise(resolve => setTimeout(resolve, time || 1000));
+}
+
+const app = Fastify({
+ logger: {
+ transport: {
+ target: 'pino-pretty',
+ options: {
+ translateTime: 'HH:MM:ss Z',
+ ignore: 'pid,hostname',
+ },
+ },
+ },
+})
+
+app.addHook('onRequest', async (request, reply) => {
+ request.raw.on('close', () => {
+ if (request.raw.aborted) {
+ app.log.info('request closed')
+ }
+ })
+})
+
+app.get('/', async (request, reply) => {
+ await sleep(3000)
+ reply.code(200).send({ ok: true })
+})
+
+const start = async () => {
+ try {
+ await app.listen({ port: 3000 })
+ } catch (err) {
+ app.log.error(err)
+ process.exit(1)
+ }
+}
+
+start()
+```
+
+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
+of `{ ok: true }`.
+- An onRequest hook that triggers when every request is received.
+- Logic that triggers in the hook when the request is closed.
+- Logging that occurs when the closed request property `aborted` is true.
+
+Whilst the `aborted` property has been deprecated, `destroyed` is not a
+suitable replacement as the
+[Node.js documentation suggests](https://nodejs.org/api/http.html#requestaborted).
+A request can be `destroyed` for various reasons, such as when the server closes
+the connection. The `aborted` property is still the most reliable way to detect
+when a client intentionally aborts a request.
+
+You can also perform this logic outside of a hook, directly in a specific route.
+
+```js
+app.get('/', async (request, reply) => {
+ request.raw.on('close', () => {
+ if (request.raw.aborted) {
+ app.log.info('request closed')
+ }
+ })
+ await sleep(3000)
+ reply.code(200).send({ ok: true })
+})
+```
+
+At any point in your business logic, you can check if the request has been
+aborted and perform alternative actions.
+
+```js
+app.get('/', async (request, reply) => {
+ await sleep(3000)
+ if (request.raw.aborted) {
+ // do something here
+ }
+ await sleep(3000)
+ reply.code(200).send({ ok: true })
+})
+```
+
+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
+request with logic to abort the request before 3 seconds. Example:
+
+```js
+const controller = new AbortController();
+const signal = controller.signal;
+
+(async () => {
+ try {
+ const response = await fetch('http://localhost:3000', { signal });
+ const body = await response.text();
+ console.log(body);
+ } catch (error) {
+ console.error(error);
+ }
+})();
+
+setTimeout(() => {
+ controller.abort()
+}, 1000);
+```
+
+With either approach, you should see the Fastify log appear at the moment the
+request is aborted.
+
+## Conclusion
+
+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
+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
+client aborts a request.
diff --git a/docs/Guides/Ecosystem.md b/docs/Guides/Ecosystem.md
index 44760038680..15dd405140f 100644
--- a/docs/Guides/Ecosystem.md
+++ b/docs/Guides/Ecosystem.md
@@ -11,9 +11,7 @@ section.
- [`@fastify/accepts`](https://github.com/fastify/fastify-accepts) to have
[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 `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
+ to serialize to output according to the `Accept` header.
- [`@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,14 +40,14 @@ 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/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
configuration.
- [`@fastify/etag`](https://github.com/fastify/fastify-etag) Automatically
generate ETags for HTTP responses.
+- [`@fastify/express`](https://github.com/fastify/fastify-express) Express
+ compatibility layer for Fastify.
- [`@fastify/flash`](https://github.com/fastify/fastify-flash) Set and get flash
messages using the session.
- [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) Plugin to
@@ -66,6 +64,8 @@ section.
your HTTP requests to another server, with hooks.
- [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) JWT utils for
Fastify, internally uses [fast-jwt](https://github.com/nearform/fast-jwt).
+- [`@fastify/kafka`](https://github.com/fastify/fastify-kafka) Plugin to interact
+ with Apache Kafka.
- [`@fastify/leveldb`](https://github.com/fastify/fastify-leveldb) Plugin to
share a common LevelDB connection across Fastify.
- [`@fastify/middie`](https://github.com/fastify/middie) Middleware engine for
@@ -75,13 +75,29 @@ section.
connection pool across every part of your server.
- [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart
support for Fastify.
+- [`@fastify/mysql`](https://github.com/fastify/fastify-mysql) Fastify MySQL
+ connection plugin.
+- [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React
+ server-side rendering support for Fastify with
+ [Next](https://github.com/vercel/next.js/).
- [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around
[`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
PostgreSQL connection plugin, with this you can share the same PostgreSQL
connection pool in every part of your server.
- [`@fastify/rate-limit`](https://github.com/fastify/fastify-rate-limit) A low
overhead rate limiter for your routes.
+- [`@fastify/redis`](https://github.com/fastify/fastify-redis) Fastify Redis
+ connection plugin, with which you can share the same Redis connection across
+ every part of your server.
+- [`@fastify/reply-from`](https://github.com/fastify/fastify-reply-from) Plugin
+ to forward the current HTTP request to another server.
- [`@fastify/request-context`](https://github.com/fastify/fastify-request-context)
Request-scoped storage, based on
[AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage)
@@ -89,29 +105,39 @@ section.
providing functionality similar to thread-local storages.
- [`@fastify/response-validation`](https://github.com/fastify/fastify-response-validation)
A simple plugin that enables response validation for Fastify.
-- [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React
- server-side rendering support for Fastify with
- [Next](https://github.com/zeit/next.js/).
-- [`@fastify/redis`](https://github.com/fastify/fastify-redis) Fastify Redis
- connection plugin, with which you can share the same Redis connection across
- every part of your server.
-- [`@fastify/reply-from`](https://github.com/fastify/fastify-reply-from) Plugin
- to forward the current HTTP request to another server.
- [`@fastify/routes`](https://github.com/fastify/fastify-routes) Plugin that
provides a `Map` of routes.
+- [`@fastify/routes-stats`](https://github.com/fastify/fastify-routes-stats)
+ Provide stats for routes using `node:perf_hooks`.
- [`@fastify/schedule`](https://github.com/fastify/fastify-schedule) Plugin for
scheduling periodic jobs, based on
[toad-scheduler](https://github.com/kibertoad/toad-scheduler).
+- [`@fastify/secure-session`](https://github.com/fastify/fastify-secure-session)
+ Create a secure stateless cookie session for Fastify.
- [`@fastify/sensible`](https://github.com/fastify/fastify-sensible) Defaults
for Fastify that everyone can agree on. It adds some useful decorators such as
HTTP errors and assertions, but also more request and reply methods.
- [`@fastify/session`](https://github.com/fastify/session) a session 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
serving Swagger/OpenAPI documentation for Fastify, supporting dynamic
generation.
+- [`@fastify/swagger-ui`](https://github.com/fastify/fastify-swagger-ui) Plugin
+ for serving Swagger UI.
+- [`@fastify/throttle`](https://github.com/fastify/fastify-throttle) Plugin for
+ throttling the download speed of a request.
+- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
+ Fastify
+ [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
+ for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts).
+- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
+ Fastify
+ [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
+ for [Typebox](https://github.com/sinclairzx81/typebox).
- [`@fastify/under-pressure`](https://github.com/fastify/under-pressure) Measure
process load with automatic handling of _"Service Unavailable"_ plugin for
Fastify.
@@ -119,13 +145,35 @@ section.
the `Request` object with a method to access raw URL components.
- [`@fastify/view`](https://github.com/fastify/point-of-view) Templates
rendering (_ejs, pug, handlebars, marko_) plugin support for Fastify.
+- [`@fastify/vite`](https://github.com/fastify/fastify-vite) Integration with
+ [Vite](https://vitejs.dev/), allows for serving SPA/MPA/SSR Vite applications.
- [`@fastify/websocket`](https://github.com/fastify/fastify-websocket) WebSocket
support for Fastify. Built upon [ws](https://github.com/websockets/ws).
+- [`@fastify/zipkin`](https://github.com/fastify/fastify-zipkin) Plugin
+ for Zipkin distributed tracing system.
#### [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/clerk/javascript/tree/main/packages/fastify)
+ Add authentication and user management to your Fastify application with Clerk.
- [`@coobaha/typed-fastify`](https://github.com/Coobaha/typed-fastify) Strongly
typed routes with a runtime validation using JSON schema generated from types.
- [`@dnlup/fastify-doc`](https://github.com/dnlup/fastify-doc) A plugin for
@@ -134,19 +182,37 @@ 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.
+
+
+- [`@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.
+- [`@glidemq/fastify`](https://github.com/avifenesh/glidemq-fastify)
+ Queue management plugin for glide-mq with REST API endpoints, SSE events,
+ and in-memory testing mode. Powered by Valkey/Redis Streams.
- [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server)
- Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your
+ Tiny (~5k), Fast, KISS, and dependency-free Node.js library to make your
Fastify API graceful.
- [`@h4ad/serverless-adapter`](https://github.com/H4ad/serverless-adapter)
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!
+- [`@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.
+- [`@matths/fastify-svelte-view`](https://github.com/matths/fastify-svelte-view)
+ A Fastify plugin for rendering Svelte components with support for SSR
+ (Server-Side Rendering), CSR (Client-Side Rendering), and SSR with hydration.
- [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit)
A plugin to close the server gracefully
- [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger)
@@ -159,18 +225,33 @@ 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
+- [`@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
-- [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify)
- Run an [Apollo Server](https://github.com/apollographql/apollo-server) to
- serve GraphQL with Fastify.
-- [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for
+- [`@yeliex/fastify-problem-details`](https://github.com/yeliex/fastify-problem-details)
+ RFC 9457 Problem Details implementation for Fastify, with typed HTTP errors.
+- [`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/ducktors/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)
+ A Fastify plugin to log requests and subsegments through AWSXray.
- [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware
for CLS-based request ID generation. An out-of-the-box solution for adding
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
+ return 204 status on empty response.
- [`fastify-405`](https://github.com/Eomm/fastify-405) Fastify plugin that adds
405 HTTP status to your routes
- [`fastify-allow`](https://github.com/mattbishop/fastify-allow) Fastify plugin
@@ -178,7 +259,7 @@ section.
405 responses for routes that have a handler but not for the request's method.
- [`fastify-amqp`](https://github.com/RafaelGSS/fastify-amqp) Fastify AMQP
connection plugin, to use with RabbitMQ or another connector. Just a wrapper
- to [`amqplib`](https://github.com/squaremo/amqp.node).
+ to [`amqplib`](https://github.com/amqp-node/amqplib).
- [`fastify-amqp-async`](https://github.com/kffl/fastify-amqp-async) Fastify
AMQP plugin with a Promise-based API provided by
[`amqplib-as-promised`](https://github.com/twawszczak/amqplib-as-promised).
@@ -187,24 +268,37 @@ section.
[`@angular/platform-server`](https://github.com/angular/angular/tree/master/packages/platform-server)
for Fastify
- [`fastify-api-key`](https://github.com/arkerone/fastify-api-key) Fastify
- plugin to authenticate HTTP requests based on api key and signature
-- [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify
+ plugin to authenticate HTTP requests based on API key and signature
+- [`fastify-appwrite`](https://github.com/maniecodes/fastify-appwrite) Fastify
Plugin for interacting with Appwrite server.
+- [`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
+ MySQL plugin with auto SQL injection attack prevention.
+- [`fastify-at-postgres`](https://github.com/mateonunez/fastify-at-postgres) Fastify
+ Postgres plugin with auto SQL injection attack prevention.
- [`fastify-auth0-verify`](https://github.com/nearform/fastify-auth0-verify):
Auth0 verification plugin for Fastify, internally uses
[fastify-jwt](https://npm.im/fastify-jwt) and
[jsonwebtoken](https://npm.im/jsonwebtoken).
-- [`fastify-autocrud`](https://github.com/paranoiasystem/fastify-autocrud)
- Plugin to auto-generate CRUD routes as fast as possible.
- [`fastify-autoroutes`](https://github.com/GiovanniCardamone/fastify-autoroutes)
Plugin to scan and load routes based on filesystem path from a custom
directory.
+- [`fastify-aws-sns`](https://github.com/gzileni/fastify-aws-sns) Fastify plugin
+ for AWS Simple Notification Service (AWS SNS) that coordinates and manages
+ the delivery or sending of messages to subscribing endpoints or clients.
+- [`fastify-aws-timestream`](https://github.com/gzileni/fastify-aws-timestream)
+ Fastify plugin for managing databases, tables, and querying and creating
+ scheduled queries with AWS Timestream.
- [`fastify-axios`](https://github.com/davidedantonio/fastify-axios) Plugin to
send HTTP requests via [axios](https://github.com/axios/axios).
- [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for
development servers that require Babel transformations of JavaScript sources.
-- [`fastify-bcrypt`](https://github.com/heply/fastify-bcrypt) A Bcrypt hash
+- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
generator & checker.
+- [`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.
- [`fastify-bookshelf`](https://github.com/butlerx/fastify-bookshelfjs) Fastify
@@ -213,6 +307,11 @@ section.
to add [boom](https://github.com/hapijs/boom) support.
- [`fastify-bree`](https://github.com/climba03003/fastify-bree) Fastify plugin
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)
+ 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
for Fastify.
- [`fastify-casbin-rest`](https://github.com/nearform/fastify-casbin-rest)
@@ -224,18 +323,27 @@ section.
- [`fastify-cloudevents`](https://github.com/smartiniOnGitHub/fastify-cloudevents)
Fastify plugin to generate and forward Fastify events in the Cloudevents
format.
+- [`fastify-cloudflare-turnstile`](https://github.com/112RG/fastify-cloudflare-turnstile)
+ Fastify plugin for CloudFlare Turnstile.
+- [`fastify-cloudinary`](https://github.com/Vanilla-IceCream/fastify-cloudinary)
+ Plugin to share a common Cloudinary connection across Fastify.
- [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb)
Fastify plugin to connect to a CockroachDB PostgreSQL instance via the
Sequelize ORM.
+- [`fastify-constraints`](https://github.com/nearform/fastify-constraints)
+ Fastify plugin to add constraints to multiple routes
- [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify
plugin to add CouchDB support via [nano](https://github.com/apache/nano).
-- [`fastify-crud-generator`](https://github.com/heply/fastify-crud-generator) A
- plugin to rapidly generate CRUD routes for any entity.
+- [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator)
+ A plugin to rapidly generate CRUD routes for any entity.
- [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck)
Fastify plugin to add health route in your server that asserts custom
functions.
- [`fastify-decorators`](https://github.com/L2jLiga/fastify-decorators) Fastify
plugin that provides the set of TypeScript decorators.
+- [`fastify-delay-request`](https://github.com/climba03003/fastify-delay-request)
+ Fastify plugin that allows requests to be delayed whilst a task the response is
+ dependent on is run, such as a resource intensive process.
- [`fastify-disablecache`](https://github.com/Fdawgs/fastify-disablecache)
Fastify plugin to disable client-side caching, inspired by
[nocache](https://github.com/helmetjs/nocache).
@@ -245,9 +353,6 @@ section.
object.
- [`fastify-dynareg`](https://github.com/greguz/fastify-dynareg) Dynamic plugin
register for Fastify.
-- [`fastify-early-hints`](https://github.com/zekth/fastify-early-hints) Plugin
- to add HTTP 103 feature based on [RFC
- 8297](https://httpwg.org/specs/rfc8297.html)
- [`fastify-envalid`](https://github.com/alemagio/fastify-envalid) Fastify
plugin to integrate [envalid](https://github.com/af/envalid) in your Fastify
project.
@@ -256,6 +361,11 @@ section.
- [`fastify-esso`](https://github.com/patrickpissurno/fastify-esso) The easiest
authentication plugin for Fastify, with built-in support for Single sign-on
(and great documentation).
+- [`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
+ [Evervault](https://evervault.com/) client.
- [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of
your decorators across all the encapsulated contexts.
- [`fastify-favicon`](https://github.com/smartiniOnGitHub/fastify-favicon)
@@ -263,18 +373,11 @@ 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-router`](https://github.com/bhouston/fastify-file-router)
+ A typesafe TanStack Start / Next.JS-style router with JSON + Zod schema support.
- [`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)
- Fastify plugin for uploading files.
-- [`fastify-firebase`](https://github.com/now-ims/fastify-firebase) Fastify
- plugin for [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup)
- to Fastify so you can easily use Firebase Auth, Firestore, Cloud Storage,
- Cloud Messaging, and more.
-- [`fastify-firebase-auth`](https://github.com/oxsav/fastify-firebase-auth)
- Firebase Authentication for Fastify supporting all of the methods relating to
- the authentication API.
- [`fastify-formidable`](https://github.com/climba03003/fastify-formidable)
Handy plugin to provide multipart support and fastify-swagger integration.
- [`fastify-gcloud-trace`](https://github.com/mkinoshi/fastify-gcloud-trace)
@@ -296,6 +399,10 @@ section.
Providers.
- [`fastify-guard`](https://github.com/hsynlms/fastify-guard) A Fastify plugin
that protects endpoints by checking authenticated user roles and/or scopes.
+- [`fastify-hana`](https://github.com/yoav0gal/fastify-hana) connects your
+ application to [`SAP-HANA`](https://help.sap.com/docs/SAP_HANA_CLIENT).
+- [`fastify-hashids`](https://github.com/andersonjoseph/fastify-hashids) A Fastify
+ plugin to encode/decode IDs using [hashids](https://github.com/niieani/hashids.js).
- [`fastify-hasura`](https://github.com/ManUtopiK/fastify-hasura) A Fastify
plugin to have fun with [Hasura](https://github.com/hasura/graphql-engine).
- [`fastify-healthcheck`](https://github.com/smartiniOnGitHub/fastify-healthcheck)
@@ -303,41 +410,63 @@ section.
- [`fastify-hemera`](https://github.com/hemerajs/fastify-hemera) Fastify Hemera
plugin, for writing reliable & fault-tolerant microservices with
[nats.io](https://nats.io/).
-- [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin
- to send HTTP(s) requests. Built upon [urllib](https://github.com/node-modules/urllib).
+- [`fastify-hl7`](https://github.com/Bugs5382/fastify-hl7) A Fastify Plugin to
+ create a server, build, and send HL7 formatted Hl7 messages. Using
+ [node-hl7-client](https://github.com/Bugs5382/node-hl7-client) and
+ [node-hl7-server](https://github.com/Bugs5382/node-hl7-server) as the
+ underlining technology to do this.
- [`fastify-http-context`](https://github.com/thorough-developer/fastify-http-context)
Fastify plugin for "simulating" a thread of execution to allow for true HTTP
context to take place per API call within the Fastify lifecycle of calls.
- [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
An error handling plugin for Fastify that uses enhanced HTTP errors.
+- [`fastify-http-exceptions`](https://github.com/bhouston/fastify-http-exceptions)
+ Typed HTTP status exceptions which are automatically converted into Fastify responses.
- [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect
HTTP requests to HTTPS, both using the same port number, or different response
on HTTP and HTTPS.
+- [`fastify-https-always`](https://github.com/mattbishop/fastify-https-always)
+ Lightweight, proxy-aware redirect plugin from HTTP to HTTPS.
- [`fastify-https-redirect`](https://github.com/tomsvogel/fastify-https-redirect)
Fastify plugin for auto-redirect from HTTP to HTTPS.
+- [`fastify-i18n`](https://github.com/Vanilla-IceCream/fastify-i18n)
+ Internationalization plugin for Fastify. Built upon node-polyglot.
- [`fastify-impressions`](https://github.com/manju4ever/fastify-impressions)
Fastify plugin to track impressions of all the routes.
- [`fastify-influxdb`](https://github.com/alex-ppg/fastify-influxdb) Fastify
InfluxDB plugin connecting to an InfluxDB instance via the Influx default
package.
+- [`fastify-ip`](https://github.com/metcoder95/fastify-ip) A plugin
+ for Fastify that allows you to infer a request ID by a
+ given set of custom Request headers.
+- [`fastify-json-to-xml`](https://github.com/Fdawgs/fastify-json-to-xml) Fastify
+ plugin to serialize JSON responses into XML.
- [`fastify-jwt-authz`](https://github.com/Ethan-Arrowood/fastify-jwt-authz) JWT
user scope verifier.
- [`fastify-jwt-webapp`](https://github.com/charlesread/fastify-jwt-webapp) JWT
authentication for Fastify-based web apps.
- [`fastify-kafkajs`](https://github.com/kffl/fastify-kafkajs) Fastify plugin
that adds support for KafkaJS - a modern Apache Kafka client library.
-- [`fastify-knexjs`](https://github.com/chapuletta/fastify-knexjs) Fastify
- plugin for support KnexJS Query Builder.
-- [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock)
- Fastify Mock KnexJS for testing support.
+- [`fastify-keycloak-adapter`](https://github.com/yubinTW/fastify-keycloak-adapter)
+ A keycloak adapter for a Fastify app.
+- [`fastify-koa`](https://github.com/rozzilla/fastify-koa) Convert Koa
+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/)
+ changes the log level of your Fastify server at runtime.
- [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua
scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and
[lured](https://github.com/enobufs/lured).
@@ -353,8 +482,6 @@ section.
exporting [Prometheus](https://prometheus.io) metrics.
- [`fastify-minify`](https://github.com/Jelenkee/fastify-minify) Plugin for
minification and transformation of responses.
-- [`fastify-mongo-memory`](https://github.com/chapuletta/fastify-mongo-memory)
- Fastify MongoDB in Memory Plugin for testing support.
- [`fastify-mongodb-sanitizer`](https://github.com/KlemenKozelj/fastify-mongodb-sanitizer)
Fastify plugin that sanitizes client input to prevent
potential MongoDB query injection attacks.
@@ -367,33 +494,31 @@ section.
[mqtt](https://www.npmjs.com/package/mqtt) client across Fastify.
- [`fastify-msgpack`](https://github.com/kenriortega/fastify-msgpack) Fastify
and MessagePack, together at last. Uses @msgpack/msgpack by default.
+- [`fastify-msgraph-webhook`](https://github.com/flower-of-the-bridges/fastify-msgraph-change-notifications-webhook)
+ to manage
+ [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-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share
- [NATS](https://nats.io) client across Fastify.
+- [`fastify-multilingual`](https://github.com/gbrugger/fastify-multilingual) Unobtrusively
+ decorates fastify request with Polyglot.js for i18n.
- [`fastify-next-auth`](https://github.com/wobsoriano/fastify-next-auth)
NextAuth.js plugin for Fastify.
- [`fastify-no-additional-properties`](https://github.com/greguz/fastify-no-additional-properties)
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-nodemailer`](https://github.com/lependu/fastify-nodemailer) Plugin
- to share [nodemailer](https://nodemailer.com) transporter across Fastify.
-- [`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
rendering support for Fastify with Nuxt.js Framework.
- [`fastify-oas`](https://gitlab.com/m03geek/fastify-oas) Generates OpenAPI 3.0+
documentation from routes schemas for Fastify.
-- [`fastify-objectionjs`](https://github.com/jarcodallo/fastify-objectionjs)
- Plugin for the Fastify framework that provides integration with objectionjs
- ORM.
- [`fastify-objectionjs-classes`](https://github.com/kamikazechaser/fastify-objectionjs-classes)
Plugin to cherry-pick classes from objectionjs ORM.
+- [`fastify-opaque-apake`](https://github.com/squirrelchat/fastify-opaque-apake)
+ A Fastify plugin to implement the OPAQUE aPAKE protocol. Uses
+ [@squirrelchat/opaque-wasm-server](https://github.com/squirrelchat/opaque-wasm).
- [`fastify-openapi-docs`](https://github.com/ShogunPanda/fastify-openapi-docs)
A Fastify plugin that generates OpenAPI spec automatically.
- [`fastify-openapi-glue`](https://github.com/seriousme/fastify-openapi-glue)
@@ -406,19 +531,28 @@ section.
- [`fastify-oracle`](https://github.com/cemremengu/fastify-oracle) Attaches an
[`oracledb`](https://github.com/oracle/node-oracledb) connection pool to a
Fastify server instance.
-- [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify
- OrientDB connection plugin, with which you can share the OrientDB connection
- across every part of your server.
+- [`fastify-orama`](https://github.com/mateonunez/fastify-orama)
+- [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify
+ 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/heply/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
a Fastify project.
+- [`fastify-postgres-dot-js`](https://github.com/kylerush/fastify-postgresjs) Fastify
+ PostgreSQL connection plugin that uses [Postgres.js](https://github.com/porsager/postgres).
- [`fastify-prettier`](https://github.com/hsynlms/fastify-prettier) A Fastify
plugin that uses [prettier](https://github.com/prettier/prettier) under the
hood to beautify outgoing responses and/or other things in the Fastify server.
@@ -428,20 +562,29 @@ section.
Fastify and protobufjs, together at last. Uses protobufjs by default.
- [`fastify-qrcode`](https://github.com/chonla/fastify-qrcode) This plugin
utilizes [qrcode](https://github.com/soldair/node-qrcode) to generate QR Code.
-- [`fastify-qs`](https://github.com/webdevium/fastify-qs) A plugin for Fastify
+- [`fastify-qs`](https://github.com/vanodevium/fastify-qs) A plugin for Fastify
that adds support for parsing URL query parameters with
[qs](https://github.com/ljharb/qs).
+- [`fastify-rabbitmq`](https://github.com/Bugs5382/fastify-rabbitmq) Fastify
+ RabbitMQ plugin that uses
+ [node-rabbitmq-client](https://github.com/cody-greene/node-rabbitmq-client)
+ plugin as a wrapper.
- [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's
plugin that adds support to handle an aborted request asynchronous.
+- [`fastify-ravendb`](https://github.com/nearform/fastify-ravendb) RavenDB
+ connection plugin. It exposes the same `DocumentStore` (or multiple ones)
+ across the whole Fastify application.
- [`fastify-raw-body`](https://github.com/Eomm/fastify-raw-body) Add the
`request.rawBody` field.
- [`fastify-rbac`](https://gitlab.com/m03geek/fastify-rbac) Fastify role-based
access control plugin.
- [`fastify-recaptcha`](https://github.com/qwertyforce/fastify-recaptcha)
- Fastify plugin for recaptcha verification.
+ Fastify plugin for reCAPTCHA verification.
- [`fastify-redis-channels`](https://github.com/hearit-io/fastify-redis-channels)
A plugin for fast, reliable, and scalable channels implementation based on
Redis streams.
+- [`fastify-redis-session`](https://github.com/mohammadraufzahed/fastify-redis-session)
+ Redis Session plugin for fastify.
- [`fastify-register-routes`](https://github.com/israeleriston/fastify-register-routes)
Plugin to automatically load routes from a specified path and optionally limit
loaded file names by a regular expression.
@@ -458,35 +601,40 @@ section.
- [`fastify-rob-config`](https://github.com/jeromemacias/fastify-rob-config)
Fastify Rob-Config integration.
- [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group)
- Convenient grouping and inheritance of routes
+ 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)
Choose the JSON schema to use based on request parameters.
- [`fastify-schema-to-typescript`](https://github.com/thomasthiebaud/fastify-schema-to-typescript)
Generate typescript types based on your JSON/YAML validation schemas so they
are always in sync.
-- [`fastify-secure-session`](https://github.com/mcollina/fastify-secure-session)
- Create a secure stateless cookie session for Fastify.
- [`fastify-sentry`](https://github.com/alex-ppg/fastify-sentry) Fastify plugin
to add the Sentry SDK error handler to requests.
- [`fastify-sequelize`](https://github.com/lyquocnam/fastify-sequelize) Fastify
- plugin work with Sequelize (adapter for NodeJS -> Sqlite, Mysql, Mssql,
+ plugin work with Sequelize (adapter for Node.js -> Sqlite, Mysql, Mssql,
Postgres).
- [`fastify-server-session`](https://github.com/jsumners/fastify-server-session)
A session plugin with support for arbitrary backing caches via
`fastify-caching`.
-- [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik
- plugin, with this you can use slonik in every part of your server.
-- [`fastify-soap-client`](https://github.com/fastify/fastify-soap-client) a SOAP
- client plugin for Fastify.
-- [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a
- Socket.io plugin for Fastify.
+- [`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-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin
+ to delay the response from the server.
- [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator)
Small plugin to allow you use multiple validators in one route based on each
HTTP part of the request.
+- [`fastify-sqlite`](https://github.com/Eomm/fastify-sqlite) connects your
+ application to a sqlite3 database.
+- [`fastify-sqlite-typed`](https://github.com/yoav0gal/fastify-sqlite-typed) connects
+ your application to a SQLite database with full Typescript support.
- [`fastify-sse`](https://github.com/lolo32/fastify-sse) to provide Server-Sent
Events with `reply.sse( … )` to Fastify.
-- [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide
- Server-Sent Events using Async Iterators (supports newer versions of Fastify).
- [`fastify-ssr-vite`](https://github.com/nineohnine/fastify-ssr-vite) A simple
plugin for setting up server side rendering with vite.
- [`fastify-stripe`](https://github.com/coopflow/fastify-stripe) Plugin to
@@ -498,27 +646,33 @@ section.
- [`fastify-tls-keygen`](https://gitlab.com/sebdeckers/fastify-tls-keygen)
Automatically generate a browser-compatible, trusted, self-signed,
localhost-only, TLS certificate.
-- [`fastify-tokenize`](https://github.com/Bowser65/fastify-tokenize)
- [Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that
- removes the pain of managing authentication tokens, with built-in integration
- for `fastify-auth`.
-- [`fastify-totp`](https://github.com/heply/fastify-totp) A plugin to handle
+- [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle
TOTP (e.g. for 2FA).
-- [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools)
- Useful functions for Twitch Extension Backend Services (EBS).
+- [`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/effect).
+- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
+ Fastify
+ [type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
+ for [zod](https://github.com/colinhacks/zod).
- [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin)
Fastify plugin to work with TypeORM.
+- [`fastify-user-agent`](https://github.com/Eomm/fastify-user-agent) parses your
+ request's `user-agent` header.
+- [`fastify-uws`](https://github.com/geut/fastify-uws) A Fastify plugin to
+ use the web server [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js).
- [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy
subdomain HTTP requests to another server (useful if you want to point
multiple subdomains to the same IP address, while running different servers on
the same machine).
-- [`fastify-vite`](https://github.com/galvez/fastify-vite)
- [Vite](https://vitejs.dev/) plugin for Fastify with SSR data support.
- [`fastify-vue-plugin`](https://github.com/TheNoim/fastify-vue)
[Nuxt.js](https://nuxtjs.org) plugin for Fastify. Control the routes nuxt
should use.
- [`fastify-wamp-router`](https://github.com/lependu/fastify-wamp-router) Web
Application Messaging Protocol router for Fastify.
+- [`fastify-web-response`](https://github.com/erfanium/fastify-web-response)
+ Enables returning web streams objects `Response` and `ReadableStream` in routes.
- [`fastify-webpack-hmr`](https://github.com/lependu/fastify-webpack-hmr)
Webpack hot module reloading plugin for Fastify.
- [`fastify-webpack-hot`](https://github.com/gajus/fastify-webpack-hot) Webpack
@@ -529,8 +683,9 @@ section.
[uws](https://github.com/uNetworking/uWebSockets).
- [`fastify-xml-body-parser`](https://github.com/NaturalIntelligence/fastify-xml-body-parser)
Parse XML payload / request body into JS / JSON object.
-- [`fastify-xray`](https://github.com/jeromemacias/fastify-xray) Fastify plugin
- for AWS XRay recording.
+- [`http-wizard`](https://github.com/flodlc/http-wizard)
+ Exports a typescript API client for your Fastify API and ensures fullstack type
+ safety for your project.
- [`i18next-http-middleware`](https://github.com/i18next/i18next-http-middleware#fastify-usage)
An [i18next](https://www.i18next.com) based i18n (internationalization)
middleware to be used with Node.js web frameworks like Express or Fastify and
@@ -549,13 +704,23 @@ section.
- [`openapi-validator-middleware`](https://github.com/PayU/openapi-validator-middleware#fastify)
Swagger and OpenAPI 3.0 spec-based request validation middleware that supports
Fastify.
+- [`pubsub-http-handler`](https://github.com/simenandre/pubsub-http-handler) A Fastify
+ plugin to easily create Google Cloud PubSub endpoints.
- [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple
and lightweight Sequelize plugin for Fastify.
-- [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
- and updated Typeorm plugin for use with Fastify.
#### [Community Tools](#community-tools)
+
- [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration
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.
diff --git a/docs/Guides/Fluent-Schema.md b/docs/Guides/Fluent-Schema.md
index b3463bac2c6..a1f10cdfe6c 100644
--- a/docs/Guides/Fluent-Schema.md
+++ b/docs/Guides/Fluent-Schema.md
@@ -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,6 @@ 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`.
diff --git a/docs/Guides/Getting-Started.md b/docs/Guides/Getting-Started.md
index 8f89026628d..68a559004fd 100644
--- a/docs/Guides/Getting-Started.md
+++ b/docs/Guides/Getting-Started.md
@@ -14,11 +14,12 @@ Let's start!
Install with npm:
-```
+```sh
npm i fastify
```
+
Install with yarn:
-```
+```sh
yarn add fastify
```
@@ -31,6 +32,7 @@ Let's write our first server:
// ESM
import Fastify from 'fastify'
+
const fastify = Fastify({
logger: true
})
@@ -54,11 +56,20 @@ fastify.listen({ port: 3000 }, function (err, address) {
})
```
+> If you are using ECMAScript Modules (ESM) in your project, be sure to
+> include "type": "module" in your package.json.
+>```js
+>{
+> "type": "module"
+>}
+>```
+
Do you prefer to use `async/await`? Fastify supports it out-of-the-box.
```js
// ESM
import Fastify from 'fastify'
+
const fastify = Fastify({
logger: true
})
@@ -95,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
@@ -117,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
@@ -132,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
*/
@@ -171,13 +185,14 @@ fastify.listen({ port: 3000 }, function (err, address) {
})
```
+
```js
// our-first-route.js
/**
* Encapsulates the routes
* @param {FastifyInstance} fastify Encapsulated Fastify Instance
- * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
+ * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
*/
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
@@ -185,6 +200,10 @@ async function routes (fastify, options) {
})
}
+//ESM
+export default routes;
+
+// CommonJs
module.exports = routes
```
In this example, we used the `register` API, which is the core of the Fastify
@@ -208,7 +227,7 @@ Let's rewrite the above example with a database connection.
First, install `fastify-plugin` and `@fastify/mongodb`:
-```
+```sh
npm i fastify-plugin @fastify/mongodb
```
@@ -216,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
@@ -277,7 +296,7 @@ async function dbConnector (fastify, options) {
// Wrapping a plugin function with fastify-plugin exposes the decorators
// and hooks, declared inside the plugin to the parent scope.
-module.exports = fastifyPlugin(dbConnector)
+export default fastifyPlugin(dbConnector)
```
@@ -292,7 +311,7 @@ const fastifyPlugin = require('fastify-plugin')
/**
* Connects to a MongoDB database
* @param {FastifyInstance} fastify Encapsulated Fastify Instance
- * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
+ * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
*/
async function dbConnector (fastify, options) {
fastify.register(require('@fastify/mongodb'), {
@@ -311,7 +330,7 @@ module.exports = fastifyPlugin(dbConnector)
/**
* A plugin that provide encapsulated routes
* @param {FastifyInstance} fastify encapsulated fastify instance
- * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
+ * @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
*/
async function routes (fastify, options) {
const collection = fastify.mongo.db.collection('test_collection')
@@ -401,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.
```
@@ -434,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
/**
@@ -545,12 +562,13 @@ Read the [testing](./Testing.md) documentation to learn more!
### Run your server from CLI
-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`:
-```
+```sh
npm i fastify-cli
```
diff --git a/docs/Guides/Index.md b/docs/Guides/Index.md
index fa8b4635984..1220b0c0f4a 100644
--- a/docs/Guides/Index.md
+++ b/docs/Guides/Index.md
@@ -15,9 +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
+ 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.
diff --git a/docs/Guides/Migration-Guide-V4.md b/docs/Guides/Migration-Guide-V4.md
index 9968d49709f..7fc50a74ff8 100644
--- a/docs/Guides/Migration-Guide-V4.md
+++ b/docs/Guides/Migration-Guide-V4.md
@@ -6,16 +6,38 @@ Before migrating to v4, please ensure that you have fixed all deprecation
warnings from v3. All v3 deprecations have been removed and they will no longer
work after upgrading.
+## Codemods
+### Fastify v4 Codemods
+
+To help with the upgrade, we’ve worked with the team at
+[Codemod](https://github.com/codemod-com/codemod) to
+publish codemods that will automatically update your code to many of
+the new APIs and patterns in Fastify v4.
+
+
+```bash
+npx codemod@latest fastify/4/migration-recipe
+```
+This applies the following codemods:
+
+- fastify/4/remove-app-use
+- fastify/4/reply-raw-access
+- fastify/4/wrap-routes-plugin
+- fastify/4/await-register-calls
+
+For information on the migration recipe, see
+https://app.codemod.com/registry/fastify/4/migration-recipe.
+
+
## Breaking Changes
### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261))
-When an error is thrown in a async error handler function,
-the upper-level error handler is executed if set.
-If there is not a upper-level error handler, the default will
-be executed as it was previously.
+When an error is thrown in an async error handler function, the upper-level
+error handler is executed if set. If there is no upper-level error handler,
+the default will be executed as it was previously:
-```
+```js
import Fastify from 'fastify'
const fastify = Fastify()
@@ -25,14 +47,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')
})
@@ -40,94 +62,199 @@ const res = await fastify.inject('/encapsulated')
console.log(res.json().message) // 'wrapped'
```
-### Deprecation of `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506))
+>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**.
+
+### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506))
-Starting this version of Fastify, we have deprecated the use of `app.use()`. We
-have decided not to support the use of middlewares. Both
-[`@fastify/middie`](https://github.com/fastify/middie) and
-[`@fastify/express`](https://github.com/fastify/fastify-express) will still be
-there and maintained. Use Fastify's [hooks](../Reference/Hooks.md) instead.
+With v4 of Fastify, `app.use()` has been removed and the use of middleware is
+no longer supported.
+
+If you need to use middleware, use
+[`@fastify/middie`](https://github.com/fastify/middie) or
+[`@fastify/express`](https://github.com/fastify/fastify-express), which will
+continue to be maintained.
+However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md).
+
+> ℹ️ Note:
+> Codemod remove `app.use()` with:
+> ```bash
+> npx codemod@latest fastify/4/remove-app-use
+> ```
### `reply.res` moved to `reply.raw`
If you previously used the `reply.res` attribute to access the underlying Request
-object you'll instead need to depend on `reply.raw`.
+object you will now need to use `reply.raw`.
+
+> ℹ️ Note:
+> Codemod `reply.res` to `reply.raw` with:
+> ```bash
+> npx codemod@latest fastify/4/reply-raw-access
+> ```
### Need to `return reply` to signal a "fork" of the promise chain
-In some situations, like when a response is sent asynchronously or when you're
-just not explicitly returning a response, you'll need to return the `reply`
+In some situations, like when a response is sent asynchronously or when you are
+not explicitly returning a response, you will now need to return the `reply`
argument from your router handler.
### `exposeHeadRoutes` true by default
-Starting from v4, all the `GET` routes will create a sibling `HEAD` route.
-You can revert this behaviour by setting the server's option `exposeHeadRoutes`
-to `false`.
+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.
-### Synchronous route definitions
+### Synchronous route definitions ([#2954](https://github.com/fastify/fastify/pull/2954))
-The route registration has been made synchronous from v4.
-This change was done to provide better error reporting for route definition.
-As a result if you specify an `onRoute` hook in a plugin you should either:
+To improve error reporting in route definitions, route registration is now synchronous.
+As a result, if you specify an `onRoute` hook in a plugin you should now either:
* wrap your routes in a plugin (recommended)
+
+ For example, refactor this:
+ ```js
+ fastify.register((instance, opts, done) => {
+ instance.addHook('onRoute', (routeOptions) => {
+ const { path, method } = routeOptions;
+ console.log({ path, method });
+ done();
+ });
+ });
+
+ fastify.get('/', (request, reply) => { reply.send('hello') });
+ ```
+
+ Into this:
+ ```js
+ fastify.register((instance, opts, done) => {
+ instance.addHook('onRoute', (routeOptions) => {
+ const { path, method } = routeOptions;
+ console.log({ path, method });
+ done();
+ });
+ });
+
+ fastify.register((instance, opts, done) => {
+ instance.get('/', (request, reply) => { reply.send('hello') });
+ done();
+ });
+ ```
+
+> ℹ️ Note:
+> Codemod synchronous route definitions with:
+> ```bash
+> npx codemod@latest fastify/4/wrap-routes-plugin
+> ```
+
* use `await register(...)`
-For example refactor this:
-```
-fastify.register((instance, opts, done) => {
- instance.addHook('onRoute', (routeOptions) => {
- const { path, method } = routeOptions;
- console.log({ path, method });
+ For example, refactor this:
+ ```js
+ fastify.register((instance, opts, done) => {
+ instance.addHook('onRoute', (routeOptions) => {
+ const { path, method } = routeOptions;
+ console.log({ path, method });
+ });
+ done();
+ });
+ ```
+
+ Into this:
+ ```js
+ await fastify.register((instance, opts, done) => {
+ instance.addHook('onRoute', (routeOptions) => {
+ const { path, method } = routeOptions;
+ console.log({ path, method });
+ });
+ done();
});
- done();
+ ```
+
+> ℹ️ Note:
+> Codemod 'await register(...)' with:
+> ```bash
+> npx codemod@latest fastify/4/await-register-calls
+> ```
+
+
+### Optional URL parameters
+
+If you've already used any implicitly optional parameters, you'll get a 404
+error when trying to access the route. You will now need to declare the
+optional parameters explicitly.
+
+For example, if you have the same route for listing and showing a post,
+refactor this:
+```js
+fastify.get('/posts/:id', (request, reply) => {
+ const { id } = request.params;
});
```
+
Into this:
-```
-await fastify.register((instance, opts, done) => {
- instance.addHook('onRoute', (routeOptions) => {
- const { path, method } = routeOptions;
- console.log({ path, method });
- });
- done();
+```js
+fastify.get('/posts/:id?', (request, reply) => {
+ const { id } = request.params;
});
```
-## Non Breaking Changes
+## Non-Breaking Changes
-### Change of schema for multiple types
+### Deprecation of variadic `.listen()` signature
+The [variadic signature](https://en.wikipedia.org/wiki/Variadic_function) of the
+`fastify.listen()` method is now deprecated.
-Since Fastify v4 has upgraded to Ajv v8. The "type" keywords with multiple types
-(other than with "null") are prohibited. Read more
-['here'](https://ajv.js.org/strict-mode.html#strict-types)
+Before this release, the following invocations of this method were valid:
-You may encounter a console warning such as
+ - `fastify.listen(8000)`
+ - `fastify.listen(8000, ‘127.0.0.1’)`
+ - `fastify.listen(8000, ‘127.0.0.1’, 511)`
+ - `fastify.listen(8000, (err) => { if (err) throw err })`
+ - `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })`
-```
+With Fastify v4, only the following invocations are valid:
+
+ - `fastify.listen()`
+ - `fastify.listen({ port: 8000 })`
+ - `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })`
+
+### Change of schema for multiple types
+
+Ajv has been upgraded to v8 in Fastify v4, meaning "type" keywords with multiple
+types other than "null"
+[are now prohibited](https://ajv.js.org/strict-mode.html#strict-types).
+
+You may encounter a console warning such as:
+```sh
strict mode: use allowUnionTypes to allow union type keyword at "#/properties/image" (strictTypes)
```
-So schemas like below will need to be changed from
-```
-type: 'object',
-properties: {
- api_key: { type: 'string' },
- image: { type: ['object', 'array'] }
+
+As such, schemas like below will need to be changed from:
+```js
+{
+ type: 'object',
+ properties: {
+ api_key: { type: 'string' },
+ image: { type: ['object', 'array'] }
}
}
```
-to
-```
-type: 'object',
-properties: {
- api_key: { type: 'string' },
- image: {
- anyOf: [
- { type: 'array' },
- { type: 'object' }
- ]
+Into:
+```js
+{
+ type: 'object',
+ properties: {
+ api_key: { type: 'string' },
+ image: {
+ anyOf: [
+ { type: 'array' },
+ { type: 'object' }
+ ]
+ }
}
}
```
diff --git a/docs/Guides/Migration-Guide-V5.md b/docs/Guides/Migration-Guide-V5.md
new file mode 100644
index 00000000000..9c8ad875a3c
--- /dev/null
+++ b/docs/Guides/Migration-Guide-V5.md
@@ -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 : unknown;
++ validator: this['schema'] extends JSONSchema ? FromSchema : unknown;
++ serializer: this['schema'] extends JSONSchema ? FromSchema : 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
+server’s 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 |
diff --git a/docs/Guides/Plugins-Guide.md b/docs/Guides/Plugins-Guide.md
index 6f1d543525b..6edb74fa82e 100644
--- a/docs/Guides/Plugins-Guide.md
+++ b/docs/Guides/Plugins-Guide.md
@@ -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
@@ -147,7 +147,7 @@ fastify.register((instance, opts, done) => {
instance.decorate('util', (a, b) => a + b)
console.log(instance.util('that is ', 'awesome'))
- fastify.register((instance, opts, done) => {
+ instance.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // This will not throw an error
done()
})
@@ -202,14 +202,14 @@ 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]
+fastify.decorate('getBoolHeader', (req, name) => {
+ return req.headers[name] ?? false // We return `false` if header is missing
})
fastify.addHook('preHandler', (request, reply, done) => {
- request.isHappy = fastify.getHeader(request.raw, 'happy')
+ request.isHappy = fastify.getBoolHeader(request, 'happy')
done()
})
@@ -219,14 +219,14 @@ fastify.get('/happiness', (request, reply) => {
```
Again, it works, but it can be much better!
```js
-fastify.decorateRequest('setHeader', function (header) {
- this.isHappy = this.headers[header]
+fastify.decorateRequest('setBoolHeader', function (name) {
+ this.isHappy = this.headers[name] ?? false
})
fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!
fastify.addHook('preHandler', (request, reply, done) => {
- request.setHeader('happy')
+ request.setBoolHeader('happy')
done()
})
@@ -308,8 +308,52 @@ fastify.get('/plugin2', (request, reply) => {
```
Now your hook will run just for the first route!
+An alternative approach is to make use of the [onRoute hook](../Reference/Hooks.md#onroute)
+to customize application routes dynamically from inside the plugin. Every time
+a new route is registered, you can read and modify the route options. For example,
+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()
+ }
+
+ instance.addHook('onRoute', (routeOptions) => {
+ if (routeOptions.config && routeOptions.config.useUtil === true) {
+ // set or add our handler to the route preHandler hook
+ if (!routeOptions.preHandler) {
+ routeOptions.preHandler = [handler]
+ return
+ }
+ if (Array.isArray(routeOptions.preHandler)) {
+ routeOptions.preHandler.push(handler)
+ return
+ }
+ routeOptions.preHandler = [routeOptions.preHandler, handler]
+ }
+ })
+
+ instance.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
+ reply.send(request)
+ })
+
+ instance.get('/plugin2', (request, reply) => {
+ reply.send(request)
+ })
+
+ done()
+})
+```
+
+This variant becomes extremely useful if you plan to distribute your plugin, as
+described in the next section.
+
As you probably noticed by now, `request` and `reply` are not the standard
-Nodejs *request* and *response* objects, but Fastify's objects.
+Node.js *request* and *response* objects, but Fastify's objects.
## How to handle encapsulation and distribution
@@ -351,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
@@ -368,7 +412,7 @@ function dbPlugin (fastify, opts, done) {
})
}
-fastify.register(fp(dbPlugin), { url: 'https://example.com' })
+fastify.register(fp(dbPlugin), { url: 'https://fastify.example' })
fastify.register(require('your-plugin'), parent => {
return { connection: parent.db, otherOption: 'foo-bar' }
})
@@ -383,7 +427,7 @@ variables that were injected by preceding plugins in the order of declaration.
ESM is supported as well from [Node.js
`v13.3.0`](https://nodejs.org/api/esm.html) and above! Just export your plugin
-as ESM module and you are good to go!
+as an ESM module and you are good to go!
```js
// plugin.mjs
@@ -395,24 +439,6 @@ async function plugin (fastify, opts) {
export default plugin
```
-__Note__: Fastify does not support named imports within an ESM context. Instead,
-the `default` export is available.
-
-```js
-// server.mjs
-import Fastify from 'fastify'
-
-const fastify = Fastify()
-
-///...
-
-fastify.listen({ port: 3000 }, (err, address) => {
- if (err) {
- fastify.log.error(err)
- process.exit(1)
- }
-})
-```
## Handle errors
@@ -467,8 +493,8 @@ use case, you can use the
```js
const warning = require('process-warning')()
-warning.create('FastifyDeprecation', 'FST_ERROR_CODE', 'message')
-warning.emit('FST_ERROR_CODE')
+warning.create('MyPluginWarning', 'MP_ERROR_CODE', 'message')
+warning.emit('MP_ERROR_CODE')
```
## Let's start!
diff --git a/docs/Guides/Prototype-Poisoning.md b/docs/Guides/Prototype-Poisoning.md
index 8d29cf6e7b6..0d01aa90477 100644
--- a/docs/Guides/Prototype-Poisoning.md
+++ b/docs/Guides/Prototype-Poisoning.md
@@ -4,29 +4,24 @@
> but otherwise remains the same. The original HTML can be retrieved from the
> above permission link.
-## A Tale of (prototype) Poisoning
+## History behind prototype poisoning
-This story is a behind-the-scenes look at the process and drama created by a
-particularity interesting web security issue. It is also a perfect illustration
-of the efforts required to maintain popular pieces of open source software and
-the limitations of existing communication channels.
-
-But first, if you use a JavaScript framework to process incoming JSON data, take
-a moment to read up on [Prototype
-Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
-in general, and the specific [technical
-details](https://github.com/hapijs/hapi/issues/3916) of this issue. I'll explain
-it all in a bit, but since this could be a critical issue, you might want to
-verify your own code first. While this story is focused on a specific framework,
-any solution that uses `JSON.parse()` to process external data is potentially at
-risk.
+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
+open-source software and the limitations of existing communication channels.
+
+But first, if we use a JavaScript framework to process incoming JSON data, take
+a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
+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()`
+to process external data is potentially at risk.
### BOOM
-Our story begins with a bang.
-
The engineering team at Lob (long time generous supporters of my work!) reported
a critical security vulnerability they identified in our data validation
module — [joi](https://github.com/hapijs/joi). They provided some technical
@@ -34,21 +29,22 @@ details and a proposed solution.
The main purpose of a data validation library is to ensure the output fully
complies with the rules defined. If it doesn't, validation fails. If it passes,
-your can blindly trust that the data you are working with is safe. In fact, most
+we can blindly trust that the data you are working with is safe. In fact, most
developers treat validated input as completely safe from a system integrity
-perspective. This is crucial.
+perspective which is crucial!
-In our case, the Lob team provided an example where some data was able to sneak
+In our case, the Lob team provided an example where some data was able to escape
by the validation logic and pass through undetected. This is the worst possible
defect a validation library can have.
### Prototype in a nutshell
-To understand this story, you need to understand how JavaScript works a bit.
+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 put inherits in quotes because
-JavaScript isn't really an object oriented language.
+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.
A long time ago, for a bunch of irrelevant reasons, someone decided that it
would be a good idea to use the special property name `__proto__` to access (and
@@ -68,22 +64,21 @@ To demonstrate:
{ b: 5 }
```
-As you can see, the object doesn't have a `c` property, but its prototype does.
+The object doesn't have a `c` property, but its prototype does.
When validating the object, the validation library ignores the prototype and
only validates the object's own properties. This allows `c` to sneak in via the
prototype.
-Another important part of this story is the way `JSON.parse()` — a utility
-provided by the language to convert JSON formatted text into objects — handles
-this magic `__proto__` property name.
+Another important part is the way `JSON.parse()` — a utility
+provided by the language to convert JSON formatted text into
+objects — handles this magic `__proto__` property name.
```
-> const text = '{ "b": 5, "__proto__": { "c": 6 } }';
+> const text = '{"b": 5, "__proto__": { "c": 6 }}';
> const a = JSON.parse(text);
> a;
-{ b: 5, __proto__: { c: 6 } }
+{b: 5, __proto__: { c: 6 }}
```
-
Notice how `a` has a `__proto__` property. This is not a prototype reference. It
is a simple object property key, just like `b`. As we've seen from the first
example, we can't actually create this key through assignment as that invokes
@@ -111,17 +106,17 @@ level properties of `a` into the provided empty `{}` object), the magic
Surprise!
-Put together, if you get some external text input, parse it with `JSON.parse()`
-then perform some simple manipulation of that object (say, shallow clone and add
-an `id` ), and then pass it to our validation library, anything passed through
-via `__proto__` would sneak in undetected.
+If you get some external text input and parse it with `JSON.parse()`
+then perform some simple manipulation of that object (e.g shallow clone and add
+an `id` ), and pass it to our validation library, it would sneak in undetected
+via `__proto__`.
### Oh joi!
The first question is, of course, why does the validation module **joi** ignore
the prototype and let potentially harmful data through? We asked ourselves the
-same question and our instant thought was "it was an oversight". A bug. A really
+same question and our instant thought was "it was an oversight". A bug - a really
big mistake. The joi module should not have allowed this to happen. But…
While joi is used primarily for validating web input data, it also has a
@@ -166,7 +161,6 @@ will share with the world how to exploit this vulnerability while also making it
more time consuming for systems to upgrade (breaking changes never get applied
automatically by build tools).
-Lose — Lose.
### A detour
@@ -386,6 +380,4 @@ plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-
coming in March. You can read more about it
[here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898).
-Of all the time consuming things, security is at the very top. I hope this story
-successfully conveyed not just the technical details, but also the human drama and
-what it takes to keep the web secure.
+
diff --git a/docs/Guides/Recommendations.md b/docs/Guides/Recommendations.md
index f0ee7b19859..3747a2dc10c 100644
--- a/docs/Guides/Recommendations.md
+++ b/docs/Guides/Recommendations.md
@@ -7,7 +7,10 @@ This document contains a set of recommendations when using Fastify.
- [Use A Reverse Proxy](#use-a-reverse-proxy)
- [HAProxy](#haproxy)
- [Nginx](#nginx)
+- [Common Causes Of Performance Degradation](#common-causes-of-performance-degradation)
- [Kubernetes](#kubernetes)
+- [Capacity Planning For Production](#capacity)
+- [Running Multiple Instances](#multiple)
## Use A Reverse Proxy
@@ -110,7 +113,7 @@ frontend proxy-ssl
# Here we define rule pairs to handle static resources. Any incoming request
# that has a path starting with `/static`, e.g.
- # `https://one.example.com/static/foo.jpeg`, will be redirected to the
+ # `https://one.fastify.example/static/foo.jpeg`, will be redirected to the
# static resources server.
acl is_static path -i -m beg /static
use_backend static-backend if is_static
@@ -120,10 +123,10 @@ frontend proxy-ssl
# the incoming hostname and define a boolean indicating if it is a match.
# The `use_backend` line is used to direct the traffic if the boolean is
# true.
- acl example1 hdr_sub(Host) one.example.com
+ acl example1 hdr_sub(Host) one.fastify.example
use_backend example1-backend if example1
- acl example2 hdr_sub(Host) two.example.com
+ acl example2 hdr_sub(Host) two.fastify.example
use_backend example2-backend if example2
# Finally, we have a fallback redirect if none of the requested hosts
@@ -142,14 +145,14 @@ backend default-server
# requests over TLS, but that is outside the scope of this example.
server server1 10.10.10.2:80
-# This backend configuration will serve requests for `https://one.example.com`
+# This backend configuration will serve requests for `https://one.fastify.example`
# by proxying requests to three backend servers in a round-robin manner.
backend example1-backend
server example1-1 10.10.11.2:80
server example1-2 10.10.11.2:80
server example2-2 10.10.11.3:80
-# This one serves requests for `https://two.example.com`
+# This one serves requests for `https://two.fastify.example`
backend example2-backend
server example2-1 10.10.12.2:80
server example2-2 10.10.12.2:80
@@ -210,17 +213,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;
@@ -278,10 +283,34 @@ server {
[nginx]: https://nginx.org/
+## Common Causes Of Performance Degradation
+
+These patterns can increase latency or reduce throughput in production:
+
+- Prefer static or simple parametric routes on hot paths. RegExp routes are
+ expensive, and routes with many parameters can also hurt router performance.
+ See [Routes - Url building](../Reference/Routes.md#url-building).
+- Use route constraints carefully. Version constraints can degrade router
+ performance, and asynchronous custom constraints should be treated as a last
+ resort. See [Routes - Constraints](../Reference/Routes.md#constraints).
+- Prefer Fastify plugins/hooks over generic middleware when possible. Fastify's
+ middleware adapters work, but native integrations are typically better for
+ performance-sensitive paths. See [Middleware](../Reference/Middleware.md).
+- Define response schemas to speed up JSON serialization. See
+ [Getting Started - Serialize your data](./Getting-Started.md#serialize-data).
+- Keep Ajv `allErrors` disabled by default. Enable it only when detailed
+ validation feedback is needed (for example, form-heavy APIs), and avoid it
+ on latency-sensitive endpoints. When `allErrors: true` is enabled, validation
+ can do more work per request and make denial-of-service attacks easier on
+ untrusted inputs.
+ See also:
+ - [Validation and Serialization - Validator Compiler](../Reference/Validation-and-Serialization.md#schema-validator)
+ - [Ajv Security Risks of Trusted Schemas](https://ajv.js.org/security.html#security-risks-of-trusted-schemas).
+
## Kubernetes
-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,
@@ -298,3 +327,52 @@ readinessProbe:
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 5
+```
+
+## Capacity Planning For Production
+
+
+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)
+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
+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.
+
+* To optimize for throughput (handling the largest possible amount of
+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
+even better throughput in certain use-cases. There are reports of API gateway
+solutions working well with 100m-200m vCPU in Kubernetes.
+
+See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8)
+to understand the workings of Node.js in greater detail and make a
+better determination about what your specific application needs.
+
+## Running Multiple Instances
+
+
+There are several use-cases where running multiple Fastify
+apps on the same server might be considered. A common example
+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.
+Each Fastify instance only generates as much load as the traffic it receives,
+plus the memory used for that Fastify instance.
diff --git a/docs/Guides/Serverless.md b/docs/Guides/Serverless.md
index 1c7b7863161..f6e3428cf65 100644
--- a/docs/Guides/Serverless.md
+++ b/docs/Guides/Serverless.md
@@ -1,20 +1,20 @@
Serverless
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,7 +25,9 @@ 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)
- [Vercel](#vercel)
@@ -34,10 +36,10 @@ snippet of code.
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.
@@ -127,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
@@ -202,7 +211,7 @@ const fastifyFunction = async (request, reply) => {
fastify.server.emit('request', request, reply)
}
-export.fastifyFunction = fastifyFunction;
+exports.fastifyFunction = fastifyFunction;
```
### Local test
@@ -228,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 \
@@ -259,6 +267,132 @@ curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
- [Google Cloud Functions - Node.js Quickstart
](https://cloud.google.com/functions/docs/quickstart-nodejs)
+## Google Firebase Functions
+
+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) => {}`.
+
+### The onRequest() handler
+
+We use the `onRequest` function to wrap our Fastify application instance.
+
+As such, we'll begin with importing it to the code:
+
+```js
+const { onRequest } = require("firebase-functions/v2/https")
+```
+
+### Creation of Fastify instance
+
+Create the Fastify instance and encapsulate the returned application instance
+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")({
+ logger: true,
+})
+
+const fastifyApp = async (request, reply) => {
+ await registerRoutes(fastify)
+ await fastify.ready()
+ fastify.server.emit("request", 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 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:
+
+```js
+async function registerRoutes (fastify) {
+ fastify.addContentTypeParser("application/json", {}, (req, payload, done) => {
+ // useful to include the request's raw body on the `req` object that will
+ // later be available in your other routes so you can calculate the HMAC
+ // if needed
+ req.rawBody = payload.rawBody
+
+ // payload.body is already the parsed JSON so we just fire the done callback
+ // with it
+ done(null, payload.body)
+ })
+
+ // define your endpoints here...
+ fastify.post("/some-route-here", async (request, reply) => {})
+
+ fastify.get('/', async (request, reply) => {
+ reply.send({message: 'Hello World!'})
+ })
+}
+```
+
+**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
+`onRequest()` function so it can pass the request and reply objects to it:
+
+```js
+exports.app = onRequest(fastifyApp)
+```
+
+### Local test
+
+Install the Firebase tools functions so you can use the CLI:
+
+```bash
+npm i -g firebase-tools
+```
+
+Then you can run your function locally with:
+
+```bash
+firebase emulators:start --only functions
+```
+
+### Deploy
+
+Deploy your Firebase Functions with:
+
+```bash
+firebase deploy --only functions
+```
+
+#### Read logs
+
+Use the Firebase tools CLI:
+
+```bash
+firebase functions:log
+```
+
+### References
+- [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
@@ -273,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
@@ -344,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
@@ -371,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
@@ -445,54 +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
-
+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:
-
-```json
-{
- "rewrites": [
- {
- "source": "/(.*)",
- "destination": "/api/serverless.js"
- }
- ]
-}
-```
-
-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";
+[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.
-// Instantiate Fastify with some config
-const app = Fastify({
- logger: true,
-});
-
-// Register your application as a normal plugin.
-app.register(import("../src/app"));
+Get started with the
+[Fastify template on Vercel](
+https://vercel.com/templates/backend/fastify-on-vercel).
-export default async (req, res) => {
- await app.ready();
- app.server.emit('request', req, res);
-}
-```
+[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/fluid-compute#enabling-fluid-compute).
\ No newline at end of file
diff --git a/docs/Guides/Style-Guide.md b/docs/Guides/Style-Guide.md
index 16f2c73087f..68fc7639033 100644
--- a/docs/Guides/Style-Guide.md
+++ b/docs/Guides/Style-Guide.md
@@ -13,7 +13,7 @@ This guide is for anyone who loves to build with Fastify or wants to contribute
to our documentation. You do not need to be an expert in writing technical
documentation. This guide is here to help you.
-Visit the [contribute](https://www.fastify.io/contribute) page on our website or
+Visit the [contribute](https://fastify.dev/contribute) page on our website or
read the
[CONTRIBUTING.md](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md)
file on GitHub to join our Open Source folks.
@@ -70,12 +70,12 @@ markdown.
**Example**
```
-To learn more about hooks, see [Fastify hooks](https://www.fastify.io/docs/latest/Reference/Hooks/).
+To learn more about hooks, see [Fastify hooks](https://fastify.dev/docs/latest/Reference/Hooks/).
```
Result:
>To learn more about hooks, see [Fastify
->hooks](https://www.fastify.io/docs/latest/Reference/Hooks/).
+>hooks](https://fastify.dev/docs/latest/Reference/Hooks/).
@@ -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,25 +217,25 @@ 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
// Add clear & brief description
-[Fastify Plugins] (https://www.fastify.io/docs/latest/Plugins/)
+[Fastify Plugins] (https://fastify.dev/docs/latest/Plugins/)
// incomplete description
-[Fastify] (https://www.fastify.io/docs/latest/Plugins/)
+[Fastify] (https://fastify.dev/docs/latest/Plugins/)
// Adding title in link brackets
-[](https://www.fastify.io/docs/latest/Plugins/ "fastify plugin")
+[](https://fastify.dev/docs/latest/Plugins/ "fastify plugin")
// Empty title
-[](https://www.fastify.io/docs/latest/Plugins/)
+[](https://fastify.dev/docs/latest/Plugins/)
// Adding links localhost URLs instead of using code strings (``)
[http://localhost:3000/](http://localhost:3000/)
diff --git a/docs/Guides/Testing.md b/docs/Guides/Testing.md
index 039cbb8ea6e..4901191e5a6 100644
--- a/docs/Guides/Testing.md
+++ b/docs/Guides/Testing.md
@@ -1,16 +1,19 @@
-Fastify
+Fastify
-## Testing
+# Testing
+
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
@@ -110,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')
})
```
@@ -211,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' })
})
})
```
@@ -243,47 +247,81 @@ after initializing routes and plugins with `fastify.ready()`.
Uses **app.js** from the previous example.
-**test-listen.js** (testing with
-[`Request`](https://www.npmjs.com/package/request))
+**test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici))
```js
-const tap = require('tap')
-const request = require('request')
+const { test } = require('node:test')
+const { Client } = require('undici')
const buildFastify = require('./app')
-tap.test('GET `/` route', t => {
- t.plan(5)
+test('should work with undici', async t => {
+ t.plan(2)
const fastify = buildFastify()
- t.teardown(() => fastify.close())
+ await fastify.listen()
- fastify.listen({ port: 0 }, (err) => {
- t.error(err)
+ const client = new Client(
+ 'http://localhost:' + fastify.server.address().port, {
+ keepAliveTimeout: 10,
+ keepAliveMaxTimeout: 10
+ }
+ )
- request({
- method: 'GET',
- url: 'http://localhost:' + fastify.server.address().port
- }, (err, response, body) => {
- t.error(err)
- t.equal(response.statusCode, 200)
- t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
- t.same(JSON.parse(body), { hello: 'world' })
- })
+ t.after(() => {
+ fastify.close()
+ client.close()
})
+
+ const response = await client.request({ method: 'GET', path: '/' })
+
+ 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)
+may be used without requiring any extra dependencies:
+
+**test-listen.js**
+```js
+const { test } = require('node:test')
+const buildFastify = require('./app')
+
+test('should work with fetch', async t => {
+ t.plan(3)
+
+ const fastify = buildFastify()
+
+ t.after(() => fastify.close())
+
+ await fastify.listen()
+
+ const response = await fetch(
+ 'http://localhost:' + fastify.server.address().port
+ )
+
+ t.assert.strictEqual(response.status, 200)
+ t.assert.strictEqual(
+ response.headers.get('content-type'),
+ 'application/json; charset=utf-8'
+ )
+ 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()
@@ -291,24 +329,153 @@ 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/
+> node --test --test-only --inspect-brk test/
```
-- `-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.
Now you should be able to step through your test file (and the rest of
`Fastify`) in your code editor.
+
+
+
+## Plugins
+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`
+
+**plugin/myFirstPlugin.js**:
+
+```js
+const fP = require("fastify-plugin")
+
+async function myPlugin(fastify, options) {
+ fastify.decorateRequest("helloRequest", "Hello World")
+ fastify.decorate("helloInstance", "Hello Fastify Instance")
+}
+
+module.exports = fP(myPlugin)
+```
+
+A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md)
+
+**test/myFirstPlugin.test.js**:
+
+```js
+const Fastify = require("fastify");
+const { test } = require("node:test");
+const myPlugin = require("../plugin/myFirstPlugin");
+
+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
+ fastify.get("/", async (request, reply) => {
+ return ({ message: request.helloRequest })
+ })
+
+ // Use fastify.inject to fake a HTTP Request
+ const fastifyResponse = await fastify.inject({
+ method: "GET",
+ url: "/"
+ })
+
+ console.log('status code: ', fastifyResponse.statusCode)
+ console.log('body: ', fastifyResponse.body)
+})
+```
+Learn more about [```fastify.inject()```](#benefits-of-using-fastifyinject).
+Run the test file in your terminal `node test/myFirstPlugin.test.js`
+
+```sh
+status code: 200
+body: {"message":"Hello World"}
+```
+
+Now we can replace our `console.log` calls with actual tests!
+
+In your `package.json` change the "test" script to:
+
+`"test": "node --test --watch"`
+
+Create the test for the endpoint.
+
+**test/myFirstPlugin.test.js**:
+
+```js
+const Fastify = require("fastify");
+const { test } = require("node:test");
+const myPlugin = require("../plugin/myFirstPlugin");
+
+test("Test the Plugin Route", async t => {
+ // Specifies the number of test
+ t.plan(2)
+
+ const fastify = Fastify()
+
+ fastify.register(myPlugin)
+
+ fastify.get("/", async (request, reply) => {
+ return ({ message: request.helloRequest })
+ })
+
+ const fastifyResponse = await fastify.inject({
+ method: "GET",
+ url: "/"
+ })
+
+ t.assert.strictEqual(fastifyResponse.statusCode, 200)
+ t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
+})
+```
+
+Finally, run `npm test` in the terminal and see your test results!
+
+Test the ```.decorate()``` and ```.decorateRequest()```.
+
+**test/myFirstPlugin.test.js**:
+
+```js
+const Fastify = require("fastify");
+const { test }= require("node:test");
+const myPlugin = require("../plugin/myFirstPlugin");
+
+test("Test the Plugin Route", async t => {
+ t.plan(5)
+ const fastify = Fastify()
+
+ fastify.register(myPlugin)
+
+ fastify.get("/", async (request, reply) => {
+ // Testing the fastify decorators
+ t.assert.ifError(request.helloRequest)
+ t.assert.ok(request.helloRequest, "Hello World")
+ t.assert.ok(fastify.helloInstance, "Hello Fastify Instance")
+ return ({ message: request.helloRequest })
+ })
+
+ const fastifyResponse = await fastify.inject({
+ method: "GET",
+ url: "/"
+ })
+ t.assert.strictEqual(fastifyResponse.statusCode, 200)
+ t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
+})
+```
diff --git a/docs/Guides/Write-Plugin.md b/docs/Guides/Write-Plugin.md
index 5d161b478fa..78e1225844b 100644
--- a/docs/Guides/Write-Plugin.md
+++ b/docs/Guides/Write-Plugin.md
@@ -1,4 +1,4 @@
-Fastify
+Fastify
# How to write a good plugin
First, thank you for deciding to write a plugin for Fastify. Fastify is a
@@ -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,16 +53,17 @@ 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
+learn about how to test your plugins.
## Code Linter
It is not mandatory, but we highly recommend you use a code linter in your
@@ -80,7 +81,7 @@ to show that the plugin works as intended. Both
Actions](https://github.com/features/actions) are free for open source projects
and easy to set up.
-In addition, you can enable services like [Dependabot](https://dependabot.com/),
+In addition, you can enable services like [Dependabot](https://github.com/dependabot),
which will help you keep your dependencies up to date and discover if a new
release of Fastify has some issues with your plugin.
diff --git a/docs/Guides/Write-Type-Provider.md b/docs/Guides/Write-Type-Provider.md
new file mode 100644
index 00000000000..0f0d750fdf9
--- /dev/null
+++ b/docs/Guides/Write-Type-Provider.md
@@ -0,0 +1,34 @@
+Fastify
+
+## How to write your own type provider
+
+Things to keep in mind when implementing a custom [type provider](../Reference/Type-Providers.md):
+
+### Type Contravariance
+
+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
+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)
+ validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
+ serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
+}
+```
+
+Unless changed to:
+```ts
+export interface SubstitutableTypeProvider extends FastifyTypeProvider {
+ // good, anything can be assigned to `unknown`
+ validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
+ serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
+}
+```
diff --git a/docs/Reference/ContentTypeParser.md b/docs/Reference/ContentTypeParser.md
index 133853b88c0..8f92873da43 100644
--- a/docs/Reference/ContentTypeParser.md
+++ b/docs/Reference/ContentTypeParser.md
@@ -1,31 +1,41 @@
Fastify
## `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.
+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`.
-
-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.
+request](./Request.md) object, accessible via `request.body`.
+
+> **Important:** When using a body schema with the
+> [`content`](./Validation-and-Serialization.md#body-content-type-validation)
+> property to validate per content type, only content types listed in the schema
+> will be validated. If you add a custom content type parser but do not include
+> its content type in the body schema's `content` property, the incoming data
+> will be parsed but **not validated**.
+
+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
@@ -44,13 +54,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)
})
@@ -61,11 +71,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
@@ -78,14 +87,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')){
@@ -97,11 +126,10 @@ 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) {
@@ -115,16 +143,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()
@@ -136,22 +159,21 @@ 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
@@ -165,30 +187,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) {
@@ -196,7 +215,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) => {
@@ -224,19 +243,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)
diff --git a/docs/Reference/Decorators.md b/docs/Reference/Decorators.md
index 9a051e6305b..5d57691c066 100644
--- a/docs/Reference/Decorators.md
+++ b/docs/Reference/Decorators.md
@@ -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
@@ -72,8 +67,7 @@ topic.
#### `decorate(name, value, [dependencies])`
-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', {
@@ -101,37 +95,71 @@ console.log(fastify.conf.db)
```
The decorated [Fastify server](./Server.md) is bound to `this` in
-route [route](./Routes.md) handlers:
+[route](./Routes.md) handlers:
```js
fastify.decorate('db', new DbConnection())
fastify.get('/', async function (request, reply) {
- reply({hello: await this.db.query('world')})
+ // using return
+ return { hello: await this.db.query('world') }
+
+ // or
+ // using reply.send()
+ reply.send({ hello: await this.db.query('world') })
+ await 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 "log" 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
-fastify.decorate('utility', fn, ['greet', 'log'])
+async function greetDecorator (fastify, opts) {
+ fastify.decorate('greet', () => {
+ return 'greet message'
+ })
+}
+
+async function hiDecorator (fastify, opts) {
+ fastify.decorate('hi', () => {
+ return 'hi message'
+ })
+}
+
+async function utilityDecorator (fastify, opts) {
+ fastify.decorate('utility', () => {
+ return `${fastify.greet()} | ${fastify.hi()}`
+ })
+}
+
+fastify.register(fastifyPlugin(greetDecorator, { name: 'greet' }))
+fastify.register(fastifyPlugin(hiDecorator, { name: 'hi' }))
+fastify.register(fastifyPlugin(utilityDecorator, { dependencies: ['greet', 'hi'] }))
+
+fastify.get('/', function (req, reply) {
+ // Response: {"hello":"greet message | hi message"}
+ reply.send({ hello: fastify.utility() })
+})
+
+fastify.listen({ port: 3000 }, (err, address) => {
+ if (err) throw err
+})
```
-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])`
-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 () {
@@ -139,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 }
})
}
@@ -172,8 +201,8 @@ See [`decorate`](#decorate) for information about the `dependencies` parameter.
#### `decorateRequest(name, value, [dependencies])`
-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 () {
@@ -181,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 }
})
@@ -210,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)`
@@ -244,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')()
@@ -297,9 +348,9 @@ server.listen({ port: 3000 })
### Getters and Setters
-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', {
@@ -314,3 +365,72 @@ Will define the `foo` property on the Fastify instance:
```js
console.log(fastify.foo) // 'a getter'
```
+
+#### `getDecorator(name)`
+
+
+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)`
+
+
+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`.
diff --git a/docs/Reference/Encapsulation.md b/docs/Reference/Encapsulation.md
index aa26ef170aa..cb02ffb0126 100644
--- a/docs/Reference/Encapsulation.md
+++ b/docs/Reference/Encapsulation.md
@@ -3,21 +3,20 @@
## Encapsulation
-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:

-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
-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'
diff --git a/docs/Reference/Errors.md b/docs/Reference/Errors.md
index ec969cd63c8..f1e66374752 100644
--- a/docs/Reference/Errors.md
+++ b/docs/Reference/Errors.md
@@ -3,12 +3,112 @@
## Errors
+**Table of contents**
+- [Errors](#errors)
+ - [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)
+ - [Errors In Input Data](#errors-in-input-data)
+ - [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify)
+ - [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler)
+ - [Fastify Error Codes](#fastify-error-codes)
+ - [FST_ERR_NOT_FOUND](#fst_err_not_found)
+ - [FST_ERR_OPTIONS_NOT_OBJ](#fst_err_options_not_obj)
+ - [FST_ERR_QSP_NOT_FN](#fst_err_qsp_not_fn)
+ - [FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN](#fst_err_schema_controller_bucket_opt_not_fn)
+ - [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_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)
+ - [FST_ERR_CTP_INVALID_HANDLER](#fst_err_ctp_invalid_handler)
+ - [FST_ERR_CTP_INVALID_PARSE_TYPE](#fst_err_ctp_invalid_parse_type)
+ - [FST_ERR_CTP_BODY_TOO_LARGE](#fst_err_ctp_body_too_large)
+ - [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)
+ - [FST_ERR_HOOK_NOT_SUPPORTED](#fst_err_hook_not_supported)
+ - [FST_ERR_MISSING_MIDDLEWARE](#fst_err_missing_middleware)
+ - [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)
+ - [FST_ERR_SEND_UNDEFINED_ERR](#fst_err_send_undefined_err)
+ - [FST_ERR_BAD_STATUS_CODE](#fst_err_bad_status_code)
+ - [FST_ERR_BAD_TRAILER_NAME](#fst_err_bad_trailer_name)
+ - [FST_ERR_BAD_TRAILER_VALUE](#fst_err_bad_trailer_value)
+ - [FST_ERR_FAILED_ERROR_SERIALIZATION](#fst_err_failed_error_serialization)
+ - [FST_ERR_MISSING_SERIALIZATION_FN](#fst_err_missing_serialization_fn)
+ - [FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN](#fst_err_missing_contenttype_serialization_fn)
+ - [FST_ERR_REQ_INVALID_VALIDATION_INVOCATION](#fst_err_req_invalid_validation_invocation)
+ - [FST_ERR_SCH_MISSING_ID](#fst_err_sch_missing_id)
+ - [FST_ERR_SCH_ALREADY_PRESENT](#fst_err_sch_already_present)
+ - [FST_ERR_SCH_CONTENT_MISSING_SCHEMA](#fst_err_sch_content_missing_schema)
+ - [FST_ERR_SCH_DUPLICATE](#fst_err_sch_duplicate)
+ - [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_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_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)
+ - [FST_ERR_ROUTE_HANDLER_NOT_FN](#fst_err_route_handler_not_fn)
+ - [FST_ERR_ROUTE_MISSING_HANDLER](#fst_err_route_missing_handler)
+ - [FST_ERR_ROUTE_METHOD_INVALID](#fst_err_route_method_invalid)
+ - [FST_ERR_ROUTE_METHOD_NOT_SUPPORTED](#fst_err_route_method_not_supported)
+ - [FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED](#fst_err_route_body_validation_schema_not_supported)
+ - [FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT](#fst_err_route_body_limit_option_not_int)
+ - [FST_ERR_HANDLER_TIMEOUT](#fst_err_handler_timeout)
+
+ - [FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT](#fst_err_route_handler_timeout_option_not_int)
+ - [FST_ERR_ROUTE_REWRITE_NOT_STR](#fst_err_route_rewrite_not_str)
+ - [FST_ERR_REOPENED_CLOSE_SERVER](#fst_err_reopened_close_server)
+ - [FST_ERR_REOPENED_SERVER](#fst_err_reopened_server)
+ - [FST_ERR_PLUGIN_VERSION_MISMATCH](#fst_err_plugin_version_mismatch)
+ - [FST_ERR_PLUGIN_CALLBACK_NOT_FN](#fst_err_plugin_callback_not_fn)
+ - [FST_ERR_PLUGIN_NOT_VALID](#fst_err_plugin_not_valid)
+ - [FST_ERR_ROOT_PLG_BOOTED](#fst_err_root_plg_booted)
+ - [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
#### 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.
@@ -17,29 +117,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
@@ -49,192 +148,230 @@ 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)
-
-- 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)
+ defined
+ - strings, buffers, and streams are sent to the client with appropriate headers
+ (no serialization)
+
+- 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 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 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, 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
+const Fastify = require('fastify')
+
+// Instantiate the framework
+const fastify = Fastify({
+ logger: true
+})
+
+// Register parent error handler
+fastify.setErrorHandler((error, request, reply) => {
+ reply.status(500).send({ ok: false })
+})
+
+fastify.register((app, options, next) => {
+ // Register child error handler
+ fastify.setErrorHandler((error, request, reply) => {
+ throw error
+ })
+
+ fastify.get('/bad', async () => {
+ // Throws a non-Error type, 'bar'
+ throw 'foo'
+ })
+
+ fastify.get('/good', async () => {
+ // Throws an Error instance, 'bar'
+ throw new Error('bar')
+ })
+
+ next()
+})
+
+// Run the server
+fastify.listen({ port: 3000 }, function (err, address) {
+ if (err) {
+ fastify.log.error(err)
+ process.exit(1)
+ }
+ // Server is listening at ${address}
+})
+```
### Fastify Error Codes
-#### FST_ERR_BAD_URL
-
-
-The router received an invalid url.
-
-
-#### FST_ERR_DUPLICATED_ROUTE
-
-The HTTP method already has a registered controller for that URL
-
-
-#### FST_ERR_CTP_ALREADY_PRESENT
-
-
-The parser for this content type was already registered.
-
-#### FST_ERR_CTP_BODY_TOO_LARGE
-
-
-The request body is larger than the provided limit.
-
-This setting can be defined in the Fastify server instance:
-[`bodyLimit`](./Server.md#bodylimit)
-
-#### FST_ERR_CTP_EMPTY_TYPE
-
-
-The content type cannot be an empty string.
-
-#### FST_ERR_CTP_INVALID_CONTENT_LENGTH
-
-
-Request body size did not match Content-Length.
-
-#### FST_ERR_CTP_INVALID_HANDLER
-
-
-An invalid handler was passed for the content type.
-
-#### FST_ERR_CTP_INVALID_MEDIA_TYPE
-
-
-The received media type is not supported (i.e. there is no suitable
-`Content-Type` parser for it).
-
-#### FST_ERR_CTP_INVALID_PARSE_TYPE
-
-
-The provided parse type is not supported. Accepted values are `string` or
-`buffer`.
-
-#### FST_ERR_CTP_INVALID_TYPE
-
-
-The `Content-Type` should be a string.
-
-#### FST_ERR_DEC_ALREADY_PRESENT
-
-
-A decorator with the same name is already registered.
-
-#### FST_ERR_DEC_MISSING_DEPENDENCY
-
-
-The decorator cannot be registered due to a missing dependency.
-
-#### FST_ERR_HOOK_INVALID_HANDLER
-
-
-The hook callback must be a function.
-
-#### FST_ERR_HOOK_INVALID_TYPE
-
-
-The hook name must be a string.
-
-#### FST_ERR_LOG_INVALID_DESTINATION
-
-
-The logger accepts either a `'stream'` or a `'file'` as the destination.
-
-#### FST_ERR_PROMISE_NOT_FULFILLED
-
-
-A promise may not be fulfilled with 'undefined' when statusCode is not 204.
-
-#### FST_ERR_REP_ALREADY_SENT
-
-
-A response was already sent.
-
-#### FST_ERR_REP_INVALID_PAYLOAD_TYPE
-
-
-Reply payload can be either a `string` or a `Buffer`.
-
-#### FST_ERR_SCH_ALREADY_PRESENT
-
-
-A schema with the same `$id` already exists.
-
-#### FST_ERR_SCH_MISSING_ID
-
-
-The schema provided does not have `$id` property.
-
-#### FST_ERR_SCH_SERIALIZATION_BUILD
-
-
-The JSON schema provided for serialization of a route response is not valid.
-
-#### FST_ERR_SCH_VALIDATION_BUILD
-
-
-The JSON schema provided for validation to a route is not valid.
-
-#### FST_ERR_SEND_INSIDE_ONERR
-
-
-You cannot use `send` inside the `onError` hook.
-
-#### FST_ERR_SEND_UNDEFINED_ERR
-
-
-Undefined error has occurred.
-
-
-#### FST_ERR_PLUGIN_NOT_VALID
-
-Plugin must be a function or a promise.
-
-
-#### FST_ERR_PLUGIN_TIMEOUT
-
-Plugin did not start in time. Default timeout (in millis): `10000`
-
-
-#### FST_ERR_HOOK_TIMEOUT
-
-A callback for a hook timed out
-
-
-#### FST_ERR_ROOT_PLG_BOOTED
-
-Root plugin has already booted (mapped directly from `avvio`)
-
-
-#### FST_ERR_PARENT_PLUGIN_BOOTED
-
-Impossible to load plugin because the parent (mapped directly from `avvio`)
-
-
-#### FST_ERR_PLUGIN_CALLBACK_NOT_FN
-
-Callback for a hook is not a function (mapped directly from `avvio`)
+You can access `errorCodes` for mapping:
+```js
+// ESM
+import { errorCodes } from 'fastify'
+
+// CommonJS
+const errorCodes = require('fastify').errorCodes
+```
+
+For example:
+```js
+const Fastify = require('fastify')
+
+// Instantiate the framework
+const fastify = Fastify({
+ logger: true
+})
+
+// Declare a route
+fastify.get('/', function (request, reply) {
+ reply.code('bad status code').send({ hello: 'world' })
+})
+
+fastify.setErrorHandler(function (error, request, reply) {
+ if (error instanceof Fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) {
+ // Log error
+ this.log.error(error)
+ // Send error response
+ reply.status(500).send({ ok: false })
+ } else {
+ // Fastify will use parent error handler to handle this
+ reply.send(error)
+ }
+})
+
+// Run the server!
+fastify.listen({ port: 3000 }, function (err, address) {
+ if (err) {
+ fastify.log.error(err)
+ process.exit(1)
+ }
+ // Server is now listening on ${address}
+})
+```
+
+Below is a table with all the error codes used by Fastify.
+
+| Code | Description | How to solve | Discussion |
+|------|-------------|--------------|------------|
+| FST_ERR_NOT_FOUND | 404 Not Found | - | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_OPTIONS_NOT_OBJ | Fastify options wrongly specified. | Fastify options should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_QSP_NOT_FN | QueryStringParser wrongly specified. | QueryStringParser option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN | SchemaController.bucket wrongly specified. | SchemaController.bucket option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN | SchemaErrorFormatter option wrongly specified. | SchemaErrorFormatter option should be a non async function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ | ajv.customOptions wrongly specified. | ajv.customOptions option should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR | ajv.plugins option wrongly specified. | ajv.plugins option should be an array. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_CTP_ALREADY_PRESENT | 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) |
+| FST_ERR_CTP_INVALID_TYPE | `Content-Type` wrongly specified | The `Content-Type` should be a string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_CTP_EMPTY_TYPE | `Content-Type` is an empty string. | `Content-Type` cannot be an empty string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_CTP_INVALID_HANDLER | Invalid handler for the content type. | Use a different handler. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_CTP_INVALID_PARSE_TYPE | The provided parse type is not supported. | Accepted values are string or buffer. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_CTP_BODY_TOO_LARGE | 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) |
+| FST_ERR_CTP_INVALID_MEDIA_TYPE | 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) |
+| FST_ERR_CTP_INVALID_CONTENT_LENGTH | Request body size did not match Content-Length. | Check the request body size and the Content-Length header. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_CTP_EMPTY_JSON_BODY | Body is not valid JSON but content-type is set to application/json. | Check if the request body is valid JSON. | [#5925](https://github.com/fastify/fastify/pull/5925) |
+| FST_ERR_CTP_INVALID_JSON_BODY | Body cannot be empty when content-type is set to application/json. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) |
+| FST_ERR_CTP_INSTANCE_ALREADY_STARTED | Fastify is already started. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_INSTANCE_ALREADY_LISTENING | Fastify instance is already listening. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_DEC_ALREADY_PRESENT | A decorator with the same name is already registered. | Use a different decorator name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_DEC_DEPENDENCY_INVALID_TYPE | The dependencies of decorator must be of type `Array`. | Use an array for the dependencies. | [#3090](https://github.com/fastify/fastify/pull/3090) |
+| FST_ERR_DEC_MISSING_DEPENDENCY | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_DEC_AFTER_START | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) |
+| FST_ERR_DEC_REFERENCE_TYPE | 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) |
+| FST_ERR_DEC_UNDECLARED | 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/)
+| FST_ERR_HOOK_INVALID_TYPE | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_HOOK_INVALID_HANDLER | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_HOOK_INVALID_ASYNC_HANDLER | 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) |
+| FST_ERR_HOOK_NOT_SUPPORTED | The hook is not supported. | Use a supported hook. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_MISSING_MIDDLEWARE | You must register a plugin for handling middlewares, visit [`Middleware`](./Middleware.md) for more info. | Register a plugin for handling middlewares. | [#2014](https://github.com/fastify/fastify/pull/2014) |
+| FST_ERR_HOOK_TIMEOUT | A callback for a hook timed out. | Increase the timeout for the hook. | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_LOG_INVALID_DESTINATION | 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) |
+| FST_ERR_LOG_INVALID_LOGGER | 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) |
+| FST_ERR_LOG_INVALID_LOGGER_INSTANCE | 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) |
+| FST_ERR_LOG_INVALID_LOGGER_CONFIG | 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) |
+| FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED | You cannot provide both `'logger'` and `'loggerInstance'`. | Please provide only one option. | [#5020](https://github.com/fastify/fastify/pull/5020) |
+| FST_ERR_REP_INVALID_PAYLOAD_TYPE | 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) |
+| FST_ERR_REP_RESPONSE_BODY_CONSUMED | 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) |
+| FST_ERR_REP_READABLE_STREAM_LOCKED | 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) |
+| FST_ERR_REP_ALREADY_SENT | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
+| FST_ERR_REP_SENT_VALUE | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
+| FST_ERR_SEND_INSIDE_ONERR | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) |
+| FST_ERR_SEND_UNDEFINED_ERR | Undefined error has occurred. | - | [#2074](https://github.com/fastify/fastify/pull/2074) |
+| FST_ERR_BAD_STATUS_CODE | The status code is not valid. | Use a valid status code. | [#2082](https://github.com/fastify/fastify/pull/2082) |
+| FST_ERR_BAD_TRAILER_NAME | Called `reply.trailer` with an invalid header name. | Use a valid header name. | [#3794](https://github.com/fastify/fastify/pull/3794) |
+| FST_ERR_BAD_TRAILER_VALUE | Called `reply.trailer` with an invalid type. Expected a function. | Use a function. | [#3794](https://github.com/fastify/fastify/pull/3794) |
+| FST_ERR_FAILED_ERROR_SERIALIZATION | Failed to serialize an error. | - | [#4601](https://github.com/fastify/fastify/pull/4601) |
+| FST_ERR_MISSING_SERIALIZATION_FN | Missing serialization function. | Add a serialization function. | [#3970](https://github.com/fastify/fastify/pull/3970) |
+| FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN | Missing `Content-Type` serialization function. | Add a serialization function. | [#4264](https://github.com/fastify/fastify/pull/4264) |
+| FST_ERR_REQ_INVALID_VALIDATION_INVOCATION | Invalid validation invocation. Missing validation function for HTTP part nor schema provided. | Add a validation function. | [#3970](https://github.com/fastify/fastify/pull/3970) |
+| FST_ERR_SCH_MISSING_ID | The schema provided does not have `$id` property. | Add a `$id` property. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_SCH_ALREADY_PRESENT | A schema with the same `$id` already exists. | Use a different `$id`. | [#1168](https://github.com/fastify/fastify/pull/1168) |
+| FST_ERR_SCH_CONTENT_MISSING_SCHEMA | A schema is missing for the corresponding content type. | Add a schema. | [#4264](https://github.com/fastify/fastify/pull/4264) |
+| FST_ERR_SCH_DUPLICATE | Schema with the same attribute already present! | Use a different attribute. | [#1954](https://github.com/fastify/fastify/pull/1954) |
+| FST_ERR_SCH_VALIDATION_BUILD | The JSON schema provided for validation to a route is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) |
+| FST_ERR_SCH_SERIALIZATION_BUILD | 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) |
+| FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX | Response schemas should be nested under a valid status code (2XX). | Use a valid status code. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_INIT_OPTS_INVALID | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) |
+| FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE | 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) |
+| FST_ERR_DUPLICATED_ROUTE | 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) |
+| FST_ERR_BAD_URL | The router received an invalid URL. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) |
+| FST_ERR_ASYNC_CONSTRAINT | The router received an error when using asynchronous constraints. | - | [#4323](https://github.com/fastify/fastify/pull/4323) |
+| FST_ERR_INVALID_URL | URL must be a string. | Use a string for the URL. | [#3653](https://github.com/fastify/fastify/pull/3653) |
+| FST_ERR_ROUTE_OPTIONS_NOT_OBJ | Options for the route must be an object. | Use an object for the route options. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_DUPLICATED_HANDLER | Duplicate handler for the route is not allowed. | Use a different handler. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_HANDLER_NOT_FN | Handler for the route must be a function. | Use a function for the handler. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_MISSING_HANDLER | Missing handler function for the route. | Add a handler function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_METHOD_INVALID | Method is not a valid value. | Use a valid value for the method. | [#4750](https://github.com/fastify/fastify/pull/4750) |
+| FST_ERR_ROUTE_METHOD_NOT_SUPPORTED | Method is not supported for the route. | Use a supported method. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED | Body validation schema route is not supported. | Use a different different method for the route. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT | `bodyLimit` option must be an integer. | Use an integer for the `bodyLimit` option. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_HANDLER_TIMEOUT | Request timed out. | Increase the `handlerTimeout` option or optimize the handler. | - |
+| FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT | `handlerTimeout` option must be a positive integer. | Use a positive integer for the `handlerTimeout` option. | - |
+| FST_ERR_ROUTE_REWRITE_NOT_STR | `rewriteUrl` needs to be of type `string`. | Use a string for the `rewriteUrl`. | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_REOPENED_CLOSE_SERVER | Fastify has already been closed and cannot be reopened. | - | [#2415](https://github.com/fastify/fastify/pull/2415) |
+| FST_ERR_REOPENED_SERVER | Fastify is already listening. | - | [#2415](https://github.com/fastify/fastify/pull/2415) |
+| FST_ERR_PLUGIN_VERSION_MISMATCH | Installed Fastify plugin mismatched expected version. | Use a compatible version of the plugin. | [#2549](https://github.com/fastify/fastify/pull/2549) |
+| FST_ERR_PLUGIN_CALLBACK_NOT_FN | Callback for a hook is not a function. | Use a function for the callback. | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_PLUGIN_NOT_VALID | Plugin must be a function or a promise. | Use a function or a promise for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_ROOT_PLG_BOOTED | Root plugin has already booted. | - | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_PARENT_PLUGIN_BOOTED | Impossible to load plugin because the parent (mapped directly from `avvio`) | - | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_PLUGIN_TIMEOUT | Plugin did not start in time. | Increase the timeout for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) |
+| FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE | The decorator is not present in the instance. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
+| FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER | The plugin being registered mixes async and callback styles. | - | [#5141](https://github.com/fastify/fastify/pull/5141) |
+| FST_ERR_VALIDATION | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) |
+| FST_ERR_LISTEN_OPTIONS_INVALID | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) |
+| FST_ERR_ERROR_HANDLER_NOT_FN | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | FST_ERR_ERROR_HANDLER_ALREADY_SET | 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) |
diff --git a/docs/Reference/HTTP2.md b/docs/Reference/HTTP2.md
index e868502763e..d6e7a2c46fd 100644
--- a/docs/Reference/HTTP2.md
+++ b/docs/Reference/HTTP2.md
@@ -2,13 +2,11 @@
## HTTP2
-_Fastify_ offers **experimental support** for HTTP2 starting from Node 8 LTS,
-which includes HTTP2 without a flag; HTTP2 is supported over either HTTPS or
-plaintext.
+_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)
@@ -17,8 +15,8 @@ HTTP2 is supported in all modern browsers __only over a secure connection__:
```js
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
const fastify = require('fastify')({
http2: true,
https: {
@@ -34,7 +32,8 @@ fastify.get('/', function (request, reply) {
fastify.listen({ port: 3000 })
```
-ALPN negotiation allows support for both HTTPS and HTTP/2 over the same socket.
+[ALPN negotiation](https://datatracker.ietf.org/doc/html/rfc7301) allows
+support for both HTTPS and HTTP/2 over the same socket.
Node core `req` and `res` objects can be either
[HTTP/1](https://nodejs.org/api/http.html) or
[HTTP/2](https://nodejs.org/api/http2.html). _Fastify_ supports this out of the
@@ -43,8 +42,8 @@ box:
```js
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
const fastify = require('fastify')({
http2: true,
https: {
@@ -62,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
@@ -70,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'
@@ -87,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
diff --git a/docs/Reference/Hooks.md b/docs/Reference/Hooks.md
index 3fd8f75245f..400b50d7ae8 100644
--- a/docs/Reference/Hooks.md
+++ b/docs/Reference/Hooks.md
@@ -19,11 +19,14 @@ are Request/Reply hooks and application hooks:
- [onSend](#onsend)
- [onResponse](#onresponse)
- [onTimeout](#ontimeout)
+ - [onRequestAbort](#onrequestabort)
- [Manage Errors from a hook](#manage-errors-from-a-hook)
- [Respond to a request from a hook](#respond-to-a-request-from-a-hook)
- [Application Hooks](#application-hooks)
- [onReady](#onready)
+ - [onListen](#onlisten)
- [onClose](#onclose)
+ - [preClose](#preclose)
- [onRoute](#onroute)
- [onRegister](#onregister)
- [Scope](#scope)
@@ -31,9 +34,10 @@ 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
@@ -65,9 +69,10 @@ 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
@@ -78,7 +83,7 @@ hooks, and a stream with the current request payload.
If it returns a value (via `return` or via the callback function), it must
return a stream.
-For instance, you can uncompress the request body:
+For instance, you can decompress the request body:
```js
fastify.addHook('preParsing', (request, reply, payload, done) => {
@@ -95,14 +100,20 @@ 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.
+
+> ℹ️ Note:
+> The size of the returned stream is checked to not exceed the limit
+> set in [`bodyLimit`](./Server.md#bodylimit) option.
### preValidation
@@ -124,6 +135,10 @@ fastify.addHook('preValidation', async (request, reply) => {
```
### preHandler
+
+The `preHandler` hook allows you to specify a function that is executed before
+a routes's handler.
+
```js
fastify.addHook('preHandler', (request, reply, done) => {
// some code
@@ -156,8 +171,9 @@ 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
@@ -179,13 +195,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 [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:
@@ -221,8 +236,9 @@ 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`, 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
@@ -244,6 +260,10 @@ 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.
+
### onTimeout
```js
@@ -262,8 +282,36 @@ fastify.addHook('onTimeout', async (request, reply) => {
`onTimeout` is useful if you need to monitor the request timed out in your
service (if the `connectionTimeout` property is set on the Fastify instance).
The `onTimeout` hook is executed when a request is timed out and the HTTP socket
-has been hanged up. Therefore, you will not be able to send data to the client.
+has been hung up. Therefore, you will not be able to send data to the client.
+
+> ℹ️ Note:
+> The `onTimeout` hook is triggered by socket-level timeouts set via
+> `connectionTimeout`. For application-level per-route timeouts, see the
+> [`handlerTimeout`](./Server.md#factory-handler-timeout) option which uses
+> `request.signal` for cooperative cancellation.
+### onRequestAbort
+
+```js
+fastify.addHook('onRequestAbort', (request, done) => {
+ // Some code
+ done()
+})
+```
+Or `async/await`:
+```js
+fastify.addHook('onRequestAbort', async (request) => {
+ // Some code
+ await asyncMethod()
+})
+```
+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.
+
+> ℹ️ 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()`
@@ -287,7 +335,7 @@ fastify.addHook('preHandler', (request, reply, done) => {
Or if you're using `async/await` you can just throw an error:
```js
-fastify.addHook('onResponse', async (request, reply) => {
+fastify.addHook('onRequest', async (request, reply) => {
throw new Error('Some error')
})
```
@@ -317,9 +365,11 @@ fastify.addHook('onRequest', (request, reply, done) => {
// Works with async functions too
fastify.addHook('preHandler', async (request, reply) => {
- await something()
- reply.send({ hello: 'world' })
+ setTimeout(() => {
+ reply.send({ hello: 'from prehandler' })
+ })
return reply // mandatory, so the request is not executed further
+// Commenting the line above will allow the hooks to continue and fail with FST_ERR_REP_ALREADY_SENT
})
```
@@ -360,7 +410,9 @@ fastify.addHook('preHandler', async (request, reply) => {
You can hook into the application-lifecycle as well.
- [onReady](#onready)
+- [onListen](#onlisten)
- [onClose](#onclose)
+- [preClose](#preclose)
- [onRoute](#onroute)
- [onRegister](#onregister)
@@ -387,14 +439,46 @@ 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
+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
+Fastify instance.
+
+This is an alternative to `fastify.server.on('listening', () => {})`.
+
+```js
+// callback style
+fastify.addHook('onListen', function (done) {
+ // Some code
+ const err = null;
+ done(err)
+})
+
+// or async/await style
+fastify.addHook('onListen', async function () {
+ // Some async code
+})
+```
+
+> ℹ️ Note:
+> This hook will not run when the server is started using
+> `fastify.inject()` or `fastify.ready()`.
+
### onClose
-Triggered when `fastify.close()` is invoked to stop the server. It is useful
-when [plugins](./Plugins.md) need a "shutdown" event, for example, to close an
-open connection to a database.
+Triggered when `fastify.close()` is invoked to stop the server. By the time
+`onClose` hooks execute, the HTTP server has already stopped listening, all
+in-flight HTTP requests have been completed, and connections have been drained.
+This makes `onClose` the safe place for [plugins](./Plugins.md) to release
+resources such as database connection pools, as no new requests will
+arrive.
-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
@@ -410,10 +494,74 @@ fastify.addHook('onClose', async (instance) => {
})
```
+#### Execution order
+
+When multiple `onClose` hooks are registered across plugins, child-plugin hooks
+execute before parent-plugin hooks. This means a database plugin's `onClose`
+hook will run before the root-level `onClose` hooks:
+
+```js
+fastify.register(function dbPlugin (instance, opts, done) {
+ instance.addHook('onClose', async (instance) => {
+ // Runs first — close the database pool
+ await instance.db.close()
+ })
+ done()
+})
+
+fastify.addHook('onClose', async (instance) => {
+ // Runs second — after child plugins have cleaned up
+})
+```
+
+See [`close`](./Server.md#close) for the full shutdown lifecycle.
+
+### preClose
+
+
+Triggered when `fastify.close()` is invoked to stop the server. At this
+point the server is already rejecting new requests with `503` (when
+[`return503OnClosing`](./Server.md#factory-return-503-on-closing) is `true`),
+but the HTTP server has not yet stopped listening and in-flight requests are
+still being processed.
+
+It is useful when [plugins](./Plugins.md) have set up state attached to the HTTP
+server that would prevent the server from closing, such as open WebSocket
+connections or Server-Sent Events streams that must be explicitly terminated for
+`server.close()` to complete.
+_It is unlikely you will need to use this hook_,
+use the [`onClose`](#onclose) for the most common case.
+
+```js
+// callback style
+fastify.addHook('preClose', (done) => {
+ // Some code
+ done()
+})
+
+// or async/await style
+fastify.addHook('preClose', async () => {
+ // Some async code
+ await removeSomeServerState()
+})
+```
+
+For example, closing WebSocket connections during shutdown:
+
+```js
+fastify.addHook('preClose', async () => {
+ // Close all WebSocket connections so that server.close() can complete.
+ // Without this, open connections would keep the server alive.
+ for (const ws of activeWebSockets) {
+ ws.close(1001, 'Server shutting down')
+ }
+})
+```
+
### onRoute
-Triggered when a new route is registered. Listeners are passed a `routeOptions`
+Triggered when a new route is registered. Listeners are passed a [`routeOptions`](./Routes.md#routes-options)
object as the sole parameter. The interface is synchronous, and, as such, the
listeners are not passed a callback. This hook is encapsulated.
@@ -446,6 +594,33 @@ fastify.addHook('onRoute', (routeOptions) => {
})
```
+To add more routes within an onRoute hook, the routes must
+be tagged correctly. The hook will run into an infinite loop if
+not tagged. The recommended approach is shown below.
+
+```js
+const kRouteAlreadyProcessed = Symbol('route-already-processed')
+
+fastify.addHook('onRoute', function (routeOptions) {
+ const { url, method } = routeOptions
+
+ const isAlreadyProcessed = (routeOptions.custom && routeOptions.custom[kRouteAlreadyProcessed]) || false
+
+ if (!isAlreadyProcessed) {
+ this.route({
+ url,
+ method,
+ custom: {
+ [kRouteAlreadyProcessed]: true
+ },
+ handler: () => {}
+ })
+ }
+})
+```
+
+For more details, see this [issue](https://github.com/fastify/fastify/issues/4319).
+
### onRegister
@@ -456,8 +631,9 @@ 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', [])
@@ -604,6 +780,12 @@ fastify.route({
// This hook will always be executed after the shared `onRequest` hooks
done()
},
+ // // Example with an async hook. All hooks support this syntax
+ //
+ // onRequest: async function (request, reply) {
+ // // This hook will always be executed after the shared `onRequest` hooks
+ // await ...
+ // }
onResponse: function (request, reply, done) {
// this hook will always be executed after the shared `onResponse` hooks
done()
@@ -648,7 +830,8 @@ 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
@@ -677,7 +860,7 @@ fastify.get('/me/is-admin', async function (req, reply) {
```
Note that `.authenticatedUser` could actually be any property name
-choosen by yourself. Using your own custom property prevents you
+chosen by yourself. Using your own custom property prevents you
from mutating existing properties, which
would be a dangerous and destructive operation. So be careful and
make sure your property is entirely new, also using this approach
@@ -703,19 +886,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
@@ -723,14 +898,14 @@ initialization of the tracking package, in the typical "require instrumentation
tools first" fashion.
```js
-const tracer = /* retrieved from elsehwere in the package */
-const dc = require('diagnostics_channel')
+const tracer = /* retrieved from elsewhere in the package */
+const dc = require('node:diagnostics_channel')
const channel = dc.channel('fastify.initialization')
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()
})
@@ -742,3 +917,42 @@ 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)
+})
+```
diff --git a/docs/Reference/Index.md b/docs/Reference/Index.md
index e72a380f66a..ef44aed2ec1 100644
--- a/docs/Reference/Index.md
+++ b/docs/Reference/Index.md
@@ -69,3 +69,5 @@ This table of contents is in alphabetical order.
+ [Validation and Serialization](./Validation-and-Serialization.md): Details
Fastify's support for validating incoming data and how Fastify serializes data
for responses.
++ [Warnings](./Warnings.md): Details the warnings Fastify emits and how to
+ solve them.
diff --git a/docs/Reference/LTS.md b/docs/Reference/LTS.md
index 8ed9431a94d..2e59c16726d 100644
--- a/docs/Reference/LTS.md
+++ b/docs/Reference/LTS.md
@@ -10,18 +10,25 @@ in this document:
versions, are supported for a minimum period of six months from their release
date. The release date of any specific version can be found at
[https://github.com/fastify/fastify/releases](https://github.com/fastify/fastify/releases).
-
2. Major releases will receive security updates for an additional six months
from the release of the next major release. After this period we will still
review and release security fixes as long as they are provided by the
community and they do not violate other constraints, e.g. minimum supported
Node.js version.
-
3. Major releases will be tested and verified against all Node.js release lines
that are supported by the [Node.js LTS
policy](https://github.com/nodejs/Release) within the LTS period of that
given Fastify release line. This implies that only the latest Node.js release
of a given line is supported.
+4. In addition to Node.js runtime, major releases of Fastify will also be tested
+ 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/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.
@@ -36,35 +43,44 @@ A "month" is defined as 30 consecutive days.
> 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.
-[semver]: https://semver.org/
+### 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
-| Version | Release Date | End Of LTS Date | Node.js |
-| :------ | :----------- | :-------------- | :------------------- |
-| 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 |
-| 4.0.0 | 2022-06-08 | TBD | 14, 16, 18 |
+| 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 | 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
-Fastify uses GitHub Actions for CI testing, please refer to [GitHub's
+Fastify uses GitHub Actions for CI testing, please refer to [GitHub's
documentation regarding workflow
-runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)
+runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners#supported-runners-and-hardware-resources)
for further details on what the latest virtual environment is in relation to the
YAML workflow labels below:
-| OS | YAML Workflow Label | Package Manager | Node.js |
-|---------|------------------------|---------------------------|--------------|
-| Linux | `ubuntu-latest` | npm | 14,16,18 |
-| Linux | `ubuntu-18.04` | yarn,pnpm | 14,16,18 |
-| Windows | `windows-latest` | npm | 14,16,18 |
-| MacOS | `macos-latest` | npm | 14,16,18 |
+| OS | YAML Workflow Label | Package Manager | Node.js | Nsolid(Node) |
+| ------- | ------------------- | --------------- | ----------- | ------------- |
+| 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
diff --git a/docs/Reference/Lifecycle.md b/docs/Reference/Lifecycle.md
index b12a1f22f62..1202759f46c 100644
--- a/docs/Reference/Lifecycle.md
+++ b/docs/Reference/Lifecycle.md
@@ -1,12 +1,13 @@
Fastify
## Lifecycle
-Following the schema of 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)*.
+This schema shows the internal lifecycle of 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
@@ -40,25 +41,28 @@ 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
+When `handlerTimeout` is configured, a timer starts after routing. If the
+response is not sent within the allowed time, `request.signal` is aborted and
+a 503 error is sent. The timer is cleared when the response finishes or when
+`reply.hijack()` is called. See [`handlerTimeout`](./Server.md#factory-handler-timeout).
+
+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
+
-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
@@ -71,16 +75,25 @@ 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:
+`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
+
+## Shutdown Lifecycle
+
-- 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
+When [`fastify.close()`](./Server.md#close) is called, the server goes through a
+graceful shutdown sequence involving
+[`preClose`](./Hooks.md#pre-close) hooks, connection draining, and
+[`onClose`](./Hooks.md#on-close) hooks. See the
+[`close`](./Server.md#close) method documentation for the full step-by-step
+lifecycle.
\ No newline at end of file
diff --git a/docs/Reference/Logging.md b/docs/Reference/Logging.md
index 960c8725b2f..94b7722bb74 100644
--- a/docs/Reference/Logging.md
+++ b/docs/Reference/Logging.md
@@ -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,29 +22,33 @@ const fastify = require('fastify')({
})
```
-Enabling the logger with appropriate configuration for both local development
-and production environment requires 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 = {
+ development: {
+ transport: {
+ target: 'pino-pretty',
+ options: {
+ translateTime: 'HH:MM:ss Z',
+ ignore: 'pid,hostname',
+ },
+ },
+ },
+ production: true,
+ test: false,
+}
const fastify = require('fastify')({
- logger: {
- transport:
- environment === 'development'
- ? {
- target: 'pino-pretty',
- options: {
- translateTime: 'HH:MM:ss Z',
- ignore: 'pid,hostname'
- }
- }
- : undefined
- }
+ 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) {
@@ -52,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#pinooptions-stream).
-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/main/docs/api.md#options)
+for available options. To specify a file destination, use:
```js
const fastify = require('fastify')({
@@ -77,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')
@@ -92,19 +97,27 @@ const fastify = require('fastify')({
})
```
-
+### Advanced Logger Configuration
+
+#### 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 behaviour
-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')({
logger: {
@@ -117,7 +130,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')({
@@ -136,12 +149,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
};
}
@@ -149,11 +161,41 @@ 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.
-See an approach to log `req.body`
+> ℹ️ 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')({
+ logger: {
+ transport: {
+ target: 'pino-pretty'
+ },
+ serializers: {
+ res (reply) {
+ // The default
+ return {
+ statusCode: reply.statusCode,
+ headers: typeof reply.getHeaders === 'function'
+ ? reply.getHeaders()
+ : {}
+ }
+ },
+ }
+ }
+});
+```
+
+> ℹ️ 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 the following approach to log `req.body`:
```js
app.addHook('preHandler', function (req, reply, done) {
@@ -164,19 +206,25 @@ app.addHook('preHandler', function (req, reply, done) {
})
```
+> ℹ️ 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`, `child`.
+### 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')
@@ -189,11 +237,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({
@@ -207,7 +255,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
}
diff --git a/docs/Reference/Middleware.md b/docs/Reference/Middleware.md
index 51d77a97214..e92a0c58c48 100644
--- a/docs/Reference/Middleware.md
+++ b/docs/Reference/Middleware.md
@@ -22,39 +22,40 @@ 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
-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('path')
+const path = require('node:path')
const serveStatic = require('serve-static')
// Single path
@@ -69,8 +70,8 @@ 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
diff --git a/docs/Reference/Plugins.md b/docs/Reference/Plugins.md
index 1e6a18f52b5..944553d76c3 100644
--- a/docs/Reference/Plugins.md
+++ b/docs/Reference/Plugins.md
@@ -1,21 +1,18 @@
Fastify
## 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`.
-
-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.
-
-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:
-```
+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*, 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.
+
+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
-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
-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'))
@@ -134,7 +126,7 @@ fastify.listen({ port: 3000 }, (err, address) => {
*async/await* is supported by `after`, `ready`, and `listen`, as well as
-`fastify` being a [Thenable](https://promisesaplus.com/).
+`fastify` being a Thenable.
```js
await fastify.register(require('my-plugin'))
@@ -145,12 +137,15 @@ await fastify.ready()
await fastify.listen({ port: 3000 })
```
+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
-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
@@ -175,21 +170,29 @@ export default plugin
### Create a plugin
-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 () {})
@@ -201,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
-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')
@@ -234,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 () {})
diff --git a/docs/Reference/Principles.md b/docs/Reference/Principles.md
new file mode 100644
index 00000000000..fc5694cbbc8
--- /dev/null
+++ b/docs/Reference/Principles.md
@@ -0,0 +1,73 @@
+# Technical Principles
+
+Every decision in the Fastify framework and its official plugins is guided by
+the following technical principles:
+
+1. “Zero” overhead in production
+2. “Good” developer experience
+3. Works great for small & big projects alike
+4. Easy to migrate to microservices (or even serverless) and back
+5. Security & data validation
+6. If something could be a plugin, it likely should be
+7. Easily testable
+8. Do not monkeypatch core
+9. Semantic versioning & Long Term Support
+10. Specification adherence
+
+## "Zero" Overhead in Production
+
+Fastify aims to implement features with minimal overhead. This is achieved by
+using fast algorithms, data structures, and JavaScript-specific features.
+
+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 its performance point.
+It offers a great out-of-the-box experience that is flexible enough to adapt to
+various situations.
+
+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
+
+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
+
+Route deployment should not matter. The framework should "just work".
+
+## Security and Data Validation
+
+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
+
+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
+
+Testing Fastify applications should be a first-class concern.
+
+## Do not monkeypatch core
+
+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
+
+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.
diff --git a/docs/Reference/Reply.md b/docs/Reference/Reply.md
index bc0b9bf30db..e950ee8a509 100644
--- a/docs/Reference/Reply.md
+++ b/docs/Reference/Reply.md
@@ -4,22 +4,25 @@
- [Reply](#reply)
- [Introduction](#introduction)
- [.code(statusCode)](#codestatuscode)
+ - [.elapsedTime](#elapsedtime)
- [.statusCode](#statuscode)
- [.server](#server)
- [.header(key, value)](#headerkey-value)
- - [set-cookie](#set-cookie)
- [.headers(object)](#headersobject)
- [.getHeader(key)](#getheaderkey)
- [.getHeaders()](#getheaders)
- [.removeHeader(key)](#removeheaderkey)
- [.hasHeader(key)](#hasheaderkey)
+ - [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback)
- [.trailer(key, function)](#trailerkey-function)
- [.hasTrailer(key)](#hastrailerkey)
- [.removeTrailer(key)](#removetrailerkey)
- - [.redirect([code,] dest)](#redirectcode--dest)
+ - [.redirect(dest, [code ,])](#redirectdest--code)
- [.callNotFound()](#callnotfound)
- - [.getResponseTime()](#getresponsetime)
- [.type(contentType)](#typecontenttype)
+ - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
+ - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
+ - [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus)
- [.serializer(func)](#serializerfunc)
- [.raw](#raw)
- [.sent](#sent)
@@ -29,6 +32,9 @@
- [Strings](#strings)
- [Streams](#streams)
- [Buffers](#buffers)
+ - [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)
@@ -43,6 +49,8 @@ object that exposes the following functions and properties:
- `.code(statusCode)` - Sets the status code.
- `.status(statusCode)` - An alias for `.code(statusCode)`.
- `.statusCode` - Read and set the HTTP status code.
+- `.elapsedTime` - Returns the amount of time passed
+since the request was received by Fastify.
- `.server` - A reference to the fastify instance object.
- `.header(name, value)` - Sets a response header.
- `.headers(object)` - Sets all the keys of the object as response headers.
@@ -50,16 +58,30 @@ object that exposes the following functions and properties:
- `.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.
- `.type(value)` - Sets the header `Content-Type`.
-- `.redirect([code,] dest)` - Redirect to the specified url, the status code is
- optional (default to `302`).
+- `.redirect(dest, [code,])` - Redirect to the specified URL, the status code is
+ optional (defaults to `302`).
- `.callNotFound()` - Invokes the custom not found handler.
- `.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
+ to the `SerializerCompiler` if provided, default to `undefined`.
+- `.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
+ 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
buffer, JSON, stream, or an Error object.
@@ -67,11 +89,10 @@ object that exposes the following functions and properties:
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.
-- `.context` - Access the [Request's context](./Request.md) property.
```js
fastify.get('/', options, function (request, reply) {
@@ -83,19 +104,21 @@ 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)
If not set via `reply.code`, the resulting `statusCode` will be `200`.
+### .elapsedTime
+
+
+Invokes the custom response time getter to calculate the amount of time passed
+since the request was received by Fastify.
+
+```js
+const milliseconds = reply.elapsedTime
+```
+
### .statusCode
@@ -129,14 +152,15 @@ 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
@@ -209,6 +233,27 @@ reply.getHeader('x-foo') // undefined
Returns a boolean indicating if the specified header has been set.
+### .writeEarlyHints(hints, callback)
+
+
+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: '; 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)
@@ -217,25 +262,35 @@ 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: Currently, the computation function only supports synchronous function.
-That means `async-await` and `promise` are not supported.*
+> ℹ️ Note:
+> Any error passed to `done` callback will be ignored. If you are interested
+> in the error, you can turn on `debug` level logging.
```js
reply.trailer('server-timing', function() {
return 'db;dur=53, app;dur=47.2'
})
-const { createHash } = require('crypto')
-// trailer function also recieve two argument
+const { createHash } = require('node:crypto')
+// trailer function also receive two argument
// @param {object} reply fastify reply
// @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
-reply.trailer('content-md5', function(reply, payload) {
+// @param {function} done callback to set trailer value
+reply.trailer('content-md5', function(reply, payload, done) {
+ const hash = createHash('md5')
+ hash.update(payload)
+ 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')
})
```
@@ -257,13 +312,14 @@ reply.getTrailer('server-timing') // undefined
```
-### .redirect([code ,] dest)
+### .redirect(dest, [code ,])
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
@@ -278,7 +334,7 @@ reply.redirect('/home')
Example (no `reply.code()` call) sets status code to `303` and redirects to
`/home`
```js
-reply.redirect(303, '/home')
+reply.redirect('/home', 303)
```
Example (`reply.code()` call) sets status code to `303` and redirects to `/home`
@@ -288,7 +344,7 @@ reply.code(303).redirect('/home')
Example (`reply.code()` call) sets status code to `302` and redirects to `/home`
```js
-reply.code(303).redirect(302, '/home')
+reply.code(303).redirect('/home', 302)
```
### .callNotFound()
@@ -301,19 +357,6 @@ hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler).
reply.callNotFound()
```
-### .getResponseTime()
-
-
-Invokes the custom response time getter to calculate the amount of time passed
-since the request was started.
-
-Note that unless this function is called in the [`onResponse`
-hook](./Hooks.md#onresponse) it will always return `0`.
-
-```js
-const milliseconds = reply.getResponseTime()
-```
-
### .type(contentType)
@@ -324,7 +367,199 @@ 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])
+
+
+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.
+
+This heavily depends of the `schema#responses` attached to the route, or
+the serialization functions compiled by using `compileSerializationSchema`.
+
+```js
+const serialize = reply
+ .getSerializationFunction({
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ })
+serialize({ foo: 'bar' }) // '{"foo":"bar"}'
+
+// or
+
+const serialize = reply
+ .getSerializationFunction(200)
+serialize({ foo: 'bar' }) // '{"foo":"bar"}'
+
+// or
+
+const serialize = reply
+ .getSerializationFunction(200, 'application/json')
+serialize({ foo: 'bar' }) // '{"foo":"bar"}'
+```
+
+See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
+for more information on how to compile serialization schemas.
+
+### .compileSerializationSchema(schema, [httpStatus], [contentType])
+
+
+This function will compile a serialization schema and
+return a function that can be used to serialize data.
+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
+to compile the serialization function if a custom `SerializerCompiler` is used.
+
+This heavily depends of the `schema#responses` attached to the route, or
+the serialization functions compiled by using `compileSerializationSchema`.
+
+```js
+const serialize = reply
+ .compileSerializationSchema({
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ })
+serialize({ foo: 'bar' }) // '{"foo":"bar"}'
+
+// or
+
+const serialize = reply
+ .compileSerializationSchema({
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ }, 200)
+serialize({ foo: 'bar' }) // '{"foo":"bar"}'
+
+// or
+
+const serialize = reply
+ .compileSerializationSchema({
+ '3xx': {
+ content: {
+ 'application/json': {
+ schema: {
+ name: { type: 'string' },
+ phone: { type: 'number' }
+ }
+ }
+ }
+ }
+ }, '3xx', 'application/json')
+serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
+```
+
+Note that you should be careful when using this function, as it will cache
+the compiled serialization functions based on the schema provided. If the
+schemas provided is mutated or changed, the serialization functions will not
+detect that the schema has been altered and for instance it will reuse the
+previously compiled serialization function based on the reference of the schema
+previously provided.
+
+If there's a need to change the properties of a schema, always opt to create
+a totally new object, otherwise the implementation won't benefit from the cache
+mechanism.
+
+:Using the following schema as example:
+```js
+const schema1 = {
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+}
+```
+
+*Not*
+```js
+const serialize = reply.compileSerializationSchema(schema1)
+
+// Later on...
+schema1.properties.foo.type. = 'integer'
+const newSerialize = reply.compileSerializationSchema(schema1)
+
+console.log(newSerialize === serialize) // true
+```
+
+*Instead*
+```js
+const serialize = reply.compileSerializationSchema(schema1)
+
+// Later on...
+const newSchema = Object.assign({}, schema1)
+newSchema.properties.foo.type = 'integer'
+
+const newSerialize = reply.compileSerializationSchema(newSchema)
+
+console.log(newSerialize === serialize) // false
+```
+
+### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
+
+
+This function will serialize the input data based on the provided schema
+or HTTP status code. If both are provided the `httpStatus` will take precedence.
+
+If there is not a serialization function for a given `schema` a new serialization
+function will be compiled, forwarding the `httpStatus` and `contentType` if provided.
+
+```js
+reply
+ .serializeInput({ foo: 'bar'}, {
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ }) // '{"foo":"bar"}'
+
+// or
+
+reply
+ .serializeInput({ foo: 'bar'}, {
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ }, 200) // '{"foo":"bar"}'
+
+// or
+
+reply
+ .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
+
+// or
+
+reply
+ .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
+```
+
+See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
+for more information on how to compile serialization schemas.
### .serializer(func)
@@ -358,7 +593,7 @@ values.
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.:
@@ -392,8 +627,9 @@ low-level request and response. Moreover, hooks will not be invoked.
*Modifying the `.sent` property directly is deprecated. Please use the
aforementioned `.hijack()` method to achieve the same effect.*
-
### .hijack()
+
+
Sometimes you might need to halt the execution of the normal request lifecycle
and handle sending the response manually.
@@ -439,9 +675,21 @@ fastify.get('/json', options, function (request, reply) {
If you pass a string to `send` without a `Content-Type`, it will be sent as
`text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a
string to `send`, it will be serialized with the custom serializer if one is
-set, otherwise, it will be sent unmodified (unless the `Content-Type` header is
-set to `application/json; charset=utf-8`, in which case it will be
-JSON-serialized like an object — see the section above).
+set, otherwise, it will be sent unmodified.
+
+> ℹ️ Note:
+> Even when the `Content-Type` header is set to `application/json`,
+> strings are sent unmodified by default. To serialize a string as JSON, you
+> must set a custom serializer:
+
+```js
+fastify.get('/json-string', async function (request, reply) {
+ reply
+ .type('application/json; charset=utf-8')
+ .serializer(JSON.stringify)
+ .send('Hello') // Returns "Hello" (JSON-encoded string)
+})
+```
```js
fastify.get('/json', options, function (request, reply) {
reply.send('plain string')
@@ -451,24 +699,47 @@ fastify.get('/json', options, function (request, reply) {
#### Streams
-*send* can also handle streams out of the box. If you are sending a stream and
-you have not set a `'Content-Type'` header, *send* will set it at
-`'application/octet-stream'`.
+If you are sending a stream and you have not set a `'Content-Type'` header,
+*send* will set it to `'application/octet-stream'`.
+
+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('fs')
const stream = fs.createReadStream('some-file', 'utf8')
+ reply.header('Content-Type', 'application/octet-stream')
reply.send(stream)
})
```
+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 stream = fs.createReadStream('some-file', 'utf8')
+ reply.header('Content-Type', 'application/octet-stream')
+ return reply.send(stream)
+})
+```
#### Buffers
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
+sent unmodified without response validation.
+
```js
-const fs = require('fs')
+const fs = require('node:fs')
+
fastify.get('/streams', function (request, reply) {
fs.readFile('some-file', (err, fileBuffer) => {
reply.send(err || fileBuffer)
@@ -476,6 +747,84 @@ 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)
+ })
+ return reply
+})
+```
+
+#### TypedArrays
+
+
+`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
+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)
+})
+```
+
+#### ReadableStream
+
+
+`ReadableStream` will be treated as a node stream mentioned above,
+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')
+ reply.send(ReadableStream.from(stream))
+})
+```
+
+#### Response
+
+
+`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
+without response validation.
+
+Please be aware when using `Response`, the status code and headers
+will not directly reflect to `reply.statusCode` and `reply.getHeaders()`.
+Such behavior is based on `Response` only allow `readonly` status
+code and headers. The data is not allow to be bi-direction editing,
+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)
+ const response = new Response(readableStream, {
+ status: 200,
+ headers: { 'content-type': 'application/octet-stream' }
+ })
+ reply.send(response)
+})
+```
+
+
#### Errors
@@ -494,8 +843,9 @@ 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
@@ -514,7 +864,7 @@ To customize the JSON error output you can do it by:
- add the additional properties to the `Error` instance
Notice that if the returned status code is not in the response schema list, the
-default behaviour will be applied.
+default behavior will be applied.
```js
fastify.get('/', {
@@ -542,14 +892,15 @@ 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')
@@ -557,6 +908,11 @@ fastify.setErrorHandler(function (error, request, reply) {
})
```
+Beware that calling `reply.send(error)` in your custom error handler will send
+the error to the default error handler.
+Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle)
+for more information.
+
The not found errors generated by the router will use the
[`setNotFoundHandler`](./Server.md#setnotfoundhandler)
@@ -591,6 +947,7 @@ Fastify natively handles promises and supports async-await.
*Note that in the following examples we are not using reply.send.*
```js
+const { promisify } = require('node:util')
const delay = promisify(setTimeout)
fastify.get('/promises', options, function (request, reply) {
@@ -640,6 +997,5 @@ For more details, see:
- https://github.com/fastify/fastify/issues/1864 for the discussion about this
feature
-- https://promisesaplus.com/ for the definition of thenables
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
for the signature
diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md
index 9784ed1f518..4931853d3cb 100644
--- a/docs/Reference/Request.md
+++ b/docs/Reference/Request.md
@@ -4,45 +4,86 @@
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
-- `routerMethod` - the method defined for the router that is handling the
- request
-- `routerPath` - 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` - 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.
+ ⚠ Security: this value comes from client-controlled headers; only trust it
+ when you control proxy behavior and have validated or allow-listed hosts.
+ No additional validation is performed beyond RFC parsing (see
+ [RFC 9110, section 7.2](https://www.rfc-editor.org/rfc/rfc9110#section-7.2) and
+ [RFC 3986, section 3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2)).
+- `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.
+- `signal` - An `AbortSignal` that aborts when the handler timeout
+ fires or the client disconnects. Created lazily on first access, so
+ there is zero overhead when not used. When
+ [`handlerTimeout`](./Server.md#factory-handler-timeout) is configured,
+ the signal is pre-created and also aborts on timeout. Pass it to
+ `fetch()`, database queries, or any API accepting a `signal` option
+ for cooperative cancellation. On timeout, `signal.reason` is the
+ `FST_ERR_HANDLER_TIMEOUT` error; on client disconnect it is a generic
+ `AbortError`. Check `signal.reason.code` to distinguish the two cases.
+- `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.
+- `routeOptions` - The route [`option`](./Routes.md#routes-options) object.
+ - `bodyLimit` - Either server limit or route limit.
+ - `handlerTimeout` - The handler timeout configured for this route.
+ - `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
+ 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`.
+- [.validateInput(data, schema | httpPart, [httpPart])](#validate) -
+ 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 = {
@@ -51,12 +92,16 @@ 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) {
@@ -69,11 +114,182 @@ 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.routerMethod)
- console.log(request.routerPath)
+ console.log(request.routeOptions.method)
+ console.log(request.routeOptions.bodyLimit)
+ console.log(request.routeOptions.method)
+ console.log(request.routeOptions.url)
+ console.log(request.routeOptions.attachValidation)
+ console.log(request.routeOptions.logLevel)
+ console.log(request.routeOptions.version)
+ console.log(request.routeOptions.exposeHeadRoute)
+ console.log(request.routeOptions.prefixTrailingSlash)
+ console.log(request.routeOptions.logLevel)
request.log.info('some info')
})
```
+### .getValidationFunction(schema | httpPart)
+
+
+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 an `errors` property. Errors encountered during the last
+validation are assigned to `errors`.
+
+```js
+const validate = request
+ .getValidationFunction({
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ })
+console.log(validate({ foo: 'bar' })) // true
+console.log(validate.errors) // null
+
+// or
+
+const validate = request
+ .getValidationFunction('body')
+console.log(validate({ foo: 0.5 })) // false
+console.log(validate.errors) // validation errors
+```
+
+See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
+for more information on compiling validation schemas.
+
+### .compileValidationSchema(schema, [httpPart])
+
+
+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 to the
+`ValidationCompiler`, allowing it to compile the validation function if a custom
+`ValidationCompiler` is provided for the route.
+
+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'
+ }
+ }
+ })
+console.log(validate({ foo: 'bar' })) // true
+console.log(validate.errors) // null
+
+// or
+
+const validate = request
+ .compileValidationSchema({
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ }, 200)
+console.log(validate({ hello: 'world' })) // false
+console.log(validate.errors) // validation errors
+```
+
+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 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
+const schema1 = {
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+}
+```
+
+*Not*
+```js
+const validate = request.compileValidationSchema(schema1)
+
+// Later on...
+schema1.properties.foo.type. = 'integer'
+const newValidate = request.compileValidationSchema(schema1)
+
+console.log(newValidate === validate) // true
+```
+
+*Instead*
+```js
+const validate = request.compileValidationSchema(schema1)
+
+// Later on...
+const newSchema = Object.assign({}, schema1)
+newSchema.properties.foo.type = 'integer'
+
+const newValidate = request.compileValidationSchema(newSchema)
+
+console.log(newValidate === validate) // false
+```
+
+### .validateInput(data, [schema | httpPart], [httpPart])
+
+
+This function validates the input based on the provided schema or HTTP part. If
+both are provided, the `httpPart` parameter takes precedence.
+
+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'
+ }
+ }
+ }) // true
+
+// or
+
+request
+ .validateInput({ foo: 'bar'}, {
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string'
+ }
+ }
+ }, 'body') // true
+
+// or
+
+request
+ .validateInput({ hello: 'world'}, 'query') // false
+```
+
+See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
+for more information on compiling validation schemas.
diff --git a/docs/Reference/Routes.md b/docs/Reference/Routes.md
index f213871c5dd..3cb215319c4 100644
--- a/docs/Reference/Routes.md
+++ b/docs/Reference/Routes.md
@@ -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,15 +31,17 @@ fastify.route(options)
### Routes options
-*`method`: currently it supports `'DELETE'`, `'GET'`, `'HEAD'`, `'PATCH'`,
- `'POST'`, `'PUT'` and `'OPTIONS'`. It could also be an array of methods.
+* `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
need to be in [JSON Schema](https://json-schema.org/) format, check
[here](./Validation-and-Serialization.md) for more info.
- * `body`: validates the body of the request if it is a POST, PUT, or PATCH
- method.
+ * `body`: validates the body of the request if it is a POST, PUT, PATCH,
+ 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
@@ -58,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
@@ -76,7 +78,7 @@ fastify.route(options)
when a response has been sent, so you will not be able to send more data to
the client. It could also be an array of functions.
* `onTimeout(request, reply, done)`: a [function](./Hooks.md#ontimeout) called
- when a request is timed out and the HTTP socket has been hanged up.
+ when a request is timed out and the HTTP socket has been hung up.
* `onError(request, reply, error, done)`: a [function](./Hooks.md#onerror)
called when an Error is thrown or sent to the client by the route handler.
* `handler(request, reply)`: the function that will handle this request. The
@@ -88,12 +90,20 @@ fastify.route(options)
To access the default handler, you can access `instance.errorHandler`. Note
that this will point to fastify's default `errorHandler` only if a plugin
hasn't overridden it already.
+* `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
+ [`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to
+ 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 } })`: function that
- builds schemas for response serialization. See the [Validation and
+* `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.
* `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from
@@ -105,11 +115,21 @@ fastify.route(options)
larger than this number of bytes. Must be an integer. You may also set this
option globally when first creating the Fastify instance with
`fastify(options)`. Defaults to `1048576` (1 MiB).
+* `handlerTimeout`: maximum number of milliseconds for the route's full
+ lifecycle. Overrides the server-level
+ [`handlerTimeout`](./Server.md#factory-handler-timeout). Must be a positive
+ integer. When the timeout fires, `request.signal` is aborted and a 503 error
+ is sent through the error handler (which can be customized per-route).
* `logLevel`: set log level for this route. See below.
* `logSerializers`: set serializers to log for this route.
* `config`: object used to store custom configuration.
* `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
+ built-in `version` and `host` constraints, with support for custom constraint
+ strategies.
* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a
route with a prefix.
* `both` (default): Will register both `/prefix` and `/prefix/`.
@@ -123,11 +143,12 @@ 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
@@ -136,8 +157,11 @@ fastify.route({
url: '/',
schema: {
querystring: {
- name: { type: 'string' },
- excitement: { type: 'integer' }
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ excitement: { type: 'integer' }
+ }
},
response: {
200: {
@@ -216,17 +240,18 @@ 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 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
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
@@ -248,9 +273,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) {
@@ -288,13 +312,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.
-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).
+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, `/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.
+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
```
@@ -305,23 +340,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)
})
```
@@ -349,48 +384,43 @@ 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
-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
-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
@@ -417,19 +447,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')()
@@ -441,23 +470,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
-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')
@@ -473,27 +500,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
-You might need different log levels in your routes; Fastify achieves this in a
-very straightforward way.
-
-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.
+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/main/docs/api.md#level-string).
-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
@@ -505,22 +528,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
-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
-[`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object)
-and attach them in the right context!
+In some contexts, logging a large object may waste resources. Define custom
+[`serializers`](https://github.com/pinojs/pino/blob/main/docs/api.md#serializers-object)
+and attach them in the appropriate context.
```js
const fastify = require('fastify')({ logger: true })
@@ -539,7 +561,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({
@@ -551,7 +573,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
}
@@ -588,7 +610,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)
@@ -600,31 +622,30 @@ fastify.listen({ port: 3000 })
### Constraints
-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 router's performance.
```js
fastify.route({
@@ -647,50 +668,49 @@ 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', async (req, reply) => {
-> if (req.headers['accept-version']) { // or the custom header you are using
+> fastify.addHook('onSend', (req, reply, payload, done) => {
+> 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)
> }
> }
+> done()
> })
> ```
-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({
method: 'GET',
url: '/',
- constraints: { host: 'auth.fastify.io' },
+ constraints: { host: 'auth.fastify.example' },
handler: function (request, reply) {
- reply.send('hello world from auth.fastify.io')
+ reply.send('hello world from auth.fastify.example')
}
})
@@ -698,7 +718,7 @@ fastify.inject({
method: 'GET',
url: '/',
headers: {
- 'Host': 'example.com'
+ 'Host': 'fastify.example'
}
}, (err, res) => {
// 404 because the host doesn't match the constraint
@@ -708,10 +728,10 @@ fastify.inject({
method: 'GET',
url: '/',
headers: {
- 'Host': 'auth.fastify.io'
+ 'Host': 'auth.fastify.dev'
}
}, (err, res) => {
- // => 'hello world from auth.fastify.io'
+ // => 'hello world from auth.fastify.dev'
})
```
@@ -722,30 +742,61 @@ matching wildcard subdomains (or any other pattern):
fastify.route({
method: 'GET',
url: '/',
- constraints: { host: /.*\.fastify\.io/ }, // will match any subdomain of fastify.io
+ constraints: { host: /.*\.fastify\.example/ }, // will match any subdomain of fastify.dev
handler: function (request, reply) {
reply.send('hello world from ' + request.headers.host)
}
})
```
-### ⚠ 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).
+#### Asynchronous Custom Constraints
+
+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) {
+ done(null, field)
+}
+
+const secret = {
+ // strategy name for referencing in the route handler `constraints` options
+ name: 'secret',
+ // storage factory for storing routes in the find-my-way route tree
+ storage: function () {
+ let handlers = {}
+ return {
+ get: (type) => { return handlers[type] || null },
+ set: (type, store) => { handlers[type] = store }
+ }
+ },
+ // function to get the value of the constraint from each incoming request
+ deriveConstraint: (req, ctx, done) => {
+ databaseOperation(req.headers['secret'], done)
+ },
+ // optional flag marking if handlers without constraints can match requests that have a value for this constraint
+ mustMatchWhenDerived: true
+}
+```
+
+> ⚠ 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, req, res) {
+> if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
+> res.code(400)
+> return res.send("Invalid header provided")
+> } else {
+> res.send(err)
+> }
+> }
+> })
+> ```
diff --git a/docs/Reference/Server.md b/docs/Reference/Server.md
index e0204aecf5f..36827525808 100644
--- a/docs/Reference/Server.md
+++ b/docs/Reference/Server.md
@@ -9,6 +9,7 @@ options object which is used to customize the resulting instance. This document
describes the properties available in that options object.
- [Factory](#factory)
+ - [`http`](#http)
- [`http2`](#http2)
- [`https`](#https)
- [`connectionTimeout`](#connectiontimeout)
@@ -16,26 +17,19 @@ describes the properties available in that options object.
- [`forceCloseConnections`](#forcecloseconnections)
- [`maxRequestsPerSocket`](#maxrequestspersocket)
- [`requestTimeout`](#requesttimeout)
- - [`ignoreTrailingSlash`](#ignoretrailingslash)
- - [`ignoreDuplicateSlashes`](#ignoreduplicateslashes)
- - [`maxParamLength`](#maxparamlength)
- [`bodyLimit`](#bodylimit)
- [`onProtoPoisoning`](#onprotopoisoning)
- [`onConstructorPoisoning`](#onconstructorpoisoning)
- [`logger`](#logger)
+ - [`loggerInstance`](#loggerinstance)
- [`disableRequestLogging`](#disablerequestlogging)
- [`serverFactory`](#serverfactory)
- - [`jsonShorthand`](#jsonshorthand)
- - [`caseSensitive`](#casesensitive)
- - [`allowUnsafeRegex`](#allowunsaferegex)
- [`requestIdHeader`](#requestidheader)
- [`requestIdLogLabel`](#requestidloglabel)
- [`genReqId`](#genreqid)
- [`trustProxy`](#trustproxy)
- [`pluginTimeout`](#plugintimeout)
- - [`querystringParser`](#querystringparser)
- [`exposeHeadRoutes`](#exposeheadroutes)
- - [`constraints`](#constraints)
- [`return503OnClosing`](#return503onclosing)
- [`ajv`](#ajv)
- [`serializerOpts`](#serializeropts)
@@ -43,6 +37,19 @@ describes the properties available in that options object.
- [`frameworkErrors`](#frameworkerrors)
- [`clientErrorHandler`](#clienterrorhandler)
- [`rewriteUrl`](#rewriteurl)
+ - [`allowErrorHandlerOverride`](#allowerrorhandleroverride)
+ - [RouterOptions](#routeroptions)
+ - [`allowUnsafeRegex`](#allowunsaferegex)
+ - [`buildPrettyMeta`](#buildprettymeta)
+ - [`caseSensitive`](#casesensitive)
+ - [`constraints`](#constraints)
+ - [`defaultRoute`](#defaultroute)
+ - [`ignoreDuplicateSlashes`](#ignoreduplicateslashes)
+ - [`ignoreTrailingSlash`](#ignoretrailingslash)
+ - [`maxParamLength`](#maxparamlength)
+ - [`onBadUrl`](#onbadurl)
+ - [`querystringParser`](#querystringparser)
+ - [`useSemicolonDelimiter`](#usesemicolondelimiter)
- [Instance](#instance)
- [Server Methods](#server-methods)
- [server](#server)
@@ -50,10 +57,10 @@ describes the properties available in that options object.
- [ready](#ready)
- [listen](#listen)
- [addresses](#addresses)
- - [getDefaultRoute](#getdefaultroute)
- - [setDefaultRoute](#setdefaultroute)
- [routing](#routing)
- [route](#route)
+ - [hasRoute](#hasroute)
+ - [findRoute](#findroute)
- [close](#close)
- [decorate\*](#decorate)
- [register](#register)
@@ -61,9 +68,11 @@ describes the properties available in that options object.
- [prefix](#prefix)
- [pluginName](#pluginname)
- [hasPlugin](#hasplugin)
+ - [listeningOrigin](#listeningorigin)
- [log](#log)
- [version](#version)
- [inject](#inject)
+ - [addHttpMethod](#addHttpMethod)
- [addSchema](#addschema)
- [getSchemas](#getschemas)
- [getSchema](#getschema)
@@ -77,6 +86,8 @@ describes the properties available in that options object.
- [schemaController](#schemacontroller)
- [setNotFoundHandler](#setnotfoundhandler)
- [setErrorHandler](#seterrorhandler)
+ - [setChildLoggerFactory](#setchildloggerfactory)
+ - [setGenReqId](#setgenreqid)
- [addConstraintStrategy](#addconstraintstrategy)
- [hasConstraintStrategy](#hasconstraintstrategy)
- [printRoutes](#printroutes)
@@ -88,204 +99,232 @@ describes the properties available in that options object.
- [getDefaultJsonParser](#getdefaultjsonparser)
- [defaultTextParser](#defaulttextparser)
- [errorHandler](#errorhandler)
+ - [childLoggerFactory](#childloggerfactory)
+ - [Symbol.asyncDispose](#symbolasyncdispose)
- [initialConfig](#initialconfig)
+### `http`
+
+
++ Default: `null`
+
+An object used to configure the server's listening socket. The options
+are the same as the Node.js core [`createServer`
+method](https://nodejs.org/docs/latest-v20.x/api/http.html#httpcreateserveroptions-requestlistener).
+
+This option is ignored if options [`http2`](#factory-http2) or
+[`https`](#factory-https) are set.
+
### `http2`
++ Default: `false`
+
If `true` Node.js core's
-[HTTP/2](https://nodejs.org/dist/latest-v14.x/docs/api/http2.html) module is
+[HTTP/2](https://nodejs.org/dist/latest-v20.x/docs/api/http2.html) module is
used for binding the socket.
-+ Default: `false`
-
### `https`
++ Default: `null`
+
An object used to configure the server's listening socket for TLS. The options
are the same as the Node.js core [`createServer`
-method](https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_createserver_options_requestlistener).
+method](https://nodejs.org/dist/latest-v20.x/docs/api/https.html#https_https_createserver_options_requestlistener).
When this property is `null`, the socket will not be configured for TLS.
This option also applies when the [`http2`](#factory-http2) option is set.
-+ Default: `null`
-
### `connectionTimeout`
++ Default: `0` (no timeout)
+
Defines the server timeout in milliseconds. See documentation for
[`server.timeout`
property](https://nodejs.org/api/http.html#http_server_timeout) to understand
-the effect of this option. When `serverFactory` option is specified, this option
-is ignored.
+the effect of this option.
-+ Default: `0` (no timeout)
+When `serverFactory` option is specified this option is ignored.
### `keepAliveTimeout`
++ Default: `72000` (72 seconds)
+
Defines the server keep-alive timeout in milliseconds. See documentation for
[`server.keepAliveTimeout`
property](https://nodejs.org/api/http.html#http_server_keepalivetimeout) to
understand the effect of this option. This option only applies when HTTP/1 is in
-use. Also, when `serverFactory` option is specified, this option is ignored.
+use.
-+ Default: `72000` (72 seconds)
+When `serverFactory` option is specified this option is ignored.
### `forceCloseConnections`
++ Default: `"idle"` if the HTTP server allows it, `false` otherwise
+
When set to `true`, upon [`close`](#close) the server will iterate the current
persistent connections and [destroy their
sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror).
-> Important: connections are not inspected to determine if requests have been
-> completed.
+When used with HTTP/2 server, it will also close all active HTTP/2 sessions.
+
+> ℹ️ Note:
+> Since Node.js v24 active sessions are closed by default
+
+
+> ⚠ Warning:
+> Connections are not inspected to determine if requests have
+> been completed.
Fastify will prefer the HTTP server's
[`closeAllConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseallconnections)
-method if supported, otherwise it will use internal connection tracking.
+method if supported, otherwise, it will use internal connection tracking.
When set to `"idle"`, upon [`close`](#close) the server will iterate the current
persistent connections which are not sending a request or waiting for a response
-and destroy their sockets. The value is supported only if the HTTP server
+and destroy their sockets. The value is only supported if the HTTP server
supports the
[`closeIdleConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseidleconnections)
method, otherwise attempting to set it will throw an exception.
-+ Default: `"idle"` if the HTTP server allows it, `false` otherwise
-
### `maxRequestsPerSocket`
-Defines the maximum number of requests socket can handle before closing keep
-alive connection. See documentation for [`server.maxRequestsPerSocket`
++ Default: `0` (no limit)
+
+Defines the maximum number of requests a socket can handle before closing keep
+alive connection. See [`server.maxRequestsPerSocket`
property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_maxrequestspersocket)
to understand the effect of this option. This option only applies when HTTP/1.1
is in use. Also, when `serverFactory` option is specified, this option is
ignored.
-> At the time of this writing, only node version greater or equal to 16.10.0
-> support this option. Check the Node.js documentation for availability in the
-> version you are running.
-+ Default: `0` (no limit)
+> ℹ️ Note:
+> At the time of writing, only node >= v16.10.0 supports this option.
### `requestTimeout`
++ Default: `0` (no limit)
+
Defines the maximum number of milliseconds for receiving the entire request from
-the client. [`server.requestTimeout`
+the client. See [`server.requestTimeout`
property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_requesttimeout)
-to understand the effect of this option. Also, when `serverFactory` option is
-specified, this option is ignored. It must be set to a non-zero value (e.g. 120
-seconds) to protect against potential Denial-of-Service attacks in case the
-server is deployed without a reverse proxy in front.
-> At the time of this writing, only node version greater or equal to 14.11.0
-> support this option. Check the Node.js documentation for availability in the
-> version you are running.
+to understand the effect of this option.
-+ Default: `0` (no limit)
+When `serverFactory` option is specified, this option is ignored.
+It must be set to a non-zero value (e.g. 120 seconds) to protect against potential
+Denial-of-Service attacks in case the server is deployed without a reverse proxy
+in front.
-### `ignoreTrailingSlash`
-
+> ℹ️ Note:
+> At the time of writing, only node >= v14.11.0 supports this option
-Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
-routing. By default, Fastify is set to take into account the trailing slashes.
-Paths like `/foo` and `/foo/` will be treated as different paths. If you want to
-change this, set this flag to `true`. That way, both `/foo` and `/foo/` will
-point to the same route. This option applies to *all* route registrations for
-the resulting server instance.
+### `handlerTimeout`
+
-+ Default: `false`
++ Default: `0` (no timeout)
+
+Defines the maximum number of milliseconds allowed for processing a request
+through the entire route lifecycle (from routing through onRequest, parsing,
+validation, handler execution, and serialization). If the response is not sent
+within this time, a `503 Service Unavailable` error is returned and
+`request.signal` is aborted.
+
+Unlike `connectionTimeout` and `requestTimeout` (which operate at the socket
+level), `handlerTimeout` is an application-level timeout that works correctly
+with HTTP keep-alive connections. It can be overridden per-route via
+[route options](./Routes.md#routes-options). When set at both levels, the
+route-level value takes precedence. Routes without an explicit `handlerTimeout`
+inherit the server default. Once a server-level timeout is set, individual
+routes cannot opt out of it — they can only override it with a different
+positive integer.
+
+The timeout is **cooperative**: when it fires, Fastify sends the 503 error
+response, but the handler's async work continues to run. Use
+[`request.signal`](./Request.md) to detect cancellation and stop ongoing work
+(database queries, HTTP requests, etc.). APIs that accept a `signal` option
+(`fetch()`, database drivers, `stream.pipeline()`) will cancel automatically.
+
+The timeout error (`FST_ERR_HANDLER_TIMEOUT`) is sent through the route's
+[error handler](./Routes.md#routes-options), which can be customized per-route
+to change the status code or response body.
+
+When `reply.hijack()` is called, the timeout timer is cleared — the handler
+takes full responsibility for the response lifecycle.
+
+> ℹ️ Note:
+> `handlerTimeout` does not apply to 404 handlers or custom not-found handlers
+> set via `setNotFoundHandler()`, as they bypass the route handler lifecycle.
```js
const fastify = require('fastify')({
- ignoreTrailingSlash: true
+ handlerTimeout: 10000 // 10s default for all routes
})
-// registers both "/foo" and "/foo/"
-fastify.get('/foo/', function (req, reply) {
- reply.send('foo')
+// Override per-route
+fastify.get('/slow-report', { handlerTimeout: 120000 }, async (request) => {
+ // Use request.signal for cooperative cancellation
+ const data = await db.query(longQuery, { signal: request.signal })
+ return data
})
-// registers both "/bar" and "/bar/"
-fastify.get('/bar', function (req, reply) {
- reply.send('bar')
-})
-```
-
-### `ignoreDuplicateSlashes`
-
-
-Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
-routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes
-from the path. It removes duplicate slashes in the route path and in the request
-URL. This option applies to *all* route registrations for the resulting server
-instance.
-
-Note that when `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set
-to true, Fastify will remove duplicate slashes, and then trailing slashes,
-meaning //a//b//c// will be converted to /a/b/c.
-
-+ Default: `false`
-
-```js
-const fastify = require('fastify')({
- ignoreDuplicateSlashes: true
-})
-
-// registers "/foo/bar/"
-fastify.get('///foo//bar//', function (req, reply) {
- reply.send('foo')
+// Customize the timeout response
+fastify.get('/custom-timeout', {
+ handlerTimeout: 5000,
+ errorHandler: (error, request, reply) => {
+ if (error.code === 'FST_ERR_HANDLER_TIMEOUT') {
+ reply.code(504).send({ error: 'Gateway Timeout' })
+ } else {
+ reply.send(error)
+ }
+ }
+}, async (request) => {
+ const result = await externalService.call({ signal: request.signal })
+ return result
})
```
-### `maxParamLength`
-
-
-You can set a custom length for parameters in parametric (standard, regex, and
-multi) routes by using `maxParamLength` option; the default value is 100
-characters.
-
-This can be useful especially if you have a regex-based route, protecting you
-against [DoS
-attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS).
-
-*If the maximum length limit is reached, the not found route will be invoked.*
-
### `bodyLimit`
-Defines the maximum payload, in bytes, the server is allowed to accept.
-
+ Default: `1048576` (1MiB)
+Defines the maximum payload, in bytes, the server is allowed to accept.
+The default body reader sends [`FST_ERR_CTP_BODY_TOO_LARGE`](./Errors.md#fst_err_ctp_body_too_large)
+reply, if the size of the body exceeds this limit.
+If [`preParsing` hook](./Hooks.md#preparsing) is provided, this limit is applied
+to the size of the stream the hook returns (i.e. the size of "decoded" body).
+
### `onProtoPoisoning`
++ Default: `'error'`
+
Defines what action the framework must take when parsing a JSON object with
`__proto__`. This functionality is provided by
[secure-json-parse](https://github.com/fastify/secure-json-parse). See
[Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about
prototype poisoning attacks.
-Possible values are `'error'`, `'remove'` and `'ignore'`.
-
-+ Default: `'error'`
+Possible values are `'error'`, `'remove'`, or `'ignore'`.
### `onConstructorPoisoning`
++ Default: `'error'`
+
Defines what action the framework must take when parsing a JSON object with
`constructor`. This functionality is provided by
[secure-json-parse](https://github.com/fastify/secure-json-parse). See
[Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about
prototype poisoning attacks.
-Possible values are `'error'`, `'remove'` and `'ignore'`.
-
-+ Default: `'error'`
+Possible values are `'error'`, `'remove'`, or `'ignore'`.
### `logger`
@@ -298,9 +337,6 @@ The possible values this property may have are:
+ Default: `false`. The logger is disabled. All logging methods will point to a
null logger [abstract-logging](https://npm.im/abstract-logging) instance.
-+ `pinoInstance`: a previously instantiated instance of Pino. The internal
- logger will point to this instance.
-
+ `object`: a standard Pino [options
object](https://github.com/pinojs/pino/blob/c77d8ec5ce/docs/API.md#constructor).
This will be passed directly to the Pino constructor. If the following
@@ -320,9 +356,15 @@ The possible values this property may have are:
```
Any user-supplied serializer will override the default serializer of the
corresponding property.
-+ `loggerInstance`: a custom logger instance. The logger must conform to the
- Pino interface by having the following methods: `info`, `error`, `debug`,
- `fatal`, `warn`, `trace`, `child`. For example:
+
+### `loggerInstance`
+
+
++ Default: `null`
+
+A custom logger instance. The logger must be a Pino instance or conform to the
+Pino interface by having the following methods: `info`, `error`, `debug`,
+`fatal`, `warn`, `trace`, `child`. For example:
```js
const pino = require('pino')();
@@ -340,19 +382,44 @@ The possible values this property may have are:
},
};
- const fastify = require('fastify')({logger: customLogger});
+ const fastify = require('fastify')({ loggerInstance: customLogger });
```
### `disableRequestLogging`
-By default, when logging is enabled, Fastify will issue an `info` level log
++ Default: `false`
+
+When logging is enabled, Fastify will issue an `info` level log
message when a request is received and when the response for that request has
been sent. By setting this option to `true`, these log messages will be
disabled. This allows for more flexible request start and end logging by
attaching custom `onRequest` and `onResponse` hooks.
-+ Default: `false`
+This option can also be a function that receives the Fastify request object
+and returns a boolean. This allows for conditional request logging based on the
+request properties (e.g., URL, headers, decorations).
+
+```js
+const fastify = require('fastify')({
+ logger: true,
+ disableRequestLogging: (request) => {
+ // Disable logging for health check endpoints
+ return request.url === '/health' || request.url === '/ready'
+ }
+})
+```
+
+The other log entries that will be disabled are:
+- an error log written by the default `onResponse` hook on reply callback errors
+- the error and info logs written by the `defaultErrorHandler`
+on error management
+- the info log written by the `fourOhFour` handler when a
+non existent route is requested
+
+Other log messages emitted by Fastify will stay enabled,
+like deprecation warnings and messages
+emitted when requests are received while the server is closing.
```js
// Examples of hooks to replicate the disabled functionality.
@@ -367,9 +434,6 @@ fastify.addHook('onResponse', (req, reply, done) => {
})
```
-Please note that this setting will also disable an error log written by the
-default `onResponse` hook on reply callback errors.
-
### `serverFactory`
@@ -404,114 +468,59 @@ enhance the server instance inside the `serverFactory` function before the
`return` statement.
-### `jsonShorthand`
-
-
-+ Default: `true`
-
-Internally, and by default, Fastify will automatically infer the root properties
-of JSON Schemas if it does not find valid root properties according to the JSON
-Schema spec. If you wish to implement your own schema validation compiler, for
-example: to parse schemas as JTD instead of JSON Schema, then you can explicitly
-set this option to `false` to make sure the schemas you receive are unmodified
-and are not being treated internally as JSON Schema.
-
-```js
-const AjvJTD = require('ajv/dist/jtd'/* only valid for AJV v7+ */)
-const ajv = new AjvJTD({
- // This would let you throw at start for invalid JTD schema objects
- allErrors: process.env.NODE_ENV === 'development'
-})
-const fastify = Fastify({ jsonShorthand: false })
-fastify.setValidatorCompiler(({ schema }) => {
- return ajv.compile(schema)
-})
-fastify.post('/', {
- schema: {
- body: {
- properties: {
- foo: { type: 'uint8' }
- }
- }
- },
- handler (req, reply) { reply.send({ ok: 1 }) }
-})
-```
-
-**Note: Fastify does not currently throw on invalid schemas, so if you turn this
-off in an existing project, you need to be careful that none of your existing
-schemas become invalid as a result, since they will be treated as a catch-all.**
-
-### `caseSensitive`
-
-
-By default, value equal to `true`, routes are registered as case sensitive. That
-is, `/foo` is not equivalent to `/Foo`. When set to `false`, routes are
-registered in a fashion such that `/foo` is equivalent to `/Foo` which is
-equivalent to `/FOO`.
-
-By setting `caseSensitive` to `false`, all paths will be matched as lowercase,
-but the route parameters or wildcards will maintain their original letter
-casing.
-
-```js
-fastify.get('/user/:username', (request, reply) => {
- // Given the URL: /USER/NodeJS
- console.log(request.params.username) // -> 'NodeJS'
-})
-```
-
-Please note that setting this option to `false` goes against
-[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1).
-
-Also note, this setting will not affect query strings. If you want to change the
-way query strings are handled take a look at
-[`querystringParser`](#querystringparser).
+### `requestIdHeader`
+
++ Default: `'request-id'`
-### `allowUnsafeRegex`
-
+The header name used to set the request-id. See [the
+request-id](./Logging.md#logging-request-id) section.
+Setting `requestIdHeader` to `true` will set the `requestIdHeader` to
+`"request-id"`.
+Setting `requestIdHeader` to a non-empty string will use
+the specified string as the `requestIdHeader`.
+By default `requestIdHeader` is set to `false` and will immediately use [genReqId](#genreqid).
+Setting `requestIdHeader` to an empty String (`""`) will set the
+requestIdHeader to `false`.
-The allowUnsafeRegex setting is false by default, so routes only allow safe
-regular expressions. To use unsafe expressions, set allowUnsafeRegex to true.
++ Default: `false`
```js
-fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => {
- // Throws an error without allowUnsafeRegex = true
+const fastify = require('fastify')({
+ requestIdHeader: 'x-custom-id', // -> use 'X-Custom-Id' header if available
+ //requestIdHeader: false, // -> always use genReqId
})
```
-Under the hood: [FindMyWay](https://github.com/delvedor/find-my-way) More info
-about safe regexp: [Safe-regex2](https://www.npmjs.com/package/safe-regex2)
-
-
-### `requestIdHeader`
-
-
-The header name used to know the request-id. See [the
-request-id](./Logging.md#logging-request-id) section.
-
-+ Default: `'request-id'`
+> ⚠ Warning:
+> Enabling this allows any callers to set `reqId` to a
+> value of their choosing.
+> No validation is performed on `requestIdHeader`.
### `requestIdLogLabel`
-Defines the label used for the request identifier when logging the request.
-
+ Default: `'reqId'`
+Defines the label used for the request identifier when logging the request.
+
### `genReqId`
-Function for generating the request-id. It will receive the incoming request as
-a parameter. This function is expected to be error-free.
-
+ Default: `value of 'request-id' header if provided or monotonically increasing
integers`
+Function for generating the request-id. It will receive the _raw_ incoming
+request as a parameter. This function is expected to be error-free.
+
Especially in distributed systems, you may want to override the default ID
generation behavior as shown below. For generating `UUID`s you may want to check
-out [hyperid](https://github.com/mcollina/hyperid)
+out [hyperid](https://github.com/mcollina/hyperid).
+
+> ℹ️ Note:
+> `genReqId` will be not called if the header set in
+> [requestIdHeader](#requestidheader) is available (defaults to
+> 'request-id').
```js
let i = 0
@@ -520,21 +529,9 @@ const fastify = require('fastify')({
})
```
-**Note: genReqId will _not_ be called if the header set in
-[requestIdHeader](#requestidheader) is available (defaults to
-'request-id').**
-
### `trustProxy`
-By enabling the `trustProxy` option, Fastify will know that it is sitting behind
-a proxy and that the `X-Forwarded-*` header fields may be trusted, which
-otherwise may be easily spoofed.
-
-```js
-const fastify = Fastify({ trustProxy: true })
-```
-
+ Default: `false`
+ `true/false`: Trust all proxies (`true`) or do not trust any proxies
(`false`).
@@ -542,86 +539,360 @@ const fastify = Fastify({ trustProxy: true })
comma separated values (e.g. `'127.0.0.1,192.168.1.1/24'`).
+ `Array`: Trust only given IP/CIDR list (e.g. `['127.0.0.1']`).
+ `number`: Trust the nth hop from the front-facing proxy server as the client.
-+ `Function`: Custom trust function that takes `address` as first arg
++ `Function`: Custom trust function that takes `address` as first argument
```js
function myTrustFn(address, hop) {
return address === '1.2.3.4' || hop === 1
}
```
+By enabling the `trustProxy` option, Fastify will know that it is sitting behind
+a proxy and that the `X-Forwarded-*` header fields may be trusted, which
+otherwise may be easily spoofed.
+
+```js
+const fastify = Fastify({ trustProxy: true })
+```
+
For more examples, refer to the
-[`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package.
+[`@fastify/proxy-addr`](https://www.npmjs.com/package/@fastify/proxy-addr) package.
-You may access the `ip`, `ips`, `hostname` and `protocol` values on the
+You may access the `ip`, `ips`, `host` and `protocol` values on the
[`request`](./Request.md) object.
```js
fastify.get('/', (request, reply) => {
console.log(request.ip)
console.log(request.ips)
- console.log(request.hostname)
+ console.log(request.host)
console.log(request.protocol)
})
```
-**Note: if a request contains multiple x-forwarded-host or
-x-forwarded-proto headers, it is only the last one that is used to
-derive request.hostname and request.protocol**
+> ℹ️ Note:
+> If a request contains multiple `x-forwarded-host` or `x-forwarded-proto`
+> headers, it is only the last one that is used to derive `request.hostname`
+> and `request.protocol`.
### `pluginTimeout`
++ Default: `10000`
+
The maximum amount of time in *milliseconds* in which a plugin can load. If not,
[`ready`](#ready) will complete with an `Error` with code
`'ERR_AVVIO_PLUGIN_TIMEOUT'`. When set to `0`, disables this check. This
controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter.
-+ Default: `10000`
-
### `querystringParser`
-The default query string parser that Fastify uses is the Node.js's core
-`querystring` module.
+The default query string parser that Fastify uses is a more performant fork
+of Node.js's core `querystring` module called
+[`fast-querystring`](https://github.com/anonrig/fast-querystring).
-You can change this default setting by passing the option `querystringParser`
-and use a custom one, such as [`qs`](https://www.npmjs.com/package/qs).
+You can use this option to use a custom parser, such as
+[`qs`](https://www.npmjs.com/package/qs).
+
+If you only want the keys (and not the values) to be case insensitive we
+recommend using a custom parser to convert only the keys to lowercase.
```js
const qs = require('qs')
const fastify = require('fastify')({
- querystringParser: str => qs.parse(str)
+ routerOptions: {
+ querystringParser: str => qs.parse(str)
+ }
})
```
-You can also use Fastify's default parser but change some handling behaviour,
+You can also use Fastify's default parser but change some handling behavior,
like the example below for case insensitive keys and values:
```js
-const querystring = require('querystring')
+const querystring = require('fast-querystring')
const fastify = require('fastify')({
- querystringParser: str => querystring.parse(str.toLowerCase())
+ routerOptions: {
+ querystringParser: str => querystring.parse(str.toLowerCase())
+ }
})
```
-Note, if you only want the keys (and not the values) to be case insensitive we
-recommend using a custom parser to convert only the keys to lowercase.
-
### `exposeHeadRoutes`
++ Default: `true`
+
Automatically creates a sibling `HEAD` route for each `GET` route defined. If
you want a custom `HEAD` handler without disabling this option, make sure to
define it before the `GET` route.
+### `return503OnClosing`
+
+
++ Default: `true`
+
+When `true`, any request arriving after [`close`](#close) has been called will
+receive a `503 Service Unavailable` response with `Connection: close` header
+(HTTP/1.1). This lets load balancers detect that the server is shutting down and
+stop routing traffic to it.
+
+When `false`, requests arriving during the closing phase are routed and
+processed normally. They will still receive a `Connection: close` header so that
+clients do not attempt to reuse the connection.
+
+### `ajv`
+
+
+Configure the Ajv v8 instance used by Fastify without providing a custom one.
+The default configuration is explained in the
+[#schema-validator](./Validation-and-Serialization.md#schema-validator) section.
+
+```js
+const fastify = require('fastify')({
+ ajv: {
+ customOptions: {
+ removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org/options.html#removeadditional)
+ },
+ plugins: [
+ require('ajv-merge-patch'),
+ [require('ajv-keywords'), 'instanceof']
+ // Usage: [plugin, pluginOptions] - Plugin with options
+ // Usage: plugin - Plugin without options
+ ],
+ onCreate: (ajv) => {
+ // Modify the ajv instance as you need.
+ ajv.addFormat('myFormat', (data) => typeof data === 'string')
+ }
+ }
+})
+```
+
+### `serializerOpts`
+
+
+Customize the options of the default
+[`fast-json-stringify`](https://github.com/fastify/fast-json-stringify#options)
+instance that serializes the response's payload:
+
+```js
+const fastify = require('fastify')({
+ serializerOpts: {
+ rounding: 'ceil'
+ }
+})
+```
+
+### `http2SessionTimeout`
+
+
++ Default: `72000`
+
+Set a default
+[timeout](https://nodejs.org/api/http2.html#http2sessionsettimeoutmsecs-callback)
+to every incoming HTTP/2 session in milliseconds. The session will be closed on
+the timeout.
+
+This option is needed to offer a graceful "close" experience when using
+HTTP/2. The low default has been chosen to mitigate denial of service attacks.
+When the server is behind a load balancer or can scale automatically this value
+can be increased to fit the use case. Node core defaults this to `0`.
+
+### `frameworkErrors`
+
+
++ Default: `null`
+
+Fastify provides default error handlers for the most common use cases. It is
+possible to override one or more of those handlers with custom code using this
+option.
+
+> ℹ️ Note:
+> Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at present.
+
+```js
+const fastify = require('fastify')({
+ frameworkErrors: function (error, req, res) {
+ if (error instanceof FST_ERR_BAD_URL) {
+ res.code(400)
+ return res.send("Provided url is not valid")
+ } else if(error instanceof FST_ERR_ASYNC_CONSTRAINT) {
+ res.code(400)
+ return res.send("Provided header is not valid")
+ } else {
+ res.send(err)
+ }
+ }
+})
+```
+
+### `clientErrorHandler`
+
+
+Set a
+[clientErrorHandler](https://nodejs.org/api/http.html#http_event_clienterror)
+that listens to `error` events emitted by client connections and responds with a
+`400`.
+
+It is possible to override the default `clientErrorHandler` using this option.
+
++ Default:
+```js
+function defaultClientErrorHandler (err, socket) {
+ if (err.code === 'ECONNRESET') {
+ return
+ }
+
+ const body = JSON.stringify({
+ error: http.STATUS_CODES['400'],
+ message: 'Client Error',
+ statusCode: 400
+ })
+ this.log.trace({ err }, 'client error')
+
+ if (socket.writable) {
+ socket.end([
+ 'HTTP/1.1 400 Bad Request',
+ `Content-Length: ${body.length}`,
+ `Content-Type: application/json\r\n\r\n${body}`
+ ].join('\r\n'))
+ }
+}
+```
+
+> ℹ️ Note:
+> `clientErrorHandler` operates with raw sockets. The handler is expected to
+> return a properly formed HTTP response that includes a status line, HTTP headers
+> and a message body. Before attempting to write the socket, the handler should
+> check if the socket is still writable as it may have already been destroyed.
+
+```js
+const fastify = require('fastify')({
+ clientErrorHandler: function (err, socket) {
+ const body = JSON.stringify({
+ error: {
+ message: 'Client error',
+ code: '400'
+ }
+ })
+
+ // `this` is bound to fastify instance
+ this.log.trace({ err }, 'client error')
+
+ // the handler is responsible for generating a valid HTTP response
+ socket.end([
+ 'HTTP/1.1 400 Bad Request',
+ `Content-Length: ${body.length}`,
+ `Content-Type: application/json\r\n\r\n${body}`
+ ].join('\r\n'))
+ }
+})
+```
+
+### `rewriteUrl`
+
+
+Set a sync callback function that must return a string that allows rewriting
+URLs. This is useful when you are behind a proxy that changes the URL.
+Rewriting a URL will modify the `url` property of the `req` object.
+
+Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it
+is an instance-wide configuration.
+
+```js
+// @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object.
+// @this Fastify The root Fastify instance (not an encapsulated instance).
+// @returns {string} The path that the request should be mapped to.
+function rewriteUrl (req) {
+ if (req.url === '/hi') {
+ this.log.debug({ originalUrl: req.url, url: '/hello' }, 'rewrite url');
+ return '/hello'
+ } else {
+ return req.url;
+ }
+}
+```
+
+## RouterOptions
+
+
+Fastify uses [`find-my-way`](https://github.com/delvedor/find-my-way) for its
+HTTP router. The `routerOptions` parameter allows passing
+[`find-my-way` options](https://github.com/delvedor/find-my-way?tab=readme-ov-file#findmywayoptions)
+to customize the HTTP router within Fastify.
+
+### `allowUnsafeRegex`
+
+
++ Default `false`
+
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which is,
+disabled by default, so routes only allow safe regular expressions. To use
+unsafe expressions, set `allowUnsafeRegex` to `true`.
+
+```js
+fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => {
+ // Throws an error without allowUnsafeRegex = true
+})
+```
+
+
+### `buildPrettyMeta`
+
+
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which
+supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta`
+function to sanitize a route's store object to use with the `prettyPrint`
+functions. This function should accept a single object and return an object.
+
+```js
+fastify.get('/user/:username', (request, reply) => {
+ routerOptions: {
+ buildPrettyMeta: route => {
+ const cleanMeta = Object.assign({}, route.store)
+
+ // remove private properties
+ Object.keys(cleanMeta).forEach(k => {
+ if (typeof k === 'symbol') delete cleanMeta[k]
+ })
+
+ return cleanMeta // this will show up in the pretty print output!
+ })
+ }
+})
+```
+
+### `caseSensitive`
+
+
+ Default: `true`
+When `true` routes are registered as case-sensitive. That is, `/foo`
+is not equal to `/Foo`.
+When `false` then routes are case-insensitive.
+
+Please note that setting this option to `false` goes against
+[RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1).
+
+By setting `caseSensitive` to `false`, all paths will be matched as lowercase,
+but the route parameters or wildcards will maintain their original letter
+casing.
+This option does not affect query strings, please refer to
+[`querystringParser`](#querystringparser) to change their handling.
+
+```js
+fastify.get('/user/:username', (request, reply) => {
+ // Given the URL: /USER/NodeJS
+ console.log(request.params.username) // -> 'NodeJS'
+})
+```
+
### `constraints`
-Fastify's built in route constraints are provided by `find-my-way`, which allow
-constraining routes by `version` or `host`. You are able to add new constraint
-strategies, or override the built in strategies by providing a `constraints`
+Fastify's built-in route constraints are provided by `find-my-way`, which
+allows constraining routes by `version` or `host`. You can add new constraint
+strategies, or override the built-in strategies, by providing a `constraints`
object with strategies for `find-my-way`. You can find more information on
constraint strategies in the
[find-my-way](https://github.com/delvedor/find-my-way) documentation.
@@ -641,173 +912,214 @@ const customVersionStrategy = {
}
const fastify = require('fastify')({
- constraints: {
- version: customVersionStrategy
+ routerOptions: {
+ constraints: {
+ version: customVersionStrategy
+ }
}
})
```
-### `return503OnClosing`
-
+### `defaultRoute`
+
-Returns 503 after calling `close` server method. If `false`, the server routes
-the incoming request as usual.
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports,
+can pass a default route with the option defaultRoute.
-+ Default: `true`
+```js
+const fastify = require('fastify')({
+ routerOptions: {
+ defaultRoute: (req, res) => {
+ res.statusCode = 404
+ res.end()
+ }
+ }
+})
+```
-### `ajv`
-
+> ℹ️ Note:
+> The `req` and `res` objects passed to `defaultRoute` are the raw Node.js
+> `IncomingMessage` and `ServerResponse` instances. They do **not** expose the
+> Fastify-specific methods available on `FastifyRequest`/`FastifyReply` (for
+> example, `res.send`).
-Configure the Ajv v8 instance used by Fastify without providing a custom one.
-The default configuration is explained in the
-[#schema-validator](Validation-and-Serialization.md#schema-validator) section.
+### `ignoreDuplicateSlashes`
+
-```js
-const fastify = require('fastify')({
- ajv: {
- customOptions: {
- removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org/#options)
- },
- plugins: [
- require('ajv-merge-patch'),
- [require('ajv-keywords'), 'instanceof']
- // Usage: [plugin, pluginOptions] - Plugin with options
- // Usage: plugin - Plugin without options
- ]
++ Default: `false`
+
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
+routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes
+from the path. It removes duplicate slashes in the route path and the request
+URL. This option applies to *all* route registrations for the resulting server
+instance.
+
+When `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set
+to `true` Fastify will remove duplicate slashes, and then trailing slashes,
+meaning `//a//b//c//` will be converted to `/a/b/c`.
+
+```js
+const fastify = require('fastify')({
+ routerOptions: {
+ ignoreDuplicateSlashes: true
}
})
+
+// registers "/foo/bar/"
+fastify.get('///foo//bar//', function (req, reply) {
+ reply.send('foo')
+})
```
-### `serializerOpts`
-
+### `ignoreTrailingSlash`
+
-Customize the options of the default
-[`fast-json-stringify`](https://github.com/fastify/fast-json-stringify#options)
-instance that serialize the response's payload:
++ Default: `false`
+
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
+routing. By default, Fastify will take into account the trailing slashes.
+Paths like `/foo` and `/foo/` are treated as different paths. If you want to
+change this, set this flag to `true`. That way, both `/foo` and `/foo/` will
+point to the same route. This option applies to *all* route registrations for
+the resulting server instance.
```js
const fastify = require('fastify')({
- serializerOpts: {
- rounding: 'ceil'
+ routerOptions: {
+ ignoreTrailingSlash: true
}
})
+
+// registers both "/foo" and "/foo/"
+fastify.get('/foo/', function (req, reply) {
+ reply.send('foo')
+})
+
+// registers both "/bar" and "/bar/"
+fastify.get('/bar', function (req, reply) {
+ reply.send('bar')
+})
```
-### `http2SessionTimeout`
-
+### `maxParamLength`
+
-Set a default
-[timeout](https://nodejs.org/api/http2.html#http2_http2session_settimeout_msecs_callback)
-to every incoming HTTP/2 session. The session will be closed on the timeout.
-Default: `72000` ms.
++ Default: `100`
-Note that this is needed to offer the graceful "close" experience when using
-HTTP/2. The low default has been chosen to mitigate denial of service attacks.
-When the server is behind a load balancer or can scale automatically this value
-can be increased to fit the use case. Node core defaults this to `0`. `
+You can set a custom length for parameters in parametric (standard, regex, and
+multi) routes by using `maxParamLength` option; the default value is 100
+characters. If the maximum length limit is reached, the not found route will
+be invoked.
-### `frameworkErrors`
-
+This can be useful especially if you have a regex-based route, protecting you
+against [ReDoS
+attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS).
-+ Default: `null`
-Fastify provides default error handlers for the most common use cases. It is
-possible to override one or more of those handlers with custom code using this
-option.
+### `onBadUrl`
+
-*Note: Only `FST_ERR_BAD_URL` is implemented at the moment.*
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports,
+the use case of a badly formatted url (eg: /hello/%world), by default find-my-way
+will invoke the defaultRoute, unless you specify the onBadUrl option.
```js
const fastify = require('fastify')({
- frameworkErrors: function (error, req, res) {
- if (error instanceof FST_ERR_BAD_URL) {
- res.code(400)
- return res.send("Provided url is not valid")
- } else {
- res.send(err)
+ routerOptions: {
+ onBadUrl: (path, req, res) => {
+ res.statusCode = 400
+ res.end(`Bad path: ${path}`)
}
}
})
```
-### `clientErrorHandler`
-
+As with `defaultRoute`, `req` and `res` are the raw Node.js request/response
+objects and do not provide Fastify's decorated helpers.
-Set a
-[clientErrorHandler](https://nodejs.org/api/http.html#http_event_clienterror)
-that listens to `error` events emitted by client connections and responds with a
-`400`.
+### `querystringParser`
+
-It is possible to override the default `clientErrorHandler` using this option.
+The default query string parser that Fastify uses is the Node.js's core
+`querystring` module.
-+ Default:
-```js
-function defaultClientErrorHandler (err, socket) {
- if (err.code === 'ECONNRESET') {
- return
- }
+You can use this option to use a custom parser, such as
+[`qs`](https://www.npmjs.com/package/qs).
- const body = JSON.stringify({
- error: http.STATUS_CODES['400'],
- message: 'Client Error',
- statusCode: 400
- })
- this.log.trace({ err }, 'client error')
+If you only want the keys (and not the values) to be case insensitive we
+recommend using a custom parser to convert only the keys to lowercase.
- if (socket.writable) {
- socket.end([
- 'HTTP/1.1 400 Bad Request',
- `Content-Length: ${body.length}`,
- `Content-Type: application/json\r\n\r\n${body}`
- ].join('\r\n'))
+```js
+const qs = require('qs')
+const fastify = require('fastify')({
+ routerOptions: {
+ querystringParser: str => qs.parse(str)
}
-}
+})
```
-*Note: `clientErrorHandler` operates with raw socket. The handler is expected to
-return a properly formed HTTP response that includes a status line, HTTP headers
-and a message body. Before attempting to write the socket, the handler should
-check if the socket is still writable as it may have already been destroyed.*
+You can also use Fastify's default parser but change some handling behavior,
+like the example below for case insensitive keys and values:
```js
+const querystring = require('node:querystring')
const fastify = require('fastify')({
- clientErrorHandler: function (err, socket) {
- const body = JSON.stringify({
- error: {
- message: 'Client error',
- code: '400'
- }
- })
+ routerOptions: {
+ querystringParser: str => querystring.parse(str.toLowerCase())
+ }
+})
+```
- // `this` is bound to fastify instance
- this.log.trace({ err }, 'client error')
+### `useSemicolonDelimiter`
+
- // the handler is responsible for generating a valid HTTP response
- socket.end([
- 'HTTP/1.1 400 Bad Request',
- `Content-Length: ${body.length}`,
- `Content-Type: application/json\r\n\r\n${body}`
- ].join('\r\n'))
++ Default `false`
+
+Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which supports,
+separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`.
+This decision originated from [delvedor/find-my-way#76]
+(https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support
+backwards compatibility for the need to split on `;`. To enable support for splitting
+on `;` set `useSemicolonDelimiter` to `true`.
+
+```js
+const fastify = require('fastify')({
+ routerOptions: {
+ useSemicolonDelimiter: true
}
})
+
+fastify.get('/dev', async (request, reply) => {
+ // An example request such as `/dev;foo=bar`
+ // Will produce the following query params result `{ foo = 'bar' }`
+ return request.query
+})
```
-### `rewriteUrl`
-
+### `allowErrorHandlerOverride`
+
-Set a sync callback function that must return a string that allows rewriting
-URLs.
+* **Default:** `true`
+
+> ⚠ Warning:
+> This option will be set to `false` by default
+> in the next major release.
-> Rewriting a URL will modify the `url` property of the `req` object
+When set to `false`, it prevents `setErrorHandler` from being called
+multiple times within the same scope, ensuring that the previous error
+handler is not unintentionally overridden.
+
+#### Example of incorrect usage:
```js
-function rewriteUrl (req) { // req is the Node.js HTTP request
- return req.url === '/hi' ? '/hello' : req.url;
-}
-```
+app.setErrorHandler(function freeSomeResources () {
+ // Never executed, memory leaks
+})
-Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it
-is an instance-wide configuration.
+app.setErrorHandler(function anotherErrorHandler () {
+ // Overrides the previous handler
+})
+```
## Instance
@@ -820,6 +1132,10 @@ is an instance-wide configuration.
[server](https://nodejs.org/api/http.html#http_class_http_server) object as
returned by the [**`Fastify factory function`**](#factory).
+> ⚠ Warning:
+> If utilized improperly, certain Fastify features could be disrupted.
+> It is recommended to only use it for attaching listeners.
+
#### after
@@ -889,14 +1205,27 @@ fastify.ready().then(() => {
Starts the server and internally waits for the `.ready()` event. The signature
is `.listen([options][, callback])`. Both the `options` object and the
-`callback` parameters follow the [Node.js
-core][https://nodejs.org/api/net.html#serverlistenoptions-callback] parameter
-definitions.
+`callback` parameters extend the [Node.js
+core](https://nodejs.org/api/net.html#serverlistenoptions-callback) options
+object. Thus, all core options are available with the following additional
+Fastify specific options:
+
+* listenTextResolver: Set an optional resolver for the text to log after server
+has been successfully started. It is possible to override the default
+`Server listening at [address]` log entry using this option.
+
+ ```js
+ server.listen({
+ port: 9080,
+ listenTextResolver: (address) => { return `Prometheus metrics server is listening at ${address}` }
+ })
+ ```
By default, the server will listen on the address(es) resolved by `localhost`
when no specific host is provided. If listening on any available interface is
desired, then specifying `0.0.0.0` for the address will listen on all IPv4
-addresses. The following table details the possible values for `host` when
+addresses. The address argument provided above will then return the first such
+IPv4 address. The following table details the possible values for `host` when
targeting `localhost`, and what the result of those values for `host` will be.
Host | IPv4 | IPv6
@@ -997,49 +1326,63 @@ const addresses = fastify.addresses()
Note that the array contains the `fastify.server.address()` too.
-#### getDefaultRoute
-
+#### routing
+
-The `defaultRoute` handler handles requests that do not match any URL specified
-by your Fastify application. This defaults to the 404 handler, but can be
-overridden with [setDefaultRoute](#setdefaultroute). Method to get the
-`defaultRoute` for the server:
+Method to access the `lookup` method of the internal router and match the
+request to the appropriate handler:
```js
-const defaultRoute = fastify.getDefaultRoute()
+fastify.routing(req, res)
```
-#### setDefaultRoute
-
+#### route
+
+
+Method to add routes to the server, it also has shorthand functions, check
+[here](./Routes.md).
+
+#### hasRoute
+
-**Note**: The default 404 handler, or one set using `setNotFoundHandler`, will
-never trigger if the default route is overridden. Use
-[setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling
-instead. Method to set the `defaultRoute` for the server:
+Method to check if a route is already registered to the internal router. It
+expects an object as the payload. `url` and `method` are mandatory fields. It
+is possible to also specify `constraints`. The method returns `true` if the
+route is registered or `false` if not.
```js
-const defaultRoute = function (req, res) {
- res.end('hello world')
-}
+const routeExists = fastify.hasRoute({
+ url: '/',
+ method: 'GET',
+ constraints: { version: '1.0.0' } // optional
+})
-fastify.setDefaultRoute(defaultRoute)
+if (routeExists === false) {
+ // add route
+}
```
-#### routing
-
+#### findRoute
+
-Method to access the `lookup` method of the internal router and match the
-request to the appropriate handler:
+Method to retrieve a route already registered to the internal router. It
+expects an object as the payload. `url` and `method` are mandatory fields. It
+is possible to also specify `constraints`.
+The method returns a route object or `null` if the route cannot be found.
```js
-fastify.routing(req, res)
-```
+const route = fastify.findRoute({
+ url: '/artists/:artistId',
+ method: 'GET',
+ constraints: { version: '1.0.0' } // optional
+})
-#### route
-
+if (route !== null) {
+ // perform some route checks
+ console.log(route.params) // `{artistId: ':artistId'}`
+}
+```
-Method to add routes to the server, it also has shorthand functions, check
-[here](./Routes.md).
#### close
@@ -1061,6 +1404,53 @@ fastify.close().then(() => {
})
```
+##### Shutdown lifecycle
+
+When `fastify.close()` is called, the following steps happen in order:
+
+1. The server is flagged as **closing**. New incoming requests receive a
+ `Connection: close` header (HTTP/1.1) and are handled according to
+ [`return503OnClosing`](#factory-return-503-on-closing).
+2. [`preClose`](./Hooks.md#pre-close) hooks execute. The server is still
+ processing in-flight requests at this point.
+3. **Connection draining** based on the
+ [`forceCloseConnections`](#forcecloseconnections) option:
+ - `"idle"` — idle keep-alive connections are closed; in-flight requests
+ continue.
+ - `true` — all persistent connections are destroyed immediately.
+ - `false` — no forced closure; idle connections remain open until they time
+ out naturally (see [`keepAliveTimeout`](#keepalivetimeout)).
+4. The HTTP server **stops accepting** new TCP connections
+ (`server.close()`). Node.js waits for all in-flight requests to complete
+ before invoking the callback.
+5. [`onClose`](./Hooks.md#on-close) hooks execute. All in-flight requests have
+ completed and the server is no longer listening.
+6. The `close` callback (or the returned Promise) resolves.
+
+```
+fastify.close() called
+ │
+ ├─▶ closing = true (new requests receive 503)
+ │
+ ├─▶ preClose hooks
+ │ (in-flight requests still active)
+ │
+ ├─▶ Connection draining (forceCloseConnections)
+ │
+ ├─▶ server.close()
+ │ (waits for in-flight requests to complete)
+ │
+ ├─▶ onClose hooks
+ │ (server stopped, all requests done)
+ │
+ └─▶ close callback / Promise resolves
+```
+
+> ℹ️ Note:
+> Upgraded connections (such as WebSocket) are not tracked by the HTTP
+> server and will prevent `server.close()` from completing. Close them
+> explicitly in a [`preClose`](./Hooks.md#pre-close) hook.
+
#### decorate*
@@ -1112,24 +1502,28 @@ fastify.register(function (instance, opts, done) {
Name of the current plugin. The root plugin is called `'fastify'`. There are
-three ways to define a name (in order).
+different ways to define a name (in order).
1. If you use [fastify-plugin](https://github.com/fastify/fastify-plugin) the
metadata `name` is used.
-2. If you `module.exports` a plugin the filename is used.
-3. If you use a regular [function
- declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions)
+2. If the exported plugin has the `Symbol.for('fastify.display-name')` property,
+ then the value of that property is used.
+ Example: `pluginFn[Symbol.for('fastify.display-name')] = "Custom Name"`
+3. If you `module.exports` a plugin the filename is used.
+4. If you use a regular [function
+ declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#defining_functions)
the function name is used.
*Fallback*: The first two lines of your plugin will represent the plugin name.
Newlines are replaced by ` -- `. This will help to identify the root cause when
you deal with many plugins.
-Important: If you have to deal with nested plugins, the name differs with the
-usage of the [fastify-plugin](https://github.com/fastify/fastify-plugin) because
-no new scope is created and therefore we have no place to attach contextual
-data. In that case, the plugin name will represent the boot order of all
-involved plugins in the format of `fastify -> plugin-A -> plugin-B`.
+> ⚠ Warning:
+> If you have to deal with nested plugins, the name differs with the usage of
+> the [fastify-plugin](https://github.com/fastify/fastify-plugin) because
+> no new scope is created and therefore we have no place to attach contextual
+> data. In that case, the plugin name will represent the boot order of all
+> involved plugins in the format of `fastify -> plugin-A -> plugin-B`.
#### hasPlugin
@@ -1150,6 +1544,15 @@ fastify.ready(() => {
})
```
+#### listeningOrigin
+
+
+The current origin the server is listening to.
+For example, a TCP socket based server returns
+a base address like `http://127.0.0.1:3000`,
+and a Unix socket server will return the socket
+path, e.g. `fastify.temp.sock`.
+
#### log
@@ -1168,6 +1571,35 @@ used by plugins.
Fake HTTP injection (for testing purposes)
[here](../Guides/Testing.md#benefits-of-using-fastifyinject).
+#### addHttpMethod
+
+
+Fastify supports the `GET`, `HEAD`, `TRACE`, `DELETE`, `OPTIONS`,
+`PATCH`, `PUT` and `POST` HTTP methods by default.
+The `addHttpMethod` method allows to add any non standard HTTP
+methods to the server that are [supported by Node.js](https://nodejs.org/api/http.html#httpmethods).
+
+```js
+// Add a new HTTP method called 'MKCOL' that supports a request body
+fastify.addHttpMethod('MKCOL', { hasBody: true, })
+
+// Add a new HTTP method called 'COPY' that does not support a request body
+fastify.addHttpMethod('COPY')
+```
+
+After calling `addHttpMethod`, it is possible to use the route shorthand
+methods to define routes for the new HTTP method:
+
+```js
+fastify.addHttpMethod('MKCOL', { hasBody: true })
+fastify.mkcol('/', (req, reply) => {
+ // Handle the 'MKCOL' request
+})
+```
+
+> ⚠ Warning:
+> `addHttpMethod` overrides existing methods.
+
#### addSchema
@@ -1223,7 +1655,9 @@ Set the schema error formatter for all routes. See
Set the schema serializer compiler for all routes. See
[#schema-serializer](./Validation-and-Serialization.md#schema-serializer).
-**Note:** [`setReplySerializer`](#set-reply-serializer) has priority if set!
+
+> ℹ️ Note:
+> [`setReplySerializer`](#set-reply-serializer) has priority if set!
#### validatorCompiler
@@ -1258,9 +1692,7 @@ This property can be used to fully manage:
- `compilersFactory`: what module must compile the JSON schemas
It can be useful when your schemas are stored in another data structure that is
-unknown to Fastify. See [issue
-#2446](https://github.com/fastify/fastify/issues/2446) for an example of what
-this property helps to resolve.
+unknown to Fastify.
Another use case is to tweak all the schemas processing. Doing so it is possible
to use Ajv v8 JTD or Standalone feature. To use such as JTD or the Standalone
@@ -1300,7 +1732,7 @@ const fastify = Fastify({
},
/**
- * The compilers factory let you fully control the validator and serializer
+ * The compilers factory lets you fully control the validator and serializer
* in the Fastify's lifecycle, providing the encapsulation to your compilers.
*/
compilersFactory: {
@@ -1335,7 +1767,7 @@ const fastify = Fastify({
buildSerializer: function factory (externalSchemas, serializerOptsServerOption) {
// This factory function must return a schema serializer compiler.
// See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer) for details.
- return function serializerCompiler ({ schema, method, url, httpStatus }) {
+ return function serializerCompiler ({ schema, method, url, httpStatus, contentType }) {
return data => JSON.stringify(data)
}
}
@@ -1352,15 +1784,20 @@ call is encapsulated by prefix, so different plugins can set different not found
handlers if a different [`prefix` option](./Plugins.md#route-prefixing-option)
is passed to `fastify.register()`. The handler is treated as a regular route
handler so requests will go through the full [Fastify
-lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well.
+lifecycle](./Lifecycle.md#lifecycle) for unexisting URLs.
+*async-await* is supported as well.
+Badly formatted URLs are sent to the [`onBadUrl`](#onbadurl)
+handler instead.
You can also register [`preValidation`](./Hooks.md#route-hooks) and
[`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler.
-_Note: The `preValidation` hook registered using this method will run for a
-route that Fastify does not recognize and **not** when a route handler manually
-calls [`reply.callNotFound`](./Reply.md#call-not-found)_. In which case, only
-preHandler will be run.
+> ℹ️ Note:
+> The `preValidation` hook registered using this method will run for a
+> route that Fastify does not recognize and **not** when a route handler manually
+> calls [`reply.callNotFound`](./Reply.md#call-not-found). In which case, only
+> preHandler will be run.
+
```js
fastify.setNotFoundHandler({
@@ -1391,16 +1828,34 @@ plugins are registered. If you would like to augment the behavior of the default
arguments `fastify.setNotFoundHandler()` within the context of these registered
plugins.
+> ℹ️ Note:
+> Some config properties from the request object will be
+> undefined inside the custom not found handler. E.g.:
+> `request.routeOptions.url`, `routeOptions.method` and `routeOptions.config`.
+> This method design goal is to allow calling the common not found route.
+> To return a per-route customized 404 response, you can do it in
+> the response itself.
+
#### setErrorHandler
`fastify.setErrorHandler(handler(error, request, reply))`: Set a function that
-will be called whenever an error happens. The handler is bound to the Fastify
-instance and is fully encapsulated, so different plugins can set different error
-handlers. *async-await* is supported as well.
-
-*Note: If the error `statusCode` is less than 400, Fastify will automatically
-set it at 500 before calling the error handler.*
+will be invoked whenever an exception is thrown during the request lifecycle.
+The handler is bound to the Fastify instance and is fully encapsulated, so
+different plugins can set different error handlers. *async-await* is
+supported as well.
+
+If the error `statusCode` is less than 400, Fastify will automatically
+set it to 500 before calling the error handler.
+
+`setErrorHandler` will ***not*** catch:
+- exceptions thrown in an `onResponse` hook because the response has already been
+ sent to the client. Use the `onSend` hook instead.
+- not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler)
+ instead.
+- Stream errors thrown during piping into the response socket, as
+ headers/response were already sent to the client.
+ Use custom in-stream data to signal such errors.
```js
fastify.setErrorHandler(function (error, request, reply) {
@@ -1416,7 +1871,7 @@ is set. It can be accessed using `fastify.errorHandler` and it logs the error
with respect to its `statusCode`.
```js
-var statusCode = error.statusCode
+const statusCode = error.statusCode
if (statusCode >= 500) {
log.error(error)
} else if (statusCode >= 400) {
@@ -1426,6 +1881,107 @@ if (statusCode >= 500) {
}
```
+> ⚠ Warning:
+> Avoid calling setErrorHandler multiple times in the same scope.
+> See [`allowErrorHandlerOverride`](#allowerrorhandleroverride).
+
+##### Custom error handler for stream replies
+
+
+If `Content-Type` differs between the endpoint and error handler, explicitly
+define it in both. For example, if the endpoint returns an `application/text`
+stream and the error handler responds with `application/json`, the error handler
+must explicitly set `Content-Type`. Otherwise, it will fail serialization with
+a `500` status code. Alternatively, always respond with serialized data in the
+error handler by manually calling a serialization method (e.g.,
+`JSON.stringify`).
+
+```js
+fastify.setErrorHandler((err, req, reply) => {
+ reply
+ .code(400)
+ .type('application/json')
+ .send({ error: err.message })
+})
+```
+
+```js
+fastify.setErrorHandler((err, req, reply) => {
+ reply
+ .code(400)
+ .send(JSON.stringify({ error: err.message }))
+})
+```
+
+#### setChildLoggerFactory
+
+
+`fastify.setChildLoggerFactory(factory(logger, bindings, opts, rawReq))`: Set a
+function that will be called when creating a child logger instance for each request
+which allows for modifying or adding child logger bindings and logger options, or
+returning a custom child logger implementation.
+
+Child logger bindings have a performance advantage over per-log bindings because
+they are pre-serialized by Pino when the child logger is created.
+
+The first parameter is the parent logger instance, followed by the default bindings
+and logger options which should be passed to the child logger, and finally
+the raw request (not a Fastify request object). The function is bound with `this`
+being the Fastify instance.
+
+For example:
+```js
+const fastify = require('fastify')({
+ childLoggerFactory: function (logger, bindings, opts, rawReq) {
+ // Calculate additional bindings from the request if needed
+ bindings.traceContext = rawReq.headers['x-cloud-trace-context']
+ return logger.child(bindings, opts)
+ }
+})
+```
+
+The handler is bound to the Fastify instance and is fully encapsulated, so
+different plugins can set different logger factories.
+
+#### setgenreqid
+
+
+`fastify.setGenReqId(function (rawReq))` Synchronous function for setting the request-id
+for additional Fastify instances. It will receive the _raw_ incoming request as
+a parameter. The provided function should not throw an Error in any case.
+
+Especially in distributed systems, you may want to override the default ID
+generation behavior to handle custom ways of generating different IDs in
+order to handle different use cases. Such as observability or webhooks plugins.
+
+For example:
+```js
+const fastify = require('fastify')({
+ genReqId: (req) => {
+ return 'base'
+ }
+})
+
+fastify.register((instance, opts, done) => {
+ instance.setGenReqId((req) => {
+ // custom request ID for `/webhooks`
+ return 'webhooks-id'
+ })
+ done()
+}, { prefix: '/webhooks' })
+
+fastify.register((instance, opts, done) => {
+ instance.setGenReqId((req) => {
+ // custom request ID for `/observability`
+ return 'observability-id'
+ })
+ done()
+}, { prefix: '/observability' })
+```
+
+The handler is bound to the Fastify instance and is fully encapsulated, so
+different plugins can set a different request ID.
+
#### addConstraintStrategy
@@ -1470,35 +2026,61 @@ a custom constraint strategy with the same name.
#### printRoutes
-`fastify.printRoutes()`: Prints the representation of the internal radix tree
-used by the router, useful for debugging. Alternatively, `fastify.printRoutes({
-commonPrefix: false })` can be used to print the flattened routes tree.
+`fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP
+method. If you call the prettyPrint without specifying an HTTP method, it will
+merge all the trees into one and print it. The merged tree doesn't represent the
+internal router structure. **Do not use it for debugging.**
*Remember to call it inside or after a `ready` call.*
```js
fastify.get('/test', () => {})
fastify.get('/test/hello', () => {})
-fastify.get('/hello/world', () => {})
-fastify.get('/helicopter', () => {})
+fastify.get('/testing', () => {})
+fastify.get('/testing/:param', () => {})
+fastify.put('/update', () => {})
fastify.ready(() => {
console.log(fastify.printRoutes())
// └── /
// ├── test (GET)
- // │ └── /hello (GET)
- // └── hel
- // ├── lo/world (GET)
- // └── licopter (GET)
+ // │ ├── /hello (GET)
+ // │ └── ing (GET)
+ // │ └── /
+ // │ └── :param (GET)
+ // └── update (PUT)
+})
+```
- console.log(fastify.printRoutes({ commonPrefix: false }))
- // └── / (-)
- // ├── test (GET)
- // │ └── /hello (GET)
- // ├── hello/world (GET)
- // └── helicopter (GET)
+If you want to print the internal router tree, you should specify the `method`
+param. Printed tree will represent the internal router structure.
+**You can use it for debugging.**
-})
+```js
+ console.log(fastify.printRoutes({ method: 'GET' }))
+ // └── /
+ // └── test (GET)
+ // ├── /hello (GET)
+ // └── ing (GET)
+ // └── /
+ // └── :param (GET)
+
+ console.log(fastify.printRoutes({ method: 'PUT' }))
+ // └── /
+ // └── update (PUT)
+```
+
+`fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This
+may be useful when you have a large number of routes with common prefixes.
+It doesn't represent the internal router structure. **Do not use it for debugging.**
+
+```js
+ console.log(fastify.printRoutes({ commonPrefix: false }))
+ // ├── /test (GET)
+ // │ ├── /hello (GET)
+ // │ └── ing (GET)
+ // │ └── /:param (GET)
+ // └── /update (PUT)
```
`fastify.printRoutes({ includeMeta: (true | []) })` will display properties from
@@ -1508,26 +2090,51 @@ A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include
all [hooks](./Hooks.md).
```js
- console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] }))
+ fastify.get('/test', () => {})
+ fastify.get('/test/hello', () => {})
+
+ const onTimeout = () => {}
+
+ fastify.addHook('onRequest', () => {})
+ fastify.addHook('onTimeout', onTimeout)
+
+ console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] }))
// └── /
- // ├── test (GET)
- // │ • (onRequest) ["anonymous()","namedFunction()"]
- // │ • (metaProperty) "value"
- // │ └── /hello (GET)
- // └── hel
- // ├── lo/world (GET)
- // │ • (onTimeout) ["anonymous()"]
- // └── licopter (GET)
+ // └── test (GET)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (errorHandler) "defaultErrorHandler()"
+ // test (HEAD)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (onSend) ["headRouteOnSendHandler()"]
+ // • (errorHandler) "defaultErrorHandler()"
+ // └── /hello (GET)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (errorHandler) "defaultErrorHandler()"
+ // /hello (HEAD)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (onSend) ["headRouteOnSendHandler()"]
+ // • (errorHandler) "defaultErrorHandler()"
console.log(fastify.printRoutes({ includeHooks: true }))
// └── /
- // ├── test (GET)
- // │ • (onRequest) ["anonymous()","namedFunction()"]
- // │ └── /hello (GET)
- // └── hel
- // ├── lo/world (GET)
- // │ • (onTimeout) ["anonymous()"]
- // └── licopter (GET)
+ // └── test (GET)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // test (HEAD)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (onSend) ["headRouteOnSendHandler()"]
+ // └── /hello (GET)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // /hello (HEAD)
+ // • (onTimeout) ["onTimeout()"]
+ // • (onRequest) ["anonymous()"]
+ // • (onSend) ["headRouteOnSendHandler()"]
```
#### printPlugins
@@ -1558,7 +2165,7 @@ fastify.ready(() => {
`fastify.addContentTypeParser(content-type, options, parser)` is used to pass
-custom parser for a given content type. Useful for adding parsers for custom
+a custom parser for a given content type. Useful for adding parsers for custom
content types, e.g. `text/json, application/vnd.oasis.opendocument.text`.
`content-type` can be a string, string array or RegExp.
@@ -1625,7 +2232,7 @@ information.
`fastify.defaultTextParser()` can be used to parse content as plain text.
```js
-fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser())
+fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser)
```
#### errorHandler
@@ -1647,33 +2254,76 @@ fastify.get('/', {
}, handler)
```
+#### childLoggerFactory
+
+
+`fastify.childLoggerFactory` returns the custom logger factory function for the
+Fastify instance. See the [`childLoggerFactory` config option](#setchildloggerfactory)
+for more info.
+
+#### Symbol.asyncDispose
+
+
+`fastify[Symbol.asyncDispose]` is a symbol that can be used to define an
+asynchronous function that will be called when the Fastify instance is closed.
+
+It's commonly used alongside the `using` TypeScript keyword to ensure that
+resources are cleaned up when the Fastify instance is closed.
+
+This combines perfectly inside short lived processes or unit tests, where you must
+close all Fastify resources after returning from inside the function.
+
+```ts
+test('Uses app and closes it afterwards', async () => {
+ await using app = fastify();
+ // do something with app.
+})
+```
+
+In the above example, Fastify is closed automatically after the test finishes.
+
+Read more about the
+[ECMAScript Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management)
+and the [using keyword](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/)
+introduced in TypeScript 5.2.
+
#### initialConfig
`fastify.initialConfig`: Exposes a frozen read-only object registering the
initial options passed down by the user to the Fastify instance.
-Currently the properties that can be exposed are:
+The properties that can currently be exposed are:
- connectionTimeout
- keepAliveTimeout
+- handlerTimeout
- bodyLimit
- caseSensitive
-- allowUnsafeRegex
- http2
- https (it will return `false`/`true` or `{ allowHTTP1: true/false }` if
explicitly passed)
-- ignoreTrailingSlash
- disableRequestLogging
-- maxParamLength
- onProtoPoisoning
- onConstructorPoisoning
- pluginTimeout
- requestIdHeader
- requestIdLogLabel
- http2SessionTimeout
+- routerOptions
+ - allowUnsafeRegex
+ - buildPrettyMeta
+ - caseSensitive
+ - constraints
+ - defaultRoute
+ - ignoreDuplicateSlashes
+ - ignoreTrailingSlash
+ - maxParamLength
+ - onBadUrl
+ - querystringParser
+ - useSemicolonDelimiter
```js
-const { readFileSync } = require('fs')
+const { readFileSync } = require('node:fs')
const Fastify = require('fastify')
const fastify = Fastify({
@@ -1683,9 +2333,11 @@ const fastify = Fastify({
cert: readFileSync('./fastify.cert')
},
logger: { level: 'trace'},
- ignoreTrailingSlash: true,
- maxParamLength: 200,
- caseSensitive: true,
+ routerOptions: {
+ ignoreTrailingSlash: true,
+ maxParamLength: 200,
+ caseSensitive: true,
+ },
trustProxy: '127.0.0.1,192.168.1.1/24',
})
@@ -1693,10 +2345,12 @@ console.log(fastify.initialConfig)
/*
will log :
{
- caseSensitive: true,
https: { allowHTTP1: true },
- ignoreTrailingSlash: true,
- maxParamLength: 200
+ routerOptions: {
+ caseSensitive: true,
+ ignoreTrailingSlash: true,
+ maxParamLength: 200
+ }
}
*/
@@ -1706,10 +2360,12 @@ fastify.register(async (instance, opts) => {
/*
will return :
{
- caseSensitive: true,
https: { allowHTTP1: true },
- ignoreTrailingSlash: true,
- maxParamLength: 200
+ routerOptions: {
+ caseSensitive: true,
+ ignoreTrailingSlash: true,
+ maxParamLength: 200
+ }
}
*/
})
diff --git a/docs/Reference/Type-Providers.md b/docs/Reference/Type-Providers.md
index 3ec4215686d..566442bbe9b 100644
--- a/docs/Reference/Type-Providers.md
+++ b/docs/Reference/Type-Providers.md
@@ -2,94 +2,100 @@
## 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. Type
-Provider packages follow a `@fastify/type-provider-{provider-name}` naming
-convention.
+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:
-- `json-schema-to-ts` -
- [github](https://github.com/ThomasAribart/json-schema-to-ts)
-- `typebox` - [github](https://github.com/sinclairzx81/typebox)
+- [`json-schema-to-ts`](https://github.com/ThomasAribart/json-schema-to-ts)
+- [`typebox`](https://github.com/sinclairzx81/typebox)
+- [`zod`](https://github.com/colinhacks/zod)
+
+See also the Type Provider wrapper packages for each of the packages respectively:
+
+- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
+- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
+- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
+ (3rd party)
### 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()
server.get('/route', {
- schema: {
- querystring: {
- type: 'object',
- properties: {
- foo: { type: 'number' },
- bar: { type: 'string' },
- },
- required: ['foo', 'bar']
- }
- } as const // don't forget to use const !
-
+ 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
+$ npm i typebox @fastify/type-provider-typebox
```
```typescript
-import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
-import { Type } from '@sinclair/typebox'
-
import fastify from 'fastify'
+import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
+import { Type } from 'typebox'
const server = fastify().withTypeProvider()
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.
+
### Scoped Type-Provider
@@ -103,7 +109,7 @@ Example:
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
-import { Type } from '@sinclair/typebox'
+import { Type } from 'typebox'
const fastify = Fastify()
@@ -134,7 +140,7 @@ function pluginWithJsonSchema(fastify: FastifyInstance, _opts, done): void {
y: { type: 'number' },
z: { type: 'boolean' }
},
- } as const
+ }
}
}, (req) => {
const { x, y, z } = req.body // type safe
@@ -146,23 +152,16 @@ 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 bellow:
+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'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
-import { Type } from '@sinclair/typebox'
+import { Type } from 'typebox'
-const server = Fastify({
- ajv: {
- customOptions: {
- strict: 'log',
- keywords: ['kind', 'modifier'],
- },
- },
-}).withTypeProvider()
+const server = Fastify().withTypeProvider()
server.register(plugin1) // wrong
server.register(plugin2) // correct
@@ -177,7 +176,7 @@ function plugin1(fastify: FastifyInstance, _opts, done): void {
})
}
}, (req) => {
- // it doesn't works! 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()
@@ -204,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
@@ -213,14 +212,7 @@ import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { registerRoutes } from './routes'
-const server = Fastify({
- ajv: {
- customOptions: {
- strict: 'log',
- keywords: ['kind', 'modifier'],
- },
- },
-}).withTypeProvider()
+const server = Fastify().withTypeProvider()
registerRoutes(server)
@@ -229,10 +221,10 @@ server.listen({ port: 3000 })
```ts
// routes.ts
-import { Type } from '@sinclair/typebox'
+import { Type } from 'typebox'
import {
FastifyInstance,
- FastifyLoggerInstance,
+ FastifyBaseLogger,
RawReplyDefaultExpression,
RawRequestDefaultExpression,
RawServerDefault
@@ -243,7 +235,7 @@ type FastifyTypebox = FastifyInstance<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
- FastifyLoggerInstance,
+ FastifyBaseLogger,
TypeBoxTypeProvider
>;
diff --git a/docs/Reference/TypeScript.md b/docs/Reference/TypeScript.md
index 9143de1c6eb..56e0e39c491 100644
--- a/docs/Reference/TypeScript.md
+++ b/docs/Reference/TypeScript.md
@@ -34,7 +34,7 @@ examples there is further, more detailed documentation for the type system.
This example will get you up and running with Fastify and TypeScript. It results
in a blank http Fastify server.
-1. Create a new npm project, install Fastify, and install typescript & node.js
+1. Create a new npm project, install Fastify, and install typescript & Node.js
types as peer dependencies:
```bash
npm init -y
@@ -58,8 +58,9 @@ in a blank http Fastify server.
or use one of the [recommended
ones](https://github.com/tsconfig/bases#node-14-tsconfigjson).
-*Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid
-[FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.*
+> ℹ️ Note:
+> Set `target` property in `tsconfig.json` to `es2017` or greater to avoid
+> [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.
4. Create an `index.ts` file - this will contain the server code
5. Add the following code block to your file:
@@ -106,7 +107,7 @@ generic types for route schemas and the dynamic properties located on the
route-level `request` object.
1. If you did not complete the previous example, follow steps 1-4 to get set up.
-2. Inside `index.ts`, define two interfaces `IQuerystring` and `IHeaders`:
+2. Inside `index.ts`, define three interfaces `IQuerystring`,`IHeaders` and `IReply`:
```typescript
interface IQuerystring {
username: string;
@@ -116,8 +117,14 @@ route-level `request` object.
interface IHeaders {
'h-Custom': string;
}
+
+ interface IReply {
+ 200: { success: boolean };
+ 302: { url: string };
+ '4xx': { error: string };
+ }
```
-3. Using the two interfaces, define a new API route and pass them as generics.
+3. Using the three interfaces, define a new API route and pass them as generics.
The shorthand route methods (i.e. `.get`) accept a generic object
`RouteGenericInterface` containing five named properties: `Body`,
`Querystring`, `Params`, `Headers` and `Reply`. The interfaces `Body`,
@@ -127,18 +134,26 @@ route-level `request` object.
```typescript
server.get<{
Querystring: IQuerystring,
- Headers: IHeaders
+ Headers: IHeaders,
+ Reply: IReply
}>('/auth', async (request, reply) => {
const { username, password } = request.query
const customerHeader = request.headers['h-Custom']
// do something with request data
- return `logged in!`
+ // chaining .statusCode/.code calls with .send allows type narrowing. For example:
+ // this works
+ reply.code(200).send({ success: true });
+ // but this gives a type error
+ reply.code(200).send('uh-oh');
+ // it even works for wildcards
+ reply.code(404).send({ error: 'Not found' });
+ return { success: true }
})
```
4. Build and run the server code with `npm run build` and `npm run start`
-5. Query the api
+5. Query the API
```bash
curl localhost:8080/auth?username=admin&password=Password123!
```
@@ -149,7 +164,8 @@ route-level `request` object.
```typescript
server.get<{
Querystring: IQuerystring,
- Headers: IHeaders
+ Headers: IHeaders,
+ Reply: IReply
}>('/auth', {
preValidation: (request, reply, done) => {
const { username, password } = request.query
@@ -158,7 +174,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
@@ -181,43 +197,44 @@ Serialization](./Validation-and-Serialization.md) documentation for more info.
Also it has the advantage to use the defined type within your handlers
(including pre-validation, etc.).
-Here are some options how to achieve this.
+Here are some options on how to achieve this.
#### Fastify Type Providers
Fastify offers two packages wrapping `json-schema-to-ts` and `typebox`:
-- `@fastify/type-provider-json-schema-to-ts`
-- `@fastify/type-provider-typebox`
+- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
+- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
+
+And a `zod` wrapper by a third party called [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
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
+#### 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/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 typebox
```
2. Define the schema you need with `Type` and create the respective type with
`Static`.
```typescript
- import { Static, Type } from '@sinclair/typebox'
+ import { Static, Type } from 'typebox'
export const User = Type.Object({
name: Type.String(),
@@ -231,12 +248,11 @@ can do it as follows:
```typescript
import Fastify from 'fastify'
- import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
// ...
- const fastify = Fastify().withTypeProvider()
+ const fastify = Fastify()
- app.post<{ Body: UserType, Reply: UserType }>(
+ fastify.post<{ Body: UserType, Reply: UserType }>(
'/',
{
schema: {
@@ -254,25 +270,12 @@ can do it as follows:
)
```
- **Note** For Ajv version 7 and above is required to use the `ajvTypeBoxPlugin`:
-
- ```typescript
- import Fastify from 'fastify'
- import { ajvTypeBoxPlugin, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
-
- const fastify = Fastify({
- ajv: {
- plugins: [ajvTypeBoxPlugin]
- }
- }).withTypeProvider()
- ```
-
-#### 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.
@@ -500,7 +503,7 @@ Fastify Plugin in a TypeScript Project.
`"compilerOptions"` object.
```json
{
- "compileOptions": {
+ "compilerOptions": {
"declaration": true
}
}
@@ -592,13 +595,13 @@ 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
})
```
5. Open `index.d.ts` and add the following code:
```typescript
- import { FastifyPlugin } from 'fastify'
+ import { FastifyPluginCallback } from 'fastify'
interface PluginOptions {
//...
@@ -619,7 +622,7 @@ your plugin.
// fastify-plugin automatically adds named export, so be sure to add also this type
// the variable name is derived from `options.name` property if `module.exports.myPlugin` is missing
- export const myPlugin: FastifyPlugin
+ export const myPlugin: FastifyPluginCallback
// fastify-plugin automatically adds `.default` property to the exported plugin. See the note below
export default myPlugin
@@ -630,7 +633,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!
@@ -685,6 +688,143 @@ Or even explicit config on tsconfig
}
```
+#### `getDecorator`
+
+Fastify's `getDecorator` method retrieves decorators with enhanced type safety.
+
+The `getDecorator` method supports generic type parameters for enhanced type
+safety:
+
+```typescript
+// Type-safe decorator retrieval
+const usersRepository = fastify.getDecorator('usersRepository')
+const session = request.getDecorator('session')
+const sendSuccess = reply.getDecorator('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` allows limiting types scope:
+
+```typescript
+serverOne.register(async function (fastify) {
+ const usersRepository = fastify.getDecorator(
+ '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('session')
+ reply.send(session)
+ })
+})
+
+serverTwo.register(async function (fastify) {
+ const usersRepository = fastify.getDecorator(
+ 'usersRepository'
+ )
+
+ fastify.decorateReply('sendSuccess', function (data) {
+ return this.send({ success: true })
+ })
+
+ fastify.get('/success', async (request, reply) => {
+ const sendSuccess = reply.getDecorator('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
+
+fastify.decorateReply('sendSuccess', sendSuccess)
+fastify.get('/success', async (request, reply) => {
+ const sendSuccess = reply.getDecorator('sendSuccess')
+ await sendSuccess()
+})
+```
+
+#### `setDecorator`
+
+Fastify's `setDecorator` method provides enhanced type safety for updating request
+decorators.
+
+The `setDecorator` 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('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` method eliminates the need for explicit type assertions
+while providing type safety:
+
+```typescript
+fastify.addHook('preHandler', async (req, reply) => {
+ req.setDecorator('user', 'Bob Dylan')
+})
+```
+
## Code Completion In Vanilla JavaScript
Vanilla JavaScript can use the published types to provide code completion (e.g.
@@ -713,8 +853,8 @@ constraint value(s). Read these articles for more information on TypeScript
generics.
- [Generic Parameter
Default](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults)
-- [Generic
- Constraints](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints)
+- [Generic Constraints](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints)
+
#### How to import
@@ -780,7 +920,7 @@ There are a couple supported import methods with the Fastify type system.
Many type definitions share the same generic parameters; they are all
documented, in detail, within this section.
-Most definitions depend on `@node/types` modules `http`, `https`, and `http2`
+Most definitions depend on `@types/node` modules `http`, `https`, and `http2`
##### RawServer
Underlying Node.js server type
@@ -827,7 +967,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
@@ -850,17 +990,22 @@ const server = fastify()
Check out the Learn By Example - [Getting Started](#getting-started) example for
a more detailed http server walkthrough.
-###### Example 2: HTTPS sever
+###### Example 2: HTTPS server
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. Follow the steps in this official [Node.js https server
- guide](https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/)
- to create the `key.pem` and `cert.pem` files
+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
+openssl req -new -key key.pem -out csr.pem
+openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
+rm csr.pem
+```
3. Instantiate a Fastify https server and add a route:
```typescript
const server = fastify({
@@ -911,7 +1056,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
@@ -963,7 +1108,7 @@ Union type of: `'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' |
##### fastify.RawServerBase
[src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L13)
-Dependant on `@types/node` modules `http`, `https`, `http2`
+Dependent on `@types/node` modules `http`, `https`, `http2`
Union type of: `http.Server | https.Server | http2.Http2Server |
http2.Http2SecureServer`
@@ -971,13 +1116,13 @@ http2.Http2SecureServer`
##### fastify.RawServerDefault
[src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L18)
-Dependant on `@types/node` modules `http`
+Dependent on `@types/node` modules `http`
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)
@@ -988,7 +1133,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)
@@ -1011,7 +1156,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
@@ -1085,12 +1230,12 @@ server.get('/', async (request, reply) => {
```
If you want to see a detailed example of using this interface check out the
-Learn by Example section: [JSON Schema](#jsonschema).
+Learn by Example section: [JSON Schema](#json-schema).
##### fastify.RawRequestDefaultExpression\<[RawServer][RawServerGeneric]\>
[src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L23)
-Dependant on `@types/node` modules `http`, `https`, `http2`
+Dependent on `@types/node` modules `http`, `https`, `http2`
Generic parameter `RawServer` defaults to [`RawServerDefault`][RawServerDefault]
@@ -1099,8 +1244,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.IncomingMessage
@@ -1111,7 +1256,7 @@ RawRequestDefaultExpression // -> 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
@@ -1147,10 +1292,10 @@ declare module 'fastify' {
}
```
-##### fastify.RawReplyDefaultExpression<[RawServer][RawServerGeneric]>
+##### fastify.RawReplyDefaultExpression< [RawServer][RawServerGeneric]>
[src](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L27)
-Dependant on `@types/node` modules `http`, `https`, `http2`
+Dependent on `@types/node` modules `http`, `https`, `http2`
Generic parameter `RawServer` defaults to [`RawServerDefault`][RawServerDefault]
@@ -1159,8 +1304,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.ServerResponse
@@ -1179,19 +1324,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
@@ -1225,20 +1370,21 @@ a function signature with an underlying generic `Options` which is defaulted to
FastifyPlugin parameter when calling this function so there is no need to
specify the underlying generic. The options parameter is the intersection of the
plugin's options and two additional optional properties: `prefix: string` and
-`logLevel`: [LogLevel][LogLevel].
+`logLevel`: [LogLevel][LogLevel]. `FastifyPlugin` is deprecated use
+`FastifyPluginCallback` and `FastifyPluginAsync` instead.
Below is an example of the options inference in action:
```typescript
const server = fastify()
-const plugin: FastifyPlugin<{
+const plugin: FastifyPluginCallback<{
option1: string;
option2: boolean;
}> = function (instance, opts, done) { }
-fastify().register(plugin, {}) // Error - options object is missing required properties
-fastify().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties
+server().register(plugin, {}) // Error - options object is missing required properties
+server().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties
```
See the Learn By Example, [Plugins](#plugins) section for more detailed examples
@@ -1259,7 +1405,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)
@@ -1288,9 +1434,22 @@ Union type of: `'info' | 'error' | 'debug' | 'fatal' | 'warn' | 'trace'`
The context type definition is similar to the other highly dynamic pieces of the
type system. Route context is available in the route handler method.
-##### fastify.FastifyContext
+##### fastify.FastifyRequestContext
+
+[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L11)
+
+An interface with a single required property `config` that is set by default to
+`unknown`. Can be specified either using a generic or an overload.
+
+This type definition is potentially incomplete. If you are using it and can
+provide more details on how to improve the definition, we strongly encourage you
+to open an issue in the main
+[fastify/fastify](https://github.com/fastify/fastify) repository. Thank you in
+advanced!
+
+##### fastify.FastifyReplyContext
-[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L6)
+[src](https://github.com/fastify/fastify/blob/main/types/context.d.ts#L11)
An interface with a single required property `config` that is set by default to
`unknown`. Can be specified either using a generic or an overload.
@@ -1309,17 +1468,17 @@ 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)
A type declaration for the route handler methods. Has two arguments, `request`
-and `reply` which are typed by `FastifyRequest` and `FastifyReply` respectfully.
+and `reply` which are typed by `FastifyRequest` and `FastifyReply` respectively.
The generics parameters are passed through to these arguments. The method
returns either `void` or `Promise` for synchronous and asynchronous
-handlers respectfully.
+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)
@@ -1331,14 +1490,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)
@@ -1346,7 +1505,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)
@@ -1361,21 +1520,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)
@@ -1417,7 +1576,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\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L17)
@@ -1427,7 +1586,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\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L35)
@@ -1442,31 +1601,32 @@ 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\ | 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\ | 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\ | 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\ | 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(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: PreSerializationPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L94)
`preSerialization` is the fifth hook to be executed in the request lifecycle.
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.
+> ℹ️ Note:
+> The hook is NOT called if the payload is a string, a Buffer,
+> a stream, or null.
-##### fastify.onSendHookHandler(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L114)
@@ -1474,10 +1634,11 @@ You can change the payload with the `onSend` hook. It is the sixth hook to be
executed in the request lifecycle. The previous hook was `preSerialization`, the
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.
+> ℹ️ 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\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L134)
@@ -1488,7 +1649,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\ | 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\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L154)
@@ -1498,14 +1659,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\ | void
+##### fastify.onRouteHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(opts: [RouteOptions][RouteOptions] & \{ path: string; prefix: string }): Promise\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L174)
@@ -1513,7 +1672,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\ | void
+##### fastify.onRegisterHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L191)
@@ -1523,9 +1682,10 @@ created. The hook will be executed before the registered code.
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.
-Note: This hook will not be called if a plugin is wrapped inside fastify-plugin.
+> ℹ️ 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\ | void
+##### fastify.onCloseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L206)
diff --git a/docs/Reference/Validation-and-Serialization.md b/docs/Reference/Validation-and-Serialization.md
index 9732a85d648..399c7a202f6 100644
--- a/docs/Reference/Validation-and-Serialization.md
+++ b/docs/Reference/Validation-and-Serialization.md
@@ -1,72 +1,73 @@
Fastify
## 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.
-
-Validation will only be attempted if the content type is `application-json`, as
-described in the documentation for the [content type
-parser](./ContentTypeParser.md).
-
-All the examples in this section are using 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.
+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 is only attempted if the content type is `application/json`,
+unless the body schema uses the [`content`](#body-content-type-validation)
+property to specify validation per content type. When the body schema defines
+a `content` field, it must enumerate all possible content types the
+application expects to handle with the associated handler.
+
+All examples use the
+[JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7)
+specification.
+
+> ⚠ 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.
>
-> Moreover, the [`$async` Ajv
-> feature](https://ajv.js.org/guide/async-validation.html) 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
-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:**
```js
fastify.addSchema({
- $id: 'http://example.com/',
+ $id: 'http://fastify.example/',
type: 'object',
properties: {
hello: { type: 'string' }
@@ -78,7 +79,7 @@ fastify.post('/', {
schema: {
body: {
type: 'array',
- items: { $ref: 'http://example.com#/properties/hello' }
+ items: { $ref: 'http://fastify.example#/properties/hello' }
}
}
})
@@ -107,9 +108,9 @@ fastify.post('/', {
#### Retrieving the shared schemas
-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({
@@ -124,8 +125,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' })
@@ -149,25 +150,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!
-
-The supported validations are:
-- `body`: validates the body of the request if it is a POST, PUT, or PATCH
- method.
+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.
+
+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
@@ -234,9 +232,64 @@ const schema = {
fastify.post('/the/url', { schema }, 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.*
+#### Body Content-Type Validation
+
+
+For `body` schema, it is further possible to differentiate the schema per content
+type by nesting the schemas inside `content` property. The schema validation
+will be applied based on the `Content-Type` header in the request.
+
+```js
+fastify.post('/the/url', {
+ schema: {
+ body: {
+ content: {
+ 'application/json': {
+ schema: { type: 'object' }
+ },
+ 'text/plain': {
+ schema: { type: 'string' }
+ }
+ // Other content types will not be validated
+ }
+ }
+ }
+}, handler)
+```
+
+> **Important:** When using [custom content type
+> parsers](./ContentTypeParser.md), the parsed body will **only** be validated
+> if the request's content type is listed in the `content` object above. If
+> a parser for a content type (e.g., `application/yaml`) is defined,
+> but it is not not included in the body schema's `content` property,
+> the incoming data will be parsed but **not validated**.
+>
+> ```js
+> // Add a custom parser for YAML
+> fastify.addContentTypeParser('application/yaml', { parseAs: 'string' }, (req, body, done) => {
+> done(null, YAML.parse(body))
+> })
+>
+> fastify.post('/the/url', {
+> schema: {
+> body: {
+> content: {
+> 'application/json': {
+> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
+> },
+> // Without this entry, application/yaml requests will NOT be validated
+> 'application/yaml': {
+> schema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
+> }
+> }
+> }
+> }
+> }, handler)
+> ```
+
+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:
@@ -268,14 +321,14 @@ fastify.listen({ port: 3000 }, (err) => {
```sh
curl -X GET "http://localhost:3000/?ids=1
-{"params":{"hello":["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 = {
@@ -313,16 +366,40 @@ server.setValidatorCompiler(req => {
})
```
-For further information see [here](https://ajv.js.org/coercion.html)
+When type coercion is enabled, using `anyOf` with nullable primitive types
+can produce unexpected results. For example, a value of `0` or `false` may be
+coerced to `null` because Ajv evaluates `anyOf` schemas in order and applies
+type coercion during matching. This means the `{ "type": "null" }` branch can
+match before the intended type:
+
+```json
+{
+ "anyOf": [
+ { "type": "null" },
+ { "type": "number" }
+ ]
+}
+```
+
+To avoid this, use the `nullable` keyword instead of `anyOf` for primitive
+types:
+
+```json
+{
+ "type": "number",
+ "nullable": true
+}
+```
+
+For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html).
#### Ajv Plugins
-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')({
@@ -383,31 +460,32 @@ fastify.post('/foo', {
#### Validator Compiler
-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:
```js
{
- coerceTypes: true, // change data type of data to match type keyword
+ coerceTypes: 'array', // change data type of data to match type keyword
useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
- removeAdditional: true, // remove additional properties
+ removeAdditional: true, // remove additional properties if additionalProperties is set to false, see: https://ajv.js.org/guide/modifying-data.html#removing-additional-properties
+ uriResolver: require('fast-uri'),
+ addUsedSchema: false,
// Explicitly set allErrors to `false`.
// When set to `true`, a DoS attack is possible.
allErrors: false
}
```
-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')()
@@ -423,29 +501,41 @@ 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
-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)
```
@@ -484,10 +574,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) {
@@ -500,30 +624,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) => {
@@ -535,9 +656,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 {
@@ -556,12 +677,10 @@ const errorHandler = (error, request, reply) => {
### Serialization
-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
@@ -580,9 +699,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: {
@@ -611,17 +729,73 @@ const schema = {
fastify.post('/the/url', { schema }, handler)
```
+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: {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ image: { type: 'string' },
+ address: { type: 'string' }
+ }
+ }
+ },
+ 'application/vnd.v1+json': {
+ schema: {
+ type: 'array',
+ items: { $ref: 'test' }
+ }
+ }
+ }
+ },
+ '3xx': {
+ content: {
+ 'application/vnd.v2+json': {
+ schema: {
+ type: 'object',
+ properties: {
+ fullName: { type: 'string' },
+ phone: { type: 'string' }
+ }
+ }
+ }
+ }
+ },
+ default: {
+ content: {
+ // */* is match-all content-type
+ '*/*': {
+ schema: {
+ type: 'object',
+ properties: {
+ desc: { type: 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fastify.post('/url', { schema }, handler)
+```
#### Serializer Compiler
-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 }) => {
+fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
return data => JSON.stringify(data)
})
@@ -632,21 +806,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 = {
@@ -660,8 +837,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
{
@@ -671,10 +848,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()
@@ -689,13 +871,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({
@@ -713,8 +895,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) {
@@ -724,25 +906,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: [
@@ -786,8 +968,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')
@@ -821,8 +1003,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 |
|-----------------------------------|-----------|------------|
@@ -929,11 +1111,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)
diff --git a/docs/Reference/Warnings.md b/docs/Reference/Warnings.md
new file mode 100644
index 00000000000..d07b23d1705
--- /dev/null
+++ b/docs/Reference/Warnings.md
@@ -0,0 +1,58 @@
+
+Fastify
+
+**Table of contents**
+- [Warnings](#warnings)
+ - [Warnings In Fastify](#warnings-in-fastify)
+ - [Fastify Warning Codes](#fastify-warning-codes)
+ - [FSTWRN001](#FSTWRN001)
+ - [FSTWRN002](#FSTWRN002)
+ - [Fastify Deprecation Codes](#fastify-deprecation-codes)
+ - [FSTDEP022](#FSTDEP022)
+
+## Warnings
+
+### Warnings In Fastify
+
+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.
+
+Warnings can also be disabled, though it is not recommended. If necessary, use
+one of the following methods:
+
+- 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 disabling warnings, see [Node's documentation](https://nodejs.org/api/cli.html).
+
+Disabling warnings may cause issues when upgrading Fastify versions. Only
+experienced users should consider disabling warnings.
+
+### Fastify Warning Codes
+
+| Code | Description | How to solve | Discussion |
+| ---- | ----------- | ------------ | ---------- |
+| FSTWRN001 | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) |
+| FSTWRN002 | The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`. | Do not mix async and callback style. | [#5139](https://github.com/fastify/fastify/pull/5139) |
+
+
+### Fastify Deprecation Codes
+
+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)
+- [--trace-deprecation](https://nodejs.org/api/cli.html#--trace-deprecation)
+
+
+| Code | Description | How to solve | Discussion |
+| ---- | ----------- | ------------ | ---------- |
+| FSTDEP022 | You are trying to access the deprecated router options on top option properties. | Use `options.routerOptions`. | [#5985](https://github.com/fastify/fastify/pull/5985)
diff --git a/docs/index.md b/docs/index.md
index 0a89105a957..625d373b903 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,8 +7,8 @@ The documentation for Fastify is split into two categories:
The reference documentation utilizes a very formal style in an effort to document
Fastify's API and implementation details thoroughly for the developer who needs
-such. The guides category utilizes an informal, educational, style as a means to
-introduce newcomers to core, and advanced, Fastify concepts.
+such. The guides category utilizes an informal educational style as a means to
+introduce newcomers to core and advanced Fastify concepts.
## Where To Start
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000000..e498cc5af4c
--- /dev/null
+++ b/eslint.config.js
@@ -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'
+ }
+ }
+]
diff --git a/examples/asyncawait.js b/examples/asyncawait.js
index de5d57f7d9f..676285398b7 100644
--- a/examples/asyncawait.js
+++ b/examples/asyncawait.js
@@ -32,5 +32,7 @@ fastify
})
fastify.listen({ port: 3000 }, err => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/benchmark/body.json b/examples/benchmark/body.json
new file mode 100644
index 00000000000..eeedd400762
--- /dev/null
+++ b/examples/benchmark/body.json
@@ -0,0 +1,3 @@
+{
+ "hello": "world"
+}
\ No newline at end of file
diff --git a/examples/benchmark/parser.js b/examples/benchmark/parser.js
new file mode 100644
index 00000000000..665c8a832e6
--- /dev/null
+++ b/examples/benchmark/parser.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const fastify = require('../../fastify')({
+ logger: false
+})
+
+const jsonParser = require('fast-json-body')
+const querystring = require('node:querystring')
+
+// Handled by fastify
+// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/
+
+// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/jsoff' http://localhost:3000/
+fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
+ jsonParser(payload, function (err, body) {
+ done(err, body)
+ })
+})
+
+// curl -X POST -d 'hello=world' -H'Content-type: application/x-www-form-urlencoded' http://localhost:3000/
+fastify.addContentTypeParser('application/x-www-form-urlencoded', function (request, payload, done) {
+ let body = ''
+ payload.on('data', function (data) {
+ body += data
+ })
+ payload.on('end', function () {
+ try {
+ const parsed = querystring.parse(body)
+ done(null, parsed)
+ } catch (e) {
+ done(e)
+ }
+ })
+ payload.on('error', done)
+})
+
+// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/vnd.custom+json' http://localhost:3000/
+fastify.addContentTypeParser(/^application\/.+\+json$/, { parseAs: 'string' }, fastify.getDefaultJsonParser('error', 'ignore'))
+
+fastify
+ .post('/', function (req, reply) {
+ reply.send(req.body)
+ })
+
+fastify.listen({ port: 3000 }, (err, address) => {
+ if (err) throw err
+})
diff --git a/examples/benchmark/webstream.js b/examples/benchmark/webstream.js
new file mode 100644
index 00000000000..7ed5fcd166b
--- /dev/null
+++ b/examples/benchmark/webstream.js
@@ -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}`)
+})
diff --git a/examples/hooks.js b/examples/hooks.js
index e087b3a66c3..cc54229296a 100644
--- a/examples/hooks.js
+++ b/examples/hooks.js
@@ -68,6 +68,9 @@ fastify
.addHook('onRoute', function (routeOptions) {
console.log('onRoute')
})
+ .addHook('onListen', async function () {
+ console.log('onListen')
+ })
.addHook('onClose', function (instance, done) {
console.log('onClose')
done()
diff --git a/examples/http2.js b/examples/http2.js
index 26bbcfe2aa4..e1a1bd0d2f6 100644
--- a/examples/http2.js
+++ b/examples/http2.js
@@ -1,7 +1,7 @@
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
const fastify = require('../fastify')({
http2: true,
https: {
@@ -33,5 +33,7 @@ fastify
})
fastify.listen({ port: 3000 }, err => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/https.js b/examples/https.js
index 33266980adc..2255552d4cf 100644
--- a/examples/https.js
+++ b/examples/https.js
@@ -1,7 +1,7 @@
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
const fastify = require('../fastify')({
https: {
key: fs.readFileSync(path.join(__dirname, '../test/https/fastify.key')),
@@ -32,5 +32,7 @@ fastify
})
fastify.listen({ port: 3000 }, err => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/parser.js b/examples/parser.js
index 2f8d25f9499..2dd50b07c65 100644
--- a/examples/parser.js
+++ b/examples/parser.js
@@ -2,7 +2,7 @@
const fastify = require('../fastify')({ logger: true })
const jsonParser = require('fast-json-body')
-const querystring = require('querystring')
+const querystring = require('node:querystring')
// Handled by fastify
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/
@@ -47,5 +47,7 @@ fastify
})
fastify.listen({ port: 3000 }, err => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/shared-schema.js b/examples/shared-schema.js
index c50b7908099..82adb889ead 100644
--- a/examples/shared-schema.js
+++ b/examples/shared-schema.js
@@ -32,5 +32,7 @@ fastify
})
fastify.listen({ port: 3000 }, err => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/simple-stream.js b/examples/simple-stream.js
index 8a354ae0505..2b7fe851b07 100644
--- a/examples/simple-stream.js
+++ b/examples/simple-stream.js
@@ -4,7 +4,7 @@ const fastify = require('../fastify')({
logger: false
})
-const Readable = require('stream').Readable
+const Readable = require('node:stream').Readable
fastify
.get('/', function (req, reply) {
@@ -13,6 +13,8 @@ fastify
})
fastify.listen({ port: 3000 }, (err, address) => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
fastify.log.info(`server listening on ${address}`)
})
diff --git a/examples/simple.js b/examples/simple.js
index 3c7495a1dfd..f2aeb9b889f 100644
--- a/examples/simple.js
+++ b/examples/simple.js
@@ -26,5 +26,7 @@ fastify
})
fastify.listen({ port: 3000 }, (err, address) => {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts
index 71f7ee0ba62..d1e24247d31 100644
--- a/examples/typescript-server.ts
+++ b/examples/typescript-server.ts
@@ -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,26 +53,27 @@ const opts: RouteShorthandOptions = {
}
}
}
-};
+}
// Add our route handler with correct types
-server.get<{
+server.post<{
Querystring: PingQuerystring;
Params: PingParams;
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}`)
+})
diff --git a/examples/use-plugin.js b/examples/use-plugin.js
index fdcf26213c0..8eedefc4f4b 100644
--- a/examples/use-plugin.js
+++ b/examples/use-plugin.js
@@ -1,3 +1,5 @@
+'use strict'
+
const fastify = require('../fastify')({ logger: true })
const opts = {
@@ -15,7 +17,9 @@ const opts = {
}
}
fastify.register(require('./plugin'), opts, function (err) {
- if (err) throw err
+ if (err) {
+ throw err
+ }
})
fastify.listen({ port: 3000 }, function (err) {
diff --git a/fastify.d.ts b/fastify.d.ts
index 6c343fa4caa..6e74460a50f 100644
--- a/fastify.d.ts
+++ b/fastify.d.ts
@@ -1,25 +1,212 @@
-import * as http from 'http'
-import * as http2 from 'http2'
-import * as https from 'https'
-import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
+import * as http from 'node:http'
+import * as http2 from 'node:http2'
+import * as https from 'node:https'
+import { Socket } from 'node:net'
-import { FastifyRequest, RequestGenericInterface } from './types/request'
-import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression } from './types/utils'
-import { FastifyBaseLogger, FastifyLoggerInstance, FastifyLoggerOptions, PinoLoggerOptions } from './types/logger'
-import { FastifyInstance } from './types/instance'
-import { FastifyServerFactory } from './types/serverFactory'
-import { Options as AjvOptions } from '@fastify/ajv-compiler'
-import { Options as FJSOptions } from '@fastify/fast-json-stringify-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 { 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 { 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, 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 { FastifySchemaValidationError } from './types/schema'
-import { ConstructorAction, ProtoAction } from "./types/content-type-parser";
-import { Socket } from 'net'
-import { ValidatorCompiler } from '@fastify/ajv-compiler'
-import { SerializerCompiler } from '@fastify/fast-json-stringify-compiler'
-import { FastifySchema } from './types/schema'
-import { FastifyContextConfig } from './types/context'
-import { FastifyTypeProvider, FastifyTypeProviderDefault } from './types/type-provider'
+import { FastifyRequest, RequestGenericInterface } from './types/request'
+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 {
+ validationContext?: SchemaErrorDataVar;
+ validation?: FastifySchemaValidationError[];
+ }
+}
+
+type Fastify = typeof fastify
+
+declare namespace fastify {
+ export const errorCodes: FastifyErrorCodes
+
+ export type FastifyHttp2SecureOptions<
+ Server extends http2.Http2SecureServer,
+ Logger extends FastifyBaseLogger = FastifyBaseLogger
+ > = FastifyServerOptions & {
+ http2: true,
+ https: http2.SecureServerOptions,
+ http2SessionTimeout?: number
+ }
+
+ export type FastifyHttp2Options<
+ Server extends http2.Http2Server,
+ Logger extends FastifyBaseLogger = FastifyBaseLogger
+ > = FastifyServerOptions & {
+ http2: true,
+ http2SessionTimeout?: number
+ }
+
+ export type FastifyHttpsOptions<
+ Server extends https.Server,
+ Logger extends FastifyBaseLogger = FastifyBaseLogger
+ > = FastifyServerOptions & {
+ https: https.ServerOptions | null
+ }
+
+ export type FastifyHttpOptions<
+ Server extends http.Server,
+ Logger extends FastifyBaseLogger = FastifyBaseLogger
+ > = FastifyServerOptions & {
+ http?: http.ServerOptions | null
+ }
+
+ type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
+ type FindMyWayConfigForServer = FindMyWayConfig>
+
+ export interface ConnectionError extends Error {
+ code: string,
+ bytesParsed: number,
+ rawPacket: {
+ type: string,
+ data: number[]
+ }
+ }
+
+ type TrustProxyFunction = (address: string, hop: number) => boolean
+
+ export type FastifyRouterOptions = Omit, 'defaultRoute' | 'onBadUrl' | 'querystringParser'> & {
+ defaultRoute?: (
+ req: RawRequestDefaultExpression,
+ res: RawReplyDefaultExpression
+ ) => void,
+ onBadUrl?: (
+ path: string,
+ req: RawRequestDefaultExpression,
+ res: RawReplyDefaultExpression
+ ) => 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
+ */
+ export type FastifyServerOptions<
+ RawServer extends RawServerBase = RawServerDefault,
+ Logger extends FastifyBaseLogger = FastifyBaseLogger
+ > = {
+ ignoreTrailingSlash?: boolean,
+ ignoreDuplicateSlashes?: boolean,
+ connectionTimeout?: number,
+ keepAliveTimeout?: number,
+ maxRequestsPerSocket?: number,
+ forceCloseConnections?: boolean | 'idle',
+ requestTimeout?: number,
+ pluginTimeout?: number,
+ bodyLimit?: number,
+ handlerTimeout?: number,
+ maxParamLength?: number,
+ disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
+ exposeHeadRoutes?: boolean,
+ onProtoPoisoning?: ProtoAction,
+ onConstructorPoisoning?: ConstructorAction,
+ logger?: boolean | FastifyLoggerOptions & PinoLoggerOptions,
+ loggerInstance?: Logger
+ serializerOpts?: FJSOptions | Record,
+ serverFactory?: FastifyServerFactory,
+ caseSensitive?: boolean,
+ allowUnsafeRegex?: boolean,
+ requestIdHeader?: string | false,
+ requestIdLogLabel?: string;
+ useSemicolonDelimiter?: boolean,
+ genReqId?: (req: RawRequestDefaultExpression) => string,
+ trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
+ querystringParser?: (str: string) => { [key: string]: unknown },
+ constraints?: {
+ [name: string]: ConstraintStrategy, unknown>,
+ },
+ schemaController?: {
+ bucket?: (parentSchemas?: unknown) => {
+ add(schema: unknown): FastifyInstance;
+ getSchema(schemaId: string): unknown;
+ getSchemas(): Record;
+ };
+ compilersFactory?: {
+ buildValidator?: ValidatorFactory;
+ buildSerializer?: SerializerFactory;
+ };
+ };
+ return503OnClosing?: boolean,
+ ajv?: Parameters[1],
+ frameworkErrors?: (
+ error: FastifyError,
+ req: FastifyRequest, FastifySchema, TypeProvider>,
+ res: FastifyReply, RawReplyDefaultExpression, FastifyContextConfig, SchemaCompiler, TypeProvider>
+ ) => void,
+ rewriteUrl?: (
+ // The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters
+ // should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type
+ this: FastifyInstance, RawReplyDefaultExpression, Logger, FastifyTypeProviderDefault>,
+ req: RawRequestDefaultExpression
+ ) => string,
+ schemaErrorFormatter?: SchemaErrorFormatter,
+ /**
+ * listener to error events emitted by client connections
+ */
+ clientErrorHandler?: (error: ConnectionError, socket: Socket) => void,
+ childLoggerFactory?: FastifyChildLoggerFactory,
+ allowErrorHandlerOverride?: boolean
+ routerOptions?: FastifyRouterOptions,
+ }
+
+ /**
+ * @deprecated use {@link FastifySchemaValidationError}
+ */
+ export type ValidationResult = FastifySchemaValidationError
+
+ /* Export additional types */
+ export type {
+ LightMyRequestChain, InjectOptions, LightMyRequestResponse, LightMyRequestCallback, // 'light-my-request'
+ FastifyRequest, RequestGenericInterface, // './types/request'
+ FastifyReply, // './types/reply'
+ FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin, // './types/plugin'
+ FastifyListenOptions, FastifyInstance, PrintRoutesOptions, // './types/instance'
+ FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel, // './types/logger'
+ FastifyRequestContext, FastifyContextConfig, FastifyReplyContext, // './types/context'
+ RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface, // './types/route'
+ FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register'
+ FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser'
+ FastifyError, // '@fastify/error'
+ 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, SafePromiseLike, // './types/type-provider'
+ FastifyErrorCodes // './types/errors'
+ }
+ // named export
+ // import { plugin } from 'plugin'
+ // const { plugin } = require('plugin')
+ export const fastify: Fastify
+ // default export
+ // import plugin from 'plugin'
+ export { fastify as default }
+}
/**
* Fastify factory function for the standard fastify http, https, or http2 server instance.
@@ -33,177 +220,34 @@ declare function fastify<
Server extends http2.Http2SecureServer,
Request extends RawRequestDefaultExpression = RawRequestDefaultExpression,
Reply extends RawReplyDefaultExpression = RawReplyDefaultExpression,
- Logger extends FastifyBaseLogger = FastifyLoggerInstance,
- TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
->(opts: FastifyHttp2SecureOptions): FastifyInstance & PromiseLike>
+ Logger extends FastifyBaseLogger = FastifyBaseLogger,
+ TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
+> (opts: fastify.FastifyHttp2SecureOptions