New:Socket for Asana Is Now Available.Learn more
Get Started

fastify

Package Overview
Dependencies
Maintainers
6
Versions
325
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify - npm Package Compare versions

Comparing version
5.11.2
to
5.11.3
+31
-2
docs/Reference/Encapsulation.md

@@ -143,3 +143,3 @@ <h1 align="center">Fastify</h1>

const fastify = require('fastify')()
const fastifyPlugin = require('fastify-plugin')
const fp = require('fastify-plugin')

@@ -165,3 +165,3 @@ fastify.decorateRequest('answer', 42)

childServer.register(fastifyPlugin(grandchildContext))
childServer.register(fp(grandchildContext))

@@ -197,2 +197,31 @@ async function grandchildContext (grandchildServer) {

`fastify-plugin` breaks encapsulation only for the plugin it wraps. Plugins
registered inside it without `fastify-plugin` still create new encapsulated
contexts:
```js
'use strict'
const fastify = require('fastify')()
const fp = require('fastify-plugin')
fastify.register(fp(async function sharedContext (childServer) {
childServer.decorate('foo', 'foo')
childServer.register(async function encapsulatedContext (grandchildServer) {
grandchildServer.decorate('bar', 'bar')
})
}))
await fastify.ready()
console.log(fastify.foo) // 'foo'
console.log(fastify.bar) // undefined
```
The `foo` decorator is available in the root context because it is added
directly by the plugin wrapped with `fastify-plugin`. The nested `register`
call still creates a grandchild context, so the `bar` decorator remains
available only in that context and its children.
[fastify-plugin]: https://github.com/fastify/fastify-plugin

@@ -14,2 +14,3 @@ <h1 align="center">Fastify</h1>

- [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify)
- [What The Default Error Handler Sends](#what-the-default-error-handler-sends)
- [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler)

@@ -146,7 +147,79 @@ - [Fastify Error Codes](#fastify-error-codes)

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.
error handler, resulting in a `500 Internal Server Error` response.
To customize this behavior, use
[`setErrorHandler`](./Server.md#seterrorhandler).
#### What The Default Error Handler Sends
The default error handler serializes the error into a JSON body with the
`statusCode`, `error`, and `message` properties, plus `code` when the error
carries one:
```js
app.get('/', async () => { throw new Error('kaboom') })
```
```json
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "kaboom"
}
```
The `error` property is the generic HTTP status text, but **`message` is
`error.message` verbatim**. This applies to every status code, including `500`.
Fastify's built-in error serializer emits only these four properties, so the
stack trace is not part of the default payload — but a route-level response
schema replaces that serializer, and one that declares a `stack` property will
serialize it.
> Security:
> Because `message` and `code` are forwarded as-is, errors thrown by libraries
> deeper in your application are exposed to the client. A database driver
> error, for example, can leak schema details and query text:
>
> ```json
> {
> "statusCode": 500,
> "code": "ER_BAD_FIELD_ERROR",
> "error": "Internal Server Error",
> "message": "Unknown column 'username' in 'field list'"
> }
> ```
>
> Fastify does not distinguish between development and production here. If
> unexpected errors must not reach the client, handle this explicitly.
Register a [`setErrorHandler`](./Server.md#seterrorhandler) to replace the
message for errors you did not raise deliberately, while letting the ones you
did pass through:
```js
app.setErrorHandler(function (error, request, reply) {
// Errors with a statusCode below 500 were raised deliberately by this
// application, as were validation errors. A status code below 500 is not on
// its own a guarantee that the message is safe to expose — narrow this
// condition if any of yours are not.
if (error.validation || (error.statusCode && error.statusCode < 500)) {
return reply.send(error)
}
// Anything else is unexpected: log it, but do not describe it to the client.
this.log.error({ err: error }, 'unhandled error')
reply.status(500).send({
statusCode: 500,
error: 'Internal Server Error',
message: 'Internal Server Error'
})
})
```
The handler above is registered on the root instance, so it applies to every
route. Error handlers are encapsulated; for how they resolve across plugin
contexts, see
[the next section](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler).
Note that a route-level response schema is still applied to whatever the error
handler sends, and will reshape the payload accordingly — including properties
the built-in error serializer would have omitted, such as `stack`. See
[Serialization](./Validation-and-Serialization.md#serialization).
### Errors In Fastify Lifecycle Hooks And A Custom Error Handler

@@ -153,0 +226,0 @@

+18
-6

@@ -12,3 +12,7 @@ <h1 align="center">Fastify</h1>

other content types.
- `params` - The params matching the URL.
- `params` - The params matching the URL. Values are percent-decoded
(for example `%20` becomes a space, and `%2f` becomes `/`). Decoded
values may contain `.`, `..`, `/`, or other characters; treat them as
untrusted input. See the security note below and
[Routes - Url building](./Routes.md#url-building).
- [`headers`](#headers) - The headers getter and setter.

@@ -39,9 +43,17 @@ - `raw` - The incoming HTTP request from Node core.

> ⚠️ Security:
> `request.params`, `request.query`, `request.headers`, and `request.body`
> are untrusted network input. Route parameter values are percent-decoded
> before your handler runs, so a segment like `..%2ffile` becomes `../file`
> in `request.params`. Do not use parameter values as filesystem paths,
> template names, or redirect targets without validating or containing
> them. Prefer [`@fastify/static`](https://github.com/fastify/fastify-static)
> (or `reply.sendFile`) when serving files from a root directory.
>
> `request.ip`, `request.ips`, `request.host`, `request.hostname`,
> `request.port`, and `request.protocol` come from request metadata
> (socket and/or forwarding headers) and should be treated as untrusted input.
> Fastify does not perform security validation for business logic.
> If these values are used in security-sensitive decisions, they must
> be validated explicitly (for example: trusted proxy configuration,
> allow-lists, strict parsing, and normalization).
> (socket and/or forwarding headers) and should also be treated as
> untrusted input. Fastify does not perform security validation for
> business logic. If these values are used in security-sensitive decisions,
> they must be validated explicitly (for example: trusted proxy
> configuration, allow-lists, strict parsing, and normalization).

@@ -48,0 +60,0 @@ - `method` - The method of the incoming request.

@@ -331,2 +331,15 @@ <h1 align="center">Fastify</h1>

> ⚠️ Security:
> Fastify (via find-my-way) percent-decodes route parameters and wildcards
> before they reach your handler. Encoded separators in a segment are
> decoded in the parameter value: for a route `/download/:file`, a request
> to `/download/..%2fsecret.txt` yields
> `request.params.file === '../secret.txt'`. Parameters are untrusted
> input. Do not pass them to `path.join`, `fs` APIs, template engines, or
> redirects without validation or path containment. To serve files from a
> directory root, use
> [`@fastify/static`](https://github.com/fastify/fastify-static) instead of
> joining `request.params` into a filesystem path yourself. See also
> [Request](./Request.md).
To include a colon in a path without declaring a parameter, use a double colon.

@@ -333,0 +346,0 @@ For example:

'use strict'
const VERSION = '5.11.2'
const VERSION = '5.11.3'

@@ -5,0 +5,0 @@ const Avvio = require('avvio')

@@ -148,2 +148,6 @@ 'use strict'

const parserRegExp = this.parserRegExpList[j]
// A parser registered with a `g`/`y` flagged RegExp keeps a mutable
// lastIndex between calls, so a plain `.test()` would skip part of the
// next content type and match inconsistently. Reset it before testing.
parserRegExp.lastIndex = 0
if (parserRegExp.test(ct)) {

@@ -150,0 +154,0 @@ parser = this.customParsers.get(parserRegExp.toString())

@@ -124,3 +124,3 @@ 'use strict'

for (let i = 0; i !== deps.length; ++i) {
if (!checkExistence(instance, deps[i])) {
if (!checkExistence(instance, deps[i]) && !hasInstanceProperty(instance, deps[i])) {
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])

@@ -127,0 +127,0 @@ }

@@ -57,3 +57,3 @@ 'use strict'

*
* @param {Error | null} error Error that occurred during the response, if any.
* @param {Error | null | undefined} error Error that occurred during the response, if any.
* @param {object} request Fastify request object.

@@ -60,0 +60,0 @@ * @param {object} reply Fastify reply object.

@@ -598,5 +598,9 @@ 'use strict'

}
// it must be chunked for trailer to work
reply.header('Transfer-Encoding', 'chunked')
reply.header('Trailer', header.trim())
if (header !== '') {
// it must be chunked for trailer to work
reply.header('Transfer-Encoding', 'chunked')
reply.header('Trailer', header.trim())
} else {
reply[kReplyTrailers] = null
}
}

@@ -603,0 +607,0 @@

{
"name": "fastify",
"version": "5.11.2",
"version": "5.11.3",
"description": "Fast and low overhead web framework, for Node.js",

@@ -5,0 +5,0 @@ "main": "fastify.js",

@@ -76,3 +76,3 @@ <div align="center"> <a href="https://fastify.dev/">

Generate a fastify project with `npm init`:
Generate a Fastify project with `npm init`:

@@ -125,3 +125,3 @@ ```sh

})
// CommonJs
// CommonJS
const fastify = require('fastify')({

@@ -152,3 +152,3 @@ logger: true

})
// CommonJs
// CommonJS
const fastify = require('fastify')({

@@ -187,3 +187,3 @@ logger: true

- **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+
frameworks in town, depending on the code complexity we can serve more than 76
thousand requests per second.

@@ -196,3 +196,3 @@ - **Extensible:** Fastify is fully extensible via its hooks, plugins, and

function.
- **Logging:** logs are extremely important but are costly; we chose the best
- **Logging:** logs are extremely important, but are costly; we chose the best
logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)!

@@ -220,4 +220,4 @@ - **Developer friendly:** the framework is built to be very expressive and help

These benchmarks taken using https://github.com/fastify/benchmarks. This is a
synthetic "hello world" benchmark that aims to evaluate the framework overhead.
These benchmarks were 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

@@ -224,0 +224,0 @@ application. You should __always__ benchmark if performance matters to you.

@@ -581,2 +581,34 @@ 'use strict'

test('content-type match - RegExp with global flag', async t => {
t.plan(4)
const fastify = Fastify()
fastify.addContentTypeParser(/^application\/.+\+xml$/g, { parseAs: 'string' }, function (request, body, done) {
done(null, body)
})
fastify.post('/', async (request) => request.body)
// Two distinct content types that both match the parser. A RegExp with the
// `g` flag keeps a mutable lastIndex between test() calls, so the second
// content type must still match rather than fall through to 415.
const first = await fastify.inject({
method: 'POST',
path: '/',
headers: { 'content-type': 'application/vnd.a+xml' },
body: '<a/>'
})
const second = await fastify.inject({
method: 'POST',
path: '/',
headers: { 'content-type': 'application/vnd.b+xml' },
body: '<b/>'
})
t.assert.strictEqual(first.statusCode, 200)
t.assert.strictEqual(first.payload, '<a/>')
t.assert.strictEqual(second.statusCode, 200)
t.assert.strictEqual(second.payload, '<b/>')
})
test('content-type fail when parameters not match - string 1', async t => {

@@ -583,0 +615,0 @@ t.plan(1)

@@ -64,1 +64,50 @@ 'use strict'

})
test('decorateRequest accepts built-in request properties as dependencies', t => {
t.plan(6)
const fastify = Fastify()
for (const name of ['id', 'params', 'raw', 'query', 'log', 'body']) {
t.assert.doesNotThrow(() => fastify.decorateRequest(`uses_${name}`, null, [name]))
}
})
test('decorateReply accepts built-in reply properties as dependencies', t => {
t.plan(3)
const fastify = Fastify()
for (const name of ['raw', 'request', 'log']) {
t.assert.doesNotThrow(() => fastify.decorateReply(`uses_${name}`, null, [name]))
}
})
test('decorateRequest accepts built-in properties as dependencies inside a plugin', (t, done) => {
t.plan(2)
const fastify = Fastify()
fastify.register(async (instance) => {
instance.decorateRequest('scopedExtra', null, ['body'])
t.assert.equal(instance.hasRequestDecorator('scopedExtra'), true)
})
fastify.ready((err) => {
t.assert.ifError(err)
done()
})
})
test('decorateRequest accepts built-in properties mixed with user decorators as dependencies', t => {
t.plan(1)
const fastify = Fastify()
fastify.decorateRequest('userProp', null)
t.assert.doesNotThrow(() => fastify.decorateRequest('mixed', null, ['userProp', 'body']))
})
test('decorateRequest still throws FST_ERR_DEC_MISSING_DEPENDENCY for unknown dependencies', t => {
t.plan(2)
const fastify = Fastify()
t.assert.throws(
() => fastify.decorateRequest('withUnknown', null, ['nonExistent']),
(err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
)
t.assert.throws(
() => fastify.decorateReply('withUnknown', null, ['body']),
(err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
)
})

@@ -145,2 +145,33 @@ 'use strict'

test('remove trailer while stream is being consumed', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.get('/', function (request, reply) {
const stream = Readable.from((function * () {
reply.removeTrailer('ETag')
yield 'hello'
})())
reply.trailer('ETag', function () {
t.assert.fail('removed trailer should not be called')
})
reply.send(stream)
})
fastify.inject({
method: 'GET',
url: '/'
}, (error, res) => {
t.assert.ifError(error)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
t.assert.strictEqual(res.headers.trailer, 'etag')
t.assert.strictEqual(res.trailers.etag, undefined)
testDone()
})
})
test('send trailers when using async-await', (t, testDone) => {

@@ -399,6 +430,6 @@ t.plan(5)

t.assert.strictEqual(res.statusCode, 200)
t.assert.ok(!res.headers.trailer)
t.assert.ok(!res.trailers.etag)
t.assert.ok(!res.trailers['should-not-call'])
t.assert.ok(!res.headers['content-length'])
t.assert.strictEqual(res.headers.trailer, undefined)
t.assert.strictEqual(res.trailers.etag, undefined)
t.assert.strictEqual(res.trailers['should-not-call'], undefined)
t.assert.strictEqual(res.headers['content-length'], '0')
testDone()

@@ -408,2 +439,59 @@ })

test('remove some trailers should keep trailer mode for the remaining ones', (t, testDone) => {
t.plan(6)
const fastify = Fastify()
fastify.get('/', function (request, reply) {
reply.trailer('ETag', function () {
t.assert.fail('removed trailer should not be called')
})
reply.removeTrailer('ETag')
reply.trailer('Content-MD5', function (reply, payload, done) {
done(null, 'custom-md5')
})
reply.send('hello')
})
fastify.inject({
method: 'GET',
url: '/'
}, (error, res) => {
t.assert.ifError(error)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.headers.trailer, 'content-md5')
t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked')
t.assert.strictEqual(res.headers['content-length'], undefined)
t.assert.strictEqual(res.trailers['content-md5'], 'custom-md5')
testDone()
})
})
test('remove all trailers should behave like no trailers were registered', (t, testDone) => {
t.plan(6)
const fastify = Fastify()
fastify.get('/', function (request, reply) {
reply.trailer('ETag', function () {
t.assert.fail('removed trailer should not be called')
})
reply.removeTrailer('ETag')
reply.send('hello')
})
fastify.inject({
method: 'GET',
url: '/'
}, (error, res) => {
t.assert.ifError(error)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.headers.trailer, undefined)
t.assert.strictEqual(res.headers['transfer-encoding'], undefined)
t.assert.strictEqual(res.headers['content-length'], '5')
t.assert.strictEqual(res.trailers.etag, undefined)
testDone()
})
})
test('hasTrailer', (t, testDone) => {

@@ -410,0 +498,0 @@ t.plan(10)

@@ -127,3 +127,3 @@ import { FastifyError } from '@fastify/error'

requestCompleted (
error: Error | null,
error: Error | null | undefined,
request: FastifyRequest,

@@ -130,0 +130,0 @@ reply: FastifyReply,

{
"entries": [
{
"type": "exact",
"value": "git fetch --all --prune --quiet && echo FETCH_OK || echo FETCH_FAILED",
"addedAt": "2026-02-07T10:03:03.909Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "npm install --ignore-scripts",
"addedAt": "2026-02-07T10:21:13.104Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "npm run coverage:ci-check-coverage",
"addedAt": "2026-02-07T15:10:08.412Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "node --test test/web-api.test.js",
"addedAt": "2026-02-07T15:24:46.872Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "gpg --list-secret-keys --keyid-format=long || true && git config --get user.signingkey && git config --get commit.gpgsign",
"addedAt": "2026-02-11T04:42:44.716Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npm run test:typescript",
"addedAt": "2026-02-19T20:22:59.298Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts",
"addedAt": "2026-02-19T20:23:45.912Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && git status --short && git diff -- types/route.d.ts test/types/route.test-d.ts | sed -n '1,220p'",
"addedAt": "2026-02-19T20:25:10.175Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts test/types/route.test-d.ts && npm run test:typescript",
"addedAt": "2026-02-19T20:25:56.421Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts test/types/route.test-d.ts",
"addedAt": "2026-02-19T20:27:12.547Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && git checkout -b fix/6513-route-hook-meta-return",
"addedAt": "2026-02-19T20:27:31.569Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && git add types/route.d.ts test/types/route.test-d.ts && git commit -m \"fix(types): allow async route hooks in shorthand options\"",
"addedAt": "2026-02-19T20:27:35.261Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npx tsc /tmp/ctx-test2.ts --noEmit --strict",
"addedAt": "2026-02-19T20:44:45.451Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/hooks.test-d.ts",
"addedAt": "2026-02-19T20:47:16.815Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/route.test-d.ts",
"addedAt": "2026-02-19T20:47:37.607Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npx tsd",
"addedAt": "2026-02-19T20:48:17.812Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts && npx tsd --files test/types/hooks.test-d.ts && npx tsd --files test/types/route.test-d.ts",
"addedAt": "2026-02-19T20:51:05.173Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "which gh && gh --version && gh auth status",
"addedAt": "2026-02-20T07:03:43.023Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "gh api repos/fastify/fastify/rulesets",
"addedAt": "2026-02-22T22:29:22.020Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "node --test test/fastify-instance.test.js",
"addedAt": "2026-02-23T07:23:34.147Z",
"note": "Always accept",
"source": "user"
},
{
"type": "exact",
"value": "nl -ba scripts/validate-ecosystem-links.js | sed -n '1,220p'",
"addedAt": "2026-02-23T07:23:55.927Z",
"note": "Always accept",
"source": "user"
},
{
"type": "pattern",
"value": "^npx\\s+eslint\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+$",
"addedAt": "2026-03-09T12:41:11.065Z",
"note": "Always accept generic",
"source": "ai"
},
{
"type": "pattern",
"value": "^node\\s+--test\\s+[\\w./~-]+\\s+[\\w./~-]+$",
"addedAt": "2026-03-09T12:41:58.425Z",
"note": "Always accept generic",
"source": "ai"
},
{
"type": "pattern",
"value": "^npm\\s+run\\s+test:typescript$",
"addedAt": "2026-03-09T12:42:48.283Z",
"note": "Always accept generic",
"source": "ai"
},
{
"type": "exact",
"value": "gh search code \"request.host org:fastify\" --limit 20 --json repository,path,url",
"addedAt": "2026-03-09T21:03:22.953Z",
"note": "Always accept exact",
"source": "user"
}
],
"version": 2
}