Fastify
fastifyResilience gives every request the same inbound protection as the Express and Hono middleware: a deadline that propagates to every pipeline called underneath, load shedding, a rate limit with RateLimit-* headers, cancellation on client disconnect, and JSON 504/503/429/409/403 responses.
npm i resilience-pipeline fastify # fastify >= 4 (optional peer dependency)import Fastify from 'fastify';import { getResilienceSignal } from 'resilience-pipeline';import { fastifyResilience } from 'resilience-pipeline/fastify';
const app = Fastify();
// Register and await it BEFORE declaring routes.await app.register(fastifyResilience, { timeout: '5s', concurrency: { permitLimit: 200, queueLimit: 100, queueTimeout: '1s' }, rateLimit: { key: (req) => req.ip, permitLimit: 100, window: '1m' },});
app.get('/orders/:id', async (req) => { // Aborted when the 5 s budget runs out or the client disconnects. return db.orders.find((req.params as { id: string }).id, { signal: getResilienceSignal() });});
await app.listen({ port: 3000 });Options
Section titled “Options”The plugin takes InboundOptions, the same as the Express middleware: timeout, concurrency, rateLimit, rateLimitHeaders, pipeline. See the options table. rateLimit.key receives the Fastify request.
How it hooks in
Section titled “How it hooks in”onRequesthook. Each request enters the pipeline (rate limit → concurrency → timeout) inonRequest. The slot is held until the raw response finishes or the connection closes.onRoutehook. Fastify reaches your handler through body parsing and other async hooks, which can loseAsyncLocalStorage(any callback-based hook or connection pool does). The plugin wraps every route handler declared after it to re-enter the request’s context, sogetResilienceSignal()and nested pipelines still see the request deadline. That is why it must be registered, and awaited, before your routes.- Encapsulation. The plugin skips Fastify’s encapsulation, like a
fastify-pluginplugin. Its hooks land on the instance you register it on: on the root instance they cover the whole app; inside a plugin they cover that plugin’s routes (and its children). - Responses. A rejection is sent with
reply.code(status).headers(…).send({ statusCode, error, message }), plusRetry-Afterwhen known. Rate-limit headers are added to every response when a rate limiter is configured. - Errors. If the pipeline fails with something other than a resilience error before your handler started, it goes to Fastify through the hook’s
next(error).
Errors thrown by your handlers go to Fastify’s error handler as usual. To map resilience errors raised downstream (an open circuit, a timeout), use describeResilienceError:
import { describeResilienceError } from 'resilience-pipeline/http';
app.setErrorHandler((error, req, reply) => { const d = describeResilienceError(error); if (!d) return reply.send(error); // Fastify's default handling if (d.retryAfter !== undefined) reply.header('retry-after', String(d.retryAfter)); return reply.code(d.httpStatus).send({ code: d.code, message: d.message });});Related
Section titled “Related”- Express and Hono: options table, status mapping, GraphQL/gRPC.
- Load shedding
- Context, signals and deadline propagation