From ts-dev-kit
Provides Fastify 5 best practices, API reference, and patterns for routes, plugins, hooks, validation, error handling, and TypeScript. Use for writing routes/plugins/hooks, looking up APIs, debugging lifecycle/validation issues, or reviewing anti-patterns.
npx claudepluginhub jgamaraalv/ts-dev-kit --plugin ts-dev-kitThis skill uses the workspace's default tool permissions.
- [Request lifecycle](#request-lifecycle-exact-order)
Builds performant Fastify APIs using schema validation, plugins, decorators, hooks, and TypeBox for type-safe routes. For high-performance REST services.
Builds REST APIs with Fastify in TypeScript, covering route creation, TypeBox schema validation, request handling, app structuring, HTTP handlers, and plugins.
Provides expert Node.js backend patterns for Express, NestJS, Fastify APIs including project structures, async error handlers, custom error classes, and global error handling.
Share bugs, ideas, or general feedback.
<quick_reference>
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>
Mixing async/callback in handlers — Use async OR callbacks, never both. With async, return the value; don't call reply.send() AND return.
Returning undefined from async handler — Fastify treats this as "no response yet". Return the data or call reply.send().
Using arrow functions when you need this — Arrow functions don't bind this to the Fastify instance. Use function declarations for handlers that need this.
Forgetting fastify-plugin wrapper — 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. Use null initial + onRequest hook to assign per-request.
Sending response in onError hook — onError is read-only for logging. Use setErrorHandler() to modify error responses.
Not handling reply.send() in async hooks — Call return reply after reply.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 response schema, Fastify serializes with JSON.stringify() (slow) and may leak sensitive fields. Use fast-json-stringify via response schemas.
</anti_patterns>
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" });
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);
},
});
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");
});
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,
});
});
Load the relevant file when you need detailed API information: