Fastify 5 Best Practices
Table of Contents
<quick_reference>
Request lifecycle (exact order)
Incoming Request
└─ Routing
└─ onRequest hooks
└─ preParsing hooks
└─ Content-Type Parsing
└─ preValidation hooks
└─ Schema Validation (→ 400 on failure)
└─ preHandler hooks
└─ Route Handler
└─ preSerialization hooks
└─ onSend hooks
└─ Response Sent
└─ onResponse hooks
Error at any stage → onError hooks → error handler → onSend → response → onResponse.
</quick_reference>
<anti_patterns>
Top anti-patterns
-
Mixing async/callback in handlers — Use
asyncOR callbacks, never both. With async,returnthe value; don't callreply.send()AND return. -
Returning
undefinedfrom async handler — Fastify treats this as "no response yet". Return the data or callreply.send(). -
Using arrow functions when you need
this— Arrow functions don't bindthisto the Fastify instance. Usefunctiondeclarations for handlers that needthis. -
Forgetting
fastify-pluginwrapper — Without it, decorators/hooks stay scoped to the child context. Parent and sibling plugins won't see them. -
Decorating with reference types directly —
decorateRequest('data', {})shares the SAME object across all requests. Usenullinitial +onRequesthook to assign per-request. -
Sending response in
onErrorhook —onErroris read-only for logging. UsesetErrorHandler()to modify error responses. -
Not handling
reply.send()in async hooks — Callreturn replyafterreply.send()in async hooks to prevent "Reply already sent" errors. -
Ignoring encapsulation — Decorators/hooks registered in child plugins are invisible to parents. Design your plugin tree carefully.
-
String concatenation in SQL from route params — Always use parameterized queries. Fastify validates input shape, not content safety.
-
Missing response schema — Without
responseschema, Fastify serializes withJSON.stringify()(slow) and may leak sensitive fields. Usefast-json-stringifyvia response schemas.
</anti_patterns>
<examples>Quick patterns
Plugin with fastify-plugin (FastifyPluginCallback)
Project convention: use FastifyPluginCallback + done() (avoids require-await lint errors).
import fp from "fastify-plugin";
import type { FastifyPluginCallback } from "fastify";
const myPlugin: FastifyPluginCallback = (fastify, opts, done) => {
fastify.decorate("myService", new MyService());
done();
};
export default fp(myPlugin, { name: "my-plugin" });
Route with validation
fastify.post<{ Body: CreateUserBody }>("/users", {
schema: {
body: {
type: "object",
required: ["email", "name"],
properties: {
email: { type: "string", format: "email" },
name: { type: "string", minLength: 1 },
},
},
response: {
201: {
type: "object",
properties: {
id: { type: "string" },
email: { type: "string" },
},
},
},
},
handler: async (request, reply) => {
const user = await createUser(request.body);
return reply.code(201).send(user);
},
});
Hook (application-level)
fastify.addHook("onRequest", async (request, reply) => {
request.startTime = Date.now();
});
fastify.addHook("onResponse", async (request, reply) => {
request.log.info({ elapsed: Date.now() - request.startTime }, "request completed");
});
Error handler
fastify.setErrorHandler((error, request, reply) => {
request.log.error(error);
const statusCode = error.statusCode ?? 500;
reply.code(statusCode).send({
error: statusCode >= 500 ? "Internal Server Error" : error.message,
});
});
</examples>
<references>
Reference files
Load the relevant file when you need detailed API information:
- Server factory & options — constructor options, server methods, properties: references/server-and-options.md
- Routes & handlers — declaration, URL params, async patterns, constraints: references/routes-and-handlers.md
- Hooks & lifecycle — all 16 hook types, signatures, scope, early response: references/hooks-and-lifecycle.md
- Plugins & encapsulation — creating plugins, fastify-plugin, context inheritance: references/plugins-and-encapsulation.md
- Validation & serialization — JSON Schema, Ajv, response schemas, custom validators: references/validation-and-serialization.md
- Request, Reply & errors — request/reply API, error handling, FST_ERR codes: references/request-reply-errors.md
- TypeScript & logging — route generics, type providers, Pino config, decorators: references/typescript-and-logging.md