Harness-engineering node-fastify-patterns

Node.js Fastify Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/node-fastify-patterns" ~/.claude/skills/intense-visions-harness-engineering-node-fastify-patterns && rm -rf "$T"
manifest: agents/skills/claude-code/node-fastify-patterns/SKILL.md
source content

Node.js Fastify Patterns

Build performant APIs with Fastify using schema validation, plugins, decorators, and hooks

When to Use

  • Building high-performance REST APIs
  • Needing built-in request/response validation with JSON Schema
  • Using a plugin system for modular architecture
  • When Express performance is insufficient

Instructions

  1. Basic Fastify server:
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' });
  1. Schema-based validation (auto-generates docs and validates):
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);
});
  1. Type-safe routes with TypeBox:
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 });
  }
);
  1. Plugins for modular architecture:
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' });
  1. Route plugins:
// 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;
  1. Hooks for cross-cutting concerns:
// 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,
  });
});
  1. Error handling:
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',
  });
});

Details

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:

  • Schema-based serialization compiles JSON stringification to optimized code (using
    fast-json-stringify
    )
  • Schema-based validation uses pre-compiled validators (using
    ajv
    )
  • Encapsulation model avoids middleware overhead
  • Built on a radix tree router (faster than Express's regex-based routing)

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:

  • JSON Schema validation is fast — but JSON Schema syntax is verbose compared to Zod
  • Plugins provide clean architecture — but the encapsulation model can be confusing at first
  • TypeBox provides TypeScript inference from schemas — but adds another dependency
  • Fastify is faster than Express — but has a smaller ecosystem and community

Source

https://fastify.dev/docs/latest/

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.