Claude Code for Fastify: High-Performance Node.js APIs — Claude Skills 360 Blog
Blog / Backend / Claude Code for Fastify: High-Performance Node.js APIs
Backend

Claude Code for Fastify: High-Performance Node.js APIs

Published: November 16, 2026
Read time: 8 min read
By: Claude Skills 360

Fastify is the fastest Node.js HTTP framework — 2-3x faster than Express due to its schema-based serialization and compiled JSON handling. TypeBox schemas define both runtime validation and TypeScript types in one declaration. The plugin system with fastify-plugin creates composable, encapsulated modules. Claude Code writes Fastify route handlers with full TypeScript generics, plugin wrappers, lifecycle hooks, and the schema definitions that give Fastify its speed advantage.

CLAUDE.md for Fastify Projects

## API Stack
- Fastify 5.x with TypeScript
- Schema: TypeBox for route schemas (one source of truth for types + validation)
- Auth: @fastify/jwt for Bearer token auth
- Database: @fastify/postgres (pg pool) or drizzle-orm
- Plugins: fastify-plugin for cross-scope decoration; scoped for route-level features
- Error handling: never throw Error — use httpErrors.unauthorized() etc.
- Validation: schema.body/params/querystring on every route — no raw req.body access
- Serialization: response schemas enable 2x faster JSON serialization

Server Setup

// src/server.ts
import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import cors from '@fastify/cors';
import jwt from '@fastify/jwt';
import { ordersRoutes } from './routes/orders';
import { authRoutes } from './routes/auth';
import { dbPlugin } from './plugins/db';

export function buildServer() {
  const app = Fastify({
    logger: {
      level: process.env.LOG_LEVEL ?? 'info',
      transport: process.env.NODE_ENV === 'development'
        ? { target: 'pino-pretty' }
        : undefined,
    },
  }).withTypeProvider<TypeBoxTypeProvider>();  // Enables TypeBox inference
  
  // Global plugins
  app.register(cors, { origin: process.env.CORS_ORIGIN ?? false });
  app.register(jwt, { secret: process.env.JWT_SECRET! });
  app.register(dbPlugin);  // Decorates app.db
  
  // Routes (encapsulated by default — auth can be scoped)
  app.register(authRoutes, { prefix: '/api/auth' });
  app.register(ordersRoutes, { prefix: '/api/orders' });
  
  // Global error handler
  app.setErrorHandler((error, request, reply) => {
    if (error.validation) {
      return reply.status(400).send({
        error: 'Validation Error',
        message: error.message,
        fields: error.validation,
      });
    }
    
    app.log.error({ err: error, req: request.id }, 'Unhandled error');
    return reply.status(error.statusCode ?? 500).send({
      error: 'Internal Server Error',
    });
  });
  
  return app;
}

// main.ts
const app = buildServer();
await app.listen({ port: parseInt(process.env.PORT ?? '3000'), host: '0.0.0.0' });

TypeBox Schemas and Routes

// src/routes/orders.ts
import { Type, type Static } from '@sinclair/typebox';
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
import createHttpError from 'http-errors';

// Schemas: define once, TypeScript types inferred automatically
const CreateOrderBody = Type.Object({
  customerId: Type.String({ format: 'uuid' }),
  items: Type.Array(Type.Object({
    productId: Type.String({ format: 'uuid' }),
    quantity: Type.Integer({ minimum: 1, maximum: 100 }),
    unitPriceCents: Type.Integer({ minimum: 1 }),
  }), { minItems: 1 }),
  notes: Type.Optional(Type.String({ maxLength: 500 })),
});

const OrderParams = Type.Object({
  orderId: Type.String({ format: 'uuid' }),
});

const OrderResponse = Type.Object({
  id: Type.String(),
  status: Type.String(),
  totalCents: Type.Integer(),
  createdAt: Type.String({ format: 'date-time' }),
});

type CreateOrderInput = Static<typeof CreateOrderBody>;

export const ordersRoutes: FastifyPluginAsyncTypebox = async (app) => {
  // Decorate all routes in this scope with JWT verification
  app.addHook('preHandler', app.authenticate);
  
  app.post('/', {
    schema: {
      body: CreateOrderBody,
      response: { 201: OrderResponse },
    },
  }, async (request, reply) => {
    // request.body is fully typed as CreateOrderInput
    const { customerId, items, notes } = request.body;
    
    const totalCents = items.reduce(
      (sum, item) => sum + item.quantity * item.unitPriceCents,
      0
    );
    
    const order = await app.db.createOrder({ customerId, items, notes, totalCents });
    
    return reply.status(201).send(order);
  });
  
  app.get('/:orderId', {
    schema: {
      params: OrderParams,
      response: { 200: OrderResponse },
    },
  }, async (request, reply) => {
    const order = await app.db.getOrder(request.params.orderId);
    
    if (!order) throw createHttpError.NotFound('Order not found');
    if (order.userId !== request.user.id && !request.user.isAdmin) {
      throw createHttpError.Forbidden();
    }
    
    return order;
  });
};

Auth Plugin with Decorator

// src/plugins/auth.ts
import fp from 'fastify-plugin';
import type { FastifyPluginAsync } from 'fastify';

// fp() makes decorations available outside the encapsulated scope
const authPlugin: FastifyPluginAsync = fp(async (app) => {
  // Decorate request with user type
  app.decorateRequest('user', null);
  
  // Reusable preHandler hook
  app.decorate('authenticate', async function(request, reply) {
    try {
      const payload = await request.jwtVerify<{ id: string; email: string; isAdmin: boolean }>();
      request.user = payload;
    } catch {
      reply.code(401).send({ error: 'Unauthorized' });
    }
  });
});

export default authPlugin;

// Type augmentation
declare module 'fastify' {
  interface FastifyInstance {
    authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
  }
  interface FastifyRequest {
    user: { id: string; email: string; isAdmin: boolean };
  }
}

Lifecycle Hooks

// src/hooks/request-id.ts — lifecycle hooks for tracing/logging
import fp from 'fastify-plugin';

export default fp(async (app) => {
  // onRequest: first hook in lifecycle
  app.addHook('onRequest', async (request) => {
    request.startTime = Date.now();
  });
  
  // onSend: called before response is sent — add headers
  app.addHook('onSend', async (request, reply) => {
    reply.header('X-Request-ID', request.id);
    reply.header('X-Response-Time', `${Date.now() - request.startTime}ms`);
  });
  
  // onError: log response errors with context
  app.addHook('onError', async (request, reply, error) => {
    app.log.error({
      reqId: request.id,
      statusCode: reply.statusCode,
      url: request.url,
      method: request.method,
      err: error.message,
    }, 'Request error');
  });
});

declare module 'fastify' {
  interface FastifyRequest { startTime: number; }
}

For the Express.js comparison — when Fastify’s performance matters vs Express’s ecosystem size — the Node.js backend patterns guide covers REST API design conventions. For deploying Fastify on AWS Lambda with a 10ms cold start, the AWS Lambda guide covers Fastify Lambda adapters. The Claude Skills 360 bundle includes Fastify skill sets covering TypeBox schemas, plugin architecture, JWT auth decorators, and lifecycle hooks. Start with the free tier to try Fastify route generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free