From harness-claude
Builds performant Fastify APIs using schema validation, plugins, decorators, hooks, and TypeBox for type-safe routes. For high-performance REST services.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Build performant APIs with Fastify using schema validation, plugins, decorators, and hooks
Guides Fastify development for high-performance Node.js APIs: routing, plugins, JSON Schema validation, hooks, serialization, error handling, and optimization.
Builds REST APIs with Fastify in TypeScript, covering route creation, TypeBox schema validation, request handling, app structuring, HTTP handlers, and plugins.
Guides Fastify Node.js backend servers and REST APIs in TypeScript/JavaScript, covering routes, plugins, JSON Schema validation, hooks, authentication, performance, Pino logging, and deployment.
Share bugs, ideas, or general feedback.
Build performant APIs with Fastify using schema validation, plugins, decorators, and hooks
import Fastify from 'fastify';
const fastify = Fastify({ logger: true });
fastify.get('/health', async () => ({ status: 'ok' }));
await fastify.listen({ port: 3000, host: '0.0.0.0' });
const createUserSchema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
} as const;
fastify.post('/users', { schema: createUserSchema }, async (request, reply) => {
const user = await createUser(request.body);
reply.status(201).send(user);
});
import { Type, Static } from '@sinclair/typebox';
const CreateUserBody = Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
});
type CreateUserBody = Static<typeof CreateUserBody>;
fastify.post<{ Body: CreateUserBody }>(
'/users',
{
schema: { body: CreateUserBody },
},
async (request) => {
const { name, email } = request.body; // Fully typed
return createUser({ name, email });
}
);
import fp from 'fastify-plugin';
// db.ts — database plugin
export default fp(async (fastify) => {
const db = await connectDatabase();
fastify.decorate('db', db);
fastify.addHook('onClose', async () => {
await db.disconnect();
});
});
// Register plugins
await fastify.register(import('./plugins/db'));
await fastify.register(import('./routes/users'), { prefix: '/api/users' });
// routes/users.ts
import { FastifyPluginAsync } from 'fastify';
const userRoutes: FastifyPluginAsync = async (fastify) => {
fastify.get('/', async () => {
return fastify.db.user.findMany();
});
fastify.get('/:id', async (request) => {
const { id } = request.params as { id: string };
const user = await fastify.db.user.findUnique({ where: { id } });
if (!user) throw fastify.httpErrors.notFound('User not found');
return user;
});
};
export default userRoutes;
// Authentication hook
fastify.addHook('preHandler', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw fastify.httpErrors.unauthorized();
request.user = await verifyToken(token);
});
// Logging hook
fastify.addHook('onResponse', async (request, reply) => {
request.log.info({
url: request.url,
method: request.method,
statusCode: reply.statusCode,
duration: reply.elapsedTime,
});
});
fastify.setErrorHandler((error, request, reply) => {
request.log.error(error);
if (error.validation) {
reply.status(400).send({ errors: error.validation });
return;
}
reply.status(error.statusCode ?? 500).send({
error: error.message || 'Internal Server Error',
});
});
Fastify is designed for speed. It uses JSON Schema for validation and serialization, achieving 2-5x higher throughput than Express for JSON APIs.
Why Fastify is faster:
fast-json-stringify)ajv)Plugin encapsulation: Fastify plugins run in isolated contexts. Decorators and hooks registered inside a plugin are scoped to that plugin and its children, not the entire application.
fp (fastify-plugin): Wrapping a plugin with fp() breaks encapsulation, making decorators available to the parent scope. Use fp for plugins that add shared functionality (database, auth); use regular plugins for routes.
Trade-offs:
https://fastify.dev/docs/latest/