fastify
SKILL.md
Fastify Web Framework (v5.8.x)
You are an expert at building high-performance HTTP servers and APIs with Fastify. This skill references the Fastify v5.8.x official documentation.
Core Principles
- Performance first — Fastify is built for speed. Use JSON Schema for validation and serialization to get 10-20% throughput gains via
fast-json-stringify. - Schema-driven — Define schemas for request body, querystring, params, headers, and response. Schemas enable validation, serialization, and documentation.
- Plugin architecture — Everything is a plugin. Use
fastify.register()to extend functionality. Plugins create encapsulation contexts (DAG structure). - Encapsulation — Decorators, hooks, and plugins are scoped. Child contexts inherit from parents but not vice versa. Use
fastify-pluginto break encapsulation when needed. - Lifecycle hooks — The request lifecycle flows: Routing → onRequest → preParsing → Parsing → preValidation → Validation → preHandler → Handler → preSerialization → onSend → onResponse. Each hook point allows injection.
How to Use This Skill
Before generating code, load the relevant reference file(s):
references/server.md— Factory options, server instance methods (listen, close, register, inject, addSchema, setErrorHandler, setNotFoundHandler, decorators, etc.)references/routes.md— Route declaration (full and shorthand), URL building, parametric/wildcard routes, async/await patterns, route prefixing, constraintsreferences/request-reply.md— Request object properties, Reply methods (code, header, send, redirect, hijack, type, serialization)references/hooks.md— All request/reply hooks (onRequest through onResponse) and application hooks (onReady, onListen, onClose, preClose, onRoute, onRegister), diagnostics channelsreferences/validation-serialization.md— JSON Schema validation with Ajv, response serialization with fast-json-stringify, shared schemas ($ref), custom validators (Joi, Yup), schemaErrorFormatterreferences/plugins-encapsulation.md— Plugin creation, scope management, fastify-plugin, encapsulation contexts, ESM supportreferences/decorators.md— decorate, decorateRequest, decorateReply, getter/setter patterns, getDecorator, setDecorator, dependency managementreferences/typescript.md— TypeScript setup, generics, type providers (@fastify/type-provider-typebox, @fastify/type-provider-json-schema-to-ts), creating typed pluginsreferences/logging.md— Pino logger setup, environment-specific config, serializers, log redaction, custom loggersreferences/errors.md— Error handling patterns, setErrorHandler, error codes (FST_ERR_*), error propagationreferences/content-type-parser.md— Custom parsers, catch-all, removeContentTypeParser, parseAs optionsreferences/http2-middleware.md— HTTP2 (secure and plain), ALPN negotiation, Express middleware via @fastify/express or @fastify/middie
Quick Reference
Creating a Server
import Fastify from 'fastify'
const fastify = Fastify({
logger: true, // enable Pino logging
bodyLimit: 1048576, // 1 MiB (default)
caseSensitive: true, // default: true
ignoreDuplicateSlashes: false,
ignoreTrailingSlash: false,
maxParamLength: 100,
})
// Register plugins
await fastify.register(import('./routes.js'), { prefix: '/api' })
// Start
await fastify.listen({ port: 3000, host: '0.0.0.0' })
Route with Full Schema
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
},
},
},
},
handler: async (request, reply) => {
const user = await createUser(request.body)
return reply.code(201).send(user)
},
})
Plugin Pattern
// my-plugin.js — encapsulated by default
export default async function myPlugin(fastify, opts) {
fastify.decorate('myUtil', () => 'hello')
fastify.get('/hello', async () => ({ hello: 'world' }))
}
// shared-plugin.js — breaks encapsulation with fastify-plugin
import fp from 'fastify-plugin'
export default fp(async function sharedPlugin(fastify, opts) {
fastify.decorateRequest('user', null)
}, { name: 'shared-plugin' })
Hooks
// Authentication hook
fastify.addHook('preHandler', async (request, reply) => {
const token = request.headers.authorization
if (!token) {
reply.code(401).send({ error: 'Unauthorized' })
return reply // must return reply to stop lifecycle
}
request.user = await verifyToken(token)
})
Error Handling
fastify.setErrorHandler((error, request, reply) => {
request.log.error(error)
const statusCode = error.statusCode >= 400 ? error.statusCode : 500
reply.status(statusCode).send({
error: statusCode >= 500 ? 'Internal Server Error' : error.message,
})
})
TypeScript
import Fastify, { FastifyRequest, FastifyReply } from 'fastify'
interface IBody { name: string }
interface IParams { id: string }
interface IQuerystring { limit: number }
fastify.get<{ Params: IParams; Querystring: IQuerystring }>(
'/users/:id',
async (request, reply) => {
const { id } = request.params // typed as string
const { limit } = request.query // typed as number
return { id, limit }
}
)
Common Patterns
Decorator Initialization (Avoid Reference Types)
// BAD: reference type shared across requests
fastify.decorateRequest('data', { shared: true })
// GOOD: value type + onRequest hook
fastify.decorateRequest('data', null)
fastify.addHook('onRequest', async (req) => {
req.data = { perRequest: true }
})
Shared Schemas with $ref
fastify.addSchema({
$id: 'User',
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
})
fastify.get('/users', {
schema: {
response: {
200: {
type: 'array',
items: { $ref: 'User#' },
},
},
},
handler: async () => getUsers(),
})
Graceful Shutdown
const signals = ['SIGINT', 'SIGTERM']
for (const signal of signals) {
process.on(signal, async () => {
await fastify.close() // triggers preClose → server.close → onClose hooks
process.exit(0)
})
}
Async/Await in Handlers
// Return value directly (no reply.send needed)
fastify.get('/', async (request, reply) => {
const data = await fetchData()
return data // automatically serialized and sent
})
// When using reply.send(), must return reply
fastify.get('/stream', async (request, reply) => {
const stream = getStream()
reply.type('application/octet-stream').send(stream)
return reply
})
Key Warnings
- Arrow functions in handlers break
thisbinding to FastifyInstance - Never mix callbacks and async in hooks — causes double execution
reply.send()in async hooks must be followed byreturn reply- Undeclared decorators accessing via
req.foowithoutdecorateRequestcauses V8 deoptimization - Schemas as code: treat schema definitions as application code — never accept user-provided schemas (uses
new Function()) - Validation with allErrors: true can enable DoS attacks — Fastify defaults to
false
Weekly Installs
1
Repository
zackbart/skillsFirst Seen
5 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1