agentleFS
ControllableModelControllableModel/SWE-Skills-Bench/skills/fastify-patterns/SKILL.md

fastify-patterns

Skill8 starsChanged 26 days agoMITView on GitHub

Production Fastify (TypeScript) patterns: schema validation, plugins, typed routes, error handling, security hardening, logging, testing with inject, and graceful shutdown

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 7 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use plugins to keep concerns isolated and testable (auth, db, routes).

    line 109
  2. Distinctive:

    Add standard security plugins and enforce payload limits.

    line 140
  3. Distinctive:

    Test routes in-memory without binding ports.

    line 171
  4. Distinctive:

    Prefer Fastify for schema-based validation, predictable plugins, and high throughput.

    line 194
  5. Distinctive:

    Prefer Express for minimal middleware and maximal ecosystem familiarity.

    line 195
  6. Distinctive:

    Prefer Zod for app codebases that already standardize on Zod (forms, tRPC, shared types).

    line 199
  7. Distinctive:

    Prefer TypeBox for OpenAPI generation and performance-critical validation.

    line 200

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Fastify (TypeScript) - Production Backend Framework

Overview

Fastify is a high-performance Node.js web framework built around JSON schema validation, encapsulated plugins, and great developer ergonomics. In TypeScript, pair Fastify with a type provider (Zod or TypeBox) to keep runtime validation and static types aligned.

Quick Start

Minimal server

✅ Correct: basic server with typed response

import Fastify from "fastify";

const app = Fastify({ logger: true });

app.get("/health", async () => ({ status: "ok" as const }));

await app.listen({ host: "0.0.0.0", port: 3000 });

❌ Wrong: start server without awaiting listen

app.listen({ port: 3000 });
console.log("started"); // races startup and hides bind failures

Schema Validation + Type Providers

Fastify validates requests/responses via JSON schema. Use a type provider to avoid duplicating types.

Zod provider (recommended for full-stack TypeScript)

✅ Correct: Zod schema drives validation + types

import Fastify from "fastify";
import { z } from "zod";
import { ZodTypeProvider } from "fastify-type-provider-zod";

const app = Fastify({ logger: true }).withTypeProvider<ZodTypeProvider>();

const Query = z.object({ q: z.string().min(1) });

app.get(
  "/search",
  { schema: { querystring: Query } },
  async (req) => {
    return { q: req.query.q };
  },
);

await app.listen({ port: 3000 });

TypeBox provider (recommended for OpenAPI + performance)

✅ Correct: TypeBox schema

import Fastify from "fastify";
import { Type } from "@sinclair/typebox";
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox";

const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>();

const Params = Type.Object({ id: Type.String({ minLength: 1 }) });
const Reply = Type.Object({ id: Type.String() });

app.get(
  "/users/:id",
  { schema: { params: Params, response: { 200: Reply } } },
  async (req) => ({ id: req.params.id }),
);

await app.listen({ port: 3000 });

Plugin Architecture (Encapsulation)

Use plugins to keep concerns isolated and testable (auth, db, routes).

✅ Correct: route plugin

import type { FastifyPluginAsync } from "fastify";

export const usersRoutes: FastifyPluginAsync = async (app) => {
  app.get("/", async () => [{ id: "1" }]);
  app.get("/:id", async (req) => ({ id: (req.params as any).id }));
};

✅ Correct: register with a prefix

app.register(usersRoutes, { prefix: "/api/v1/users" });

Error Handling

Centralize unexpected failures and return stable error shapes.

✅ Correct: setErrorHandler

app.setErrorHandler((err, req, reply) => {
  req.log.error({ err }, "request failed");
  reply.status(500).send({ error: "internal" as const });
});

Security Hardening (Baseline)

Add standard security plugins and enforce payload limits.

✅ Correct: Helmet + CORS + rate limiting

import helmet from "@fastify/helmet";
import cors from "@fastify/cors";
import rateLimit from "@fastify/rate-limit";

await app.register(helmet);
await app.register(cors, { origin: false });
await app.register(rateLimit, { max: 100, timeWindow: "1 minute" });

Graceful Shutdown

Close HTTP server and downstream clients (DB, queues) on SIGINT/SIGTERM.

✅ Correct: close on signals

const close = async (signal: string) => {
  app.log.info({ signal }, "shutting down");
  await app.close();
  process.exit(0);
};

process.on("SIGINT", () => void close("SIGINT"));
process.on("SIGTERM", () => void close("SIGTERM"));

Testing (Fastify inject)

Test routes in-memory without binding ports.

✅ Correct: inject request

import Fastify from "fastify";
import { describe, it, expect } from "vitest";

describe("health", () => {
  it("returns ok", async () => {
    const app = Fastify();
    app.get("/health", async () => ({ status: "ok" as const }));

    const res = await app.inject({ method: "GET", url: "/health" });
    expect(res.statusCode).toBe(200);
    expect(res.json()).toEqual({ status: "ok" });
  });
});

Decision Trees

Fastify vs Express

  • Prefer Fastify for schema-based validation, predictable plugins, and high throughput.
  • Prefer Express for minimal middleware and maximal ecosystem familiarity.

Zod vs TypeBox

  • Prefer Zod for app codebases that already standardize on Zod (forms, tRPC, shared types).
  • Prefer TypeBox for OpenAPI generation and performance-critical validation.

Anti-Patterns

  • Skip request validation; validate at boundaries with schemas.
  • Register everything in main.ts; isolate routes and dependencies into plugins.
  • Return raw error objects; return stable error shapes and log the details.

Resources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.