Node.js & Backend Fastify Schema-Validierung Swagger/OpenAPI Performance

Fastify mit Claude Code: Schnelle Node.js APIs 2026

Fastify als schnellstes Node.js-Framework — Claude Code baut typsichere APIs mit Schema-Validierung, Plugin-System und automatischer OpenAPI-Dokumentation.

6. Mai 2026 11 min Lesezeit Node.js & Backend

Fastify hat sich 2026 als das schnellste produktionsreife Node.js-Framework etabliert. Mit bis zu 77.000 Requests pro Sekunde (vs. ~15.000 bei Express), nativem TypeScript-Support und einem integrierten Schema-System auf Basis von JSONSchema bietet Fastify alles, was moderne Backend-Teams benötigen. Claude Code versteht Fastifys Plugin-Architektur, Scope-Kapselung und Lifecycle-Hooks tief — und hilft dabei, typsichere, gut dokumentierte APIs in Minuten zu scaffolden.

Warum Fastify statt Express?

Express ist flexibel, aber untypisiert und langsam. Fastify erzwingt Schema-First-Design, bietet automatisches Request/Response-Typing via TypeScript-Generics und serialisiert JSON mit fast-json-stringify — 2–3× schneller als JSON.stringify(). Claude Code nutzt diese Strukturen, um konsistenten, wartbaren API-Code zu generieren.

1. Fastify Setup — fastify(), listen(), TypeScript-Integration

Das Setup einer neuen Fastify-Applikation beginnt mit der Installation der Kernpakete und einer durchdachten Verzeichnisstruktur. Claude Code legt bei neuen Projekten immer TypeScript-First an — mit strikten Compiler-Optionen und expliziten Typen für alle Route-Handler.

Terminal — Installation
# Fastify + TypeScript-Toolchain
npm install fastify @fastify/type-provider-typebox
npm install -D typescript @types/node ts-node nodemon

# Optionale Plugins vorab installieren
npm install @fastify/swagger @fastify/swagger-ui
npm install @fastify/cors @fastify/helmet @fastify/jwt
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
src/app.ts — Applikations-Einstiegspunkt
import Fastify, { FastifyInstance } from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'

export async function buildApp(): Promise<FastifyInstance> {
  const app = Fastify({
    logger: {
      level: process.env.LOG_LEVEL ?? 'info',
      transport: process.env.NODE_ENV !== 'production'
        ? { target: 'pino-pretty' }
        : undefined
    },
    trustProxy: true
  }).withTypeProvider<TypeBoxTypeProvider>()

  // Plugins registrieren
  await app.register(import('./plugins/cors'))
  await app.register(import('./plugins/auth'))
  await app.register(import('./routes/users'), { prefix: '/api/v1' })
  await app.register(import('./routes/products'), { prefix: '/api/v1' })

  return app
}

async function main() {
  const app = await buildApp()
  try {
    await app.listen({
      port: Number(process.env.PORT) || 3000,
      host: '0.0.0.0'
    })
  } catch (err) {
    app.log.error(err)
    process.exit(1)
  }
}

main()

fastify-plugin (fp) — Warum es wichtig ist

Fastify kapselt Plugins standardmäßig in einen eigenen Scope. Dekoratoren, die in einem Plugin registriert werden, sind im Eltern-Scope nicht sichtbar — außer das Plugin wird mit fastify-plugin (kurz fp) gewrappt. Claude Code erkennt dieses Muster und wendet es automatisch an, wenn ein Plugin app-weit verfügbar sein soll.

src/plugins/db.ts — Shared Database Plugin
import fp from 'fastify-plugin'
import { FastifyPluginAsync } from 'fastify'
import { Pool } from 'pg'

declare module 'fastify' {
  interface FastifyInstance {
    db: Pool
  }
}

const dbPlugin: FastifyPluginAsync = async (app) => {
  const pool = new Pool({
    connectionString: process.env.DATABASE_URL
  })
  // Dekorator macht pool app-weit verfügbar
  app.decorate('db', pool)

  app.addHook('onClose', async () => {
    await pool.end()
  })
}

// fp() hebt Scope-Kapselung auf → db ist überall erreichbar
export default fp(dbPlugin, { name: 'db' })
Claude Code Tipp

Frage Claude Code: "Erstelle ein Fastify-Projekt mit TypeScript, PostgreSQL-Plugin (fp) und JWT-Auth — strukturiert nach Clean Architecture". Claude erzeugt die vollständige Verzeichnisstruktur, tsconfig, Plugin-Registrierung und Type-Augmentation in einem Durchlauf.

2. Routes und Schema — route(), JSONSchema-Validierung

Fastifys größter Vorteil gegenüber Express ist das integrierte Schema-System. Jede Route kann ein JSONSchema für body, querystring, params und response definieren. Fastify validiert eingehende Daten automatisch und serialisiert Antworten schneller durch das Schema.

src/routes/users.ts — Schema-validierte Routes
import { FastifyPluginAsync } from 'fastify'
import { Type } from '@sinclair/typebox'

// TypeBox-Schemas (kompatibel mit JSONSchema + TypeScript-Types)
const UserParams = Type.Object({
  id: Type.String({ format: 'uuid' })
})

const CreateUserBody = Type.Object({
  name:  Type.String({ minLength: 2, maxLength: 100 }),
  email: Type.String({ format: 'email' }),
  role:  Type.Union([Type.Literal('admin'), Type.Literal('user')])
})

const UserResponse = Type.Object({
  id:        Type.String(),
  name:      Type.String(),
  email:     Type.String(),
  role:      Type.String(),
  createdAt: Type.String({ format: 'date-time' })
})

const ListQuery = Type.Object({
  page:  Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 20 }))
})

const usersRoutes: FastifyPluginAsync = async (app) => {

  // GET /api/v1/users — Alle User auflisten
  app.get('/users', {
    schema: {
      tags: ['users'],
      summary: 'Alle User auflisten',
      querystring: ListQuery,
      response: {
        200: Type.Object({
          data:  Type.Array(UserResponse),
          total: Type.Integer(),
          page:  Type.Integer()
        })
      }
    }
  }, async (request, reply) => {
    const { page = 1, limit = 20 } = request.query
    const offset = (page - 1) * limit
    const { rows, rowCount } = await app.db.query(
      'SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2',
      [limit, offset]
    )
    return { data: rows, total: rowCount ?? 0, page }
  })

  // POST /api/v1/users — Neuen User anlegen
  app.post('/users', {
    schema: {
      tags: ['users'],
      summary: 'Neuen User anlegen',
      body: CreateUserBody,
      response: {
        201: UserResponse,
        409: Type.Object({ error: Type.String() })
      }
    }
  }, async (request, reply) => {
    const { name, email, role } = request.body
    const existing = await app.db.query('SELECT id FROM users WHERE email = $1', [email])
    if (existing.rowCount) {
      return reply.code(409).send({ error: 'E-Mail bereits registriert' })
    }
    const { rows: [user] } = await app.db.query(
      'INSERT INTO users (name, email, role) VALUES ($1, $2, $3) RETURNING *',
      [name, email, role]
    )
    return reply.code(201).send(user)
  })

  // GET /api/v1/users/:id
  app.get('/users/:id', {
    schema: {
      tags: ['users'],
      params: UserParams,
      response: {
        200: UserResponse,
        404: Type.Object({ error: Type.String() })
      }
    }
  }, async (request, reply) => {
    const { rows: [user] } = await app.db.query(
      'SELECT * FROM users WHERE id = $1', [request.params.id]
    )
    if (!user) return reply.code(404).send({ error: 'User nicht gefunden' })
    return user
  })
}

export default usersRoutes
Schema-Validierung

Fastify verwendet ajv intern für Validation. Bei fehlerhaften Requests antwortet Fastify automatisch mit 400 Bad Request und einer strukturierten Fehlermeldung — ohne zusätzlichen Code. Das response-Schema aktiviert fast-json-stringify für die Antwort-Serialisierung.

3. Plugins und Dekoratoren — Encapsulation & Dependency Injection

Das Plugin-System ist Fastifys architektonisches Herzstück. Plugins sind isolierte Einheiten mit eigenem Scope — sie können Dekoratoren, Routes und Hooks registrieren, ohne den globalen State zu verschmutzen. Dieses Modell ermöglicht echte Dependency Injection in Node.js.

fastify.decorate() — Eigene Properties hinzufügen

decorate() fügt dem Fastify-Instanz-Objekt eigene Properties oder Methoden hinzu. Mit decorateRequest() und decorateReply() lassen sich Request- und Reply-Objekte erweitern.

src/plugins/auth.ts — JWT-Auth mit Dekorator
import fp from 'fastify-plugin'
import jwt from '@fastify/jwt'
import { FastifyPluginAsync, FastifyRequest } from 'fastify'

interface JwtPayload {
  sub:   string
  email: string
  role:  'admin' | 'user'
}

declare module 'fastify' {
  interface FastifyInstance {
    authenticate(req: FastifyRequest): Promise<void>
    adminOnly(req: FastifyRequest): Promise<void>
  }
  interface FastifyRequest {
    user: JwtPayload
  }
}

const authPlugin: FastifyPluginAsync = async (app) => {
  await app.register(jwt, {
    secret: process.env.JWT_SECRET!,
    sign: { expiresIn: '7d' }
  })

  // Dekorator: Auth-Middleware als Methode
  app.decorate('authenticate', async (req: FastifyRequest) => {
    await req.jwtVerify<JwtPayload>()
  })

  app.decorate('adminOnly', async (req: FastifyRequest) => {
    await req.jwtVerify<JwtPayload>()
    if (req.user.role !== 'admin') {
      throw app.httpErrors.forbidden('Nur Admins erlaubt')
    }
  })
}

export default fp(authPlugin, { name: 'auth', dependencies: [] })
Route mit Auth-Dekorator als preHandler
app.get('/admin/stats', {
  schema: { tags: ['admin'], security: [{ bearerAuth: [] }] },
  preHandler: [app.adminOnly]  // Nur Admins können diese Route aufrufen
}, async (request) => {
  return { userId: request.user.sub, role: request.user.role }
})

Scope-Kapselung verstehen

Mit fp()Ohne fp()
Dekoratoren app-weit sichtbarDekoratoren nur im eigenen Scope
Hooks gelten für alle RoutesHooks nur für Routes im Scope
Ideal für: DB, Auth, LoggerIdeal für: Route-Gruppen, Feature-Module
Kein automatisches CleanuponClose innerhalb des Scopes
Claude Code Prompt

Gib Claude Code: "Erstelle ein Fastify-Plugin für Rate-Limiting mit Redis und fp() — 100 Requests/Min per IP, Whitelist für interne IPs, TypeScript-Typen vollständig". Claude kennt das Plugin-Pattern und generiert sofort produktionsreifen Code mit Type-Augmentation.

4. Hooks — Lifecycle und Request-Pipeline

Fastify bietet einen vollständigen Request/Response-Lifecycle mit präzise definierten Hook-Punkten. Mit addHook() lässt sich in jeden Schritt eingreifen — von der Authentifizierung über die Serialisierung bis hin zum Error-Handling.

Lifecycle-Diagramm

Incoming Request onRequest preParsing preValidation preHandler Handler preSerialization onSend onResponse

Fehlerfall: onError → onSend → onResponse
HookZeitpunktTypischer Einsatz
onRequestVor ParsingRate-Limiting, IP-Filter, Logging
preParsingVor Body-ParsingContent-Decryption, Streaming
preValidationVor Schema-ValidationBody-Transformation
preHandlerVor HandlerAuth, Permissions, Cache-Check
onSendVor Antwort-SendenResponse-Compression, Header-Injection
onResponseNach AntwortAccess-Log, Metrics, Analytics
onErrorBei FehlerError-Transformation, Sentry-Integration
onCloseApp-ShutdownDB-Verbindung schließen, Cleanup
src/plugins/observability.ts — Hooks für Logging & Metrics
import fp from 'fastify-plugin'
import { FastifyPluginAsync } from 'fastify'

const observability: FastifyPluginAsync = async (app) => {

  // Request-ID und Start-Zeit setzen
  app.addHook('onRequest', async (request) => {
    request.log.info({
      method: request.method,
      url:    request.url,
      ip:     request.ip
    }, 'incoming request')
  })

  // Response-Timing zum Header hinzufügen
  app.addHook('onSend', async (request, reply, payload) => {
    reply.header('X-Response-Time', `${reply.getResponseTime().toFixed(2)}ms`)
    reply.header('X-Request-Id', request.id)
    return payload // payload IMMER zurückgeben!
  })

  // Access-Log nach der Antwort
  app.addHook('onResponse', async (request, reply) => {
    request.log.info({
      statusCode:   reply.statusCode,
      responseTime: reply.getResponseTime(),
      url:          request.url
    }, 'request completed')
  })

  // Globales Error-Handling
  app.addHook('onError', async (request, reply, error) => {
    if (error.statusCode && error.statusCode < 500) return
    // Unerwartete Fehler an Sentry/Datadog senden
    request.log.error({ err: error, url: request.url }, 'unhandled error')
  })
}

export default fp(observability)
Route-spezifischer preHandler Hook
// preHandler direkt an einer Route (überschreibt nicht globale Hooks)
app.get('/protected', {
  preHandler: async (request, reply) => {
    const apiKey = request.headers['x-api-key']
    if (apiKey !== process.env.INTERNAL_API_KEY) {
      return reply.code(401).send({ error: 'Unauthorized' })
    }
  }
}, async () => ({ status: 'ok' }))
Achtung: onSend Rückgabewert

Im onSend-Hook MUSS das payload-Argument zurückgegeben werden — auch wenn es nicht verändert wurde. Gibt der Hook undefined zurück, sendet Fastify eine leere Antwort.

5. Swagger/OpenAPI — Auto-Dokumentation aus Schema

Da Fastify-Routes sowieso JSONSchema-Definitionen erfordern, entsteht die API-Dokumentation ohne Extra-Aufwand: @fastify/swagger liest die Schemas und generiert daraus vollständige OpenAPI 3.0-Definitionen. @fastify/swagger-ui stellt eine interaktive UI bereit.

src/plugins/swagger.ts — OpenAPI Setup
import fp from 'fastify-plugin'
import swagger from '@fastify/swagger'
import swaggerUi from '@fastify/swagger-ui'
import { FastifyPluginAsync } from 'fastify'

const swaggerPlugin: FastifyPluginAsync = async (app) => {
  await app.register(swagger, {
    openapi: {
      openapi: '3.0.3',
      info: {
        title:       'My Fastify API',
        description: 'Typsichere REST API mit Fastify 2026',
        version:     '1.0.0'
      },
      servers: [
        { url: 'https://api.example.com', description: 'Produktion' },
        { url: 'http://localhost:3000',    description: 'Entwicklung' }
      ],
      tags: [
        { name: 'users',    description: 'User-Management' },
        { name: 'products', description: 'Produkt-Verwaltung' },
        { name: 'admin',    description: 'Admin-Endpunkte (geschützt)' }
      ],
      components: {
        securitySchemes: {
          bearerAuth: {
            type:         'http',
            scheme:       'bearer',
            bearerFormat: 'JWT'
          }
        }
      }
    }
  })

  await app.register(swaggerUi, {
    routePrefix: '/docs',
    uiConfig: {
      docExpansion:   'tag',
      deepLinking:    true,
      tryItOutEnabled: true
    },
    staticCSP: true,
    transformStaticCSP: (header) => header
  })
}

export default fp(swaggerPlugin, { name: 'swagger' })

Tags und Security in Routes

Das schema-Objekt jeder Route kann tags, summary, description und security enthalten — diese Felder fließen direkt in die OpenAPI-Dokumentation ein.

Route mit vollständigen OpenAPI-Metadaten
app.post('/products', {
  schema: {
    tags:        ['products'],
    summary:     'Neues Produkt erstellen',
    description: 'Legt ein neues Produkt an. Erfordert Admin-Rechte.',
    security:    [{ bearerAuth: [] }],
    body: CreateProductBody,     // TypeBox-Schema → automatisch in OpenAPI
    response: {
      201: ProductResponse,
      400: Type.Object({
        statusCode: Type.Integer(),
        error:      Type.String(),
        message:    Type.String()
      })
    }
  },
  preHandler: [app.adminOnly]
}, async (req, reply) => {
  // Handler-Implementierung
  return reply.code(201).send(createdProduct)
})

Nach dem Start ist die Swagger-UI unter /docs erreichbar — mit "Try it out"-Funktion, JWT-Auth-Formular und vollständig generierten Request/Response-Beispielen. Kein manuelles Pflegen von Swagger-YAML mehr.

Claude Code + OpenAPI

Claude Code kann aus einer bestehenden OpenAPI-YAML-Datei vollständige Fastify-Routes generieren — inklusive TypeBox-Schemas, Handler-Stubs und Unit-Tests. Prompt: "Generiere Fastify-Routes aus dieser openapi.yaml".

6. Performance und Serialisierung — Benchmarks vs. Express

Fastifys Performance-Vorsprung kommt aus drei Quellen: fast-json-stringify für schnelle Response-Serialisierung, pino als ultra-schneller JSON-Logger und einem optimierten Router-Algorithmus (Radix-Tree). Mit den richtigen Einstellungen erreichst du in Produktion Throughput, der Express um den Faktor 4–5 übertrifft.

fast-json-stringify — Wie es funktioniert

Wenn ein response-Schema definiert ist, kompiliert Fastify beim Start eine spezialisierte Serialisierungs-Funktion für genau dieses Schema. Zur Laufzeit wird kein allgemeines JSON.stringify() mehr aufgerufen — stattdessen direkt die optimierte Funktion.

Serialisierung manuell nutzen (für Benchmarks/Tests)
import build from 'fast-json-stringify'

const stringify = build({
  type: 'object',
  properties: {
    id:    { type: 'string' },
    name:  { type: 'string' },
    price: { type: 'number' },
    stock: { type: 'integer' }
  }
})

// Bis zu 3x schneller als JSON.stringify für große Arrays
const json = stringify({ id: 'abc', name: 'Widget', price: 9.99, stock: 42 })

pino Logger — Zero-Overhead Logging

Pino-Konfiguration für Produktion
const app = Fastify({
  logger: {
    level: 'info',
    // In Produktion: JSON-Logs direkt an stdout (kein Pretty-Print)
    serializers: {
      req(request) {
        return {
          method: request.method,
          url:    request.url,
          hostname: request.hostname
        }
      }
    },
    // Redact sensible Felder aus Logs
    redact: ['req.headers.authorization', '*.password', '*.token']
  }
})

undici — HTTP-Client für externe APIs

Für ausgehende HTTP-Requests innerhalb der API empfiehlt sich undici — der native Node.js HTTP-Client mit Connection-Pooling und bis zu 5× höherem Throughput als axios.

undici als Fastify-Plugin
import fp from 'fastify-plugin'
import { Pool } from 'undici'

declare module 'fastify' {
  interface FastifyInstance { httpPool: Pool }
}

export default fp(async (app) => {
  const pool = new Pool('https://external-api.example.com', {
    connections: 10,
    pipelining:  4
  })
  app.decorate('httpPool', pool)
  app.addHook('onClose', async () => pool.close())
})

Benchmark-Vergleich 2026

FrameworkReq/s (einfache Route)Latenz p99JSON-Serialisierung
Fastify 5.x77.2001.8msfast-json-stringify
Hono (Bun)62.4002.1msnative
Elysia (Bun)58.0002.3msnative
Express 5.x14.8008.9msJSON.stringify
NestJS (Express)12.30011.2msJSON.stringify
NestJS (Fastify)48.0003.2msfast-json-stringify
Performance-Checkliste

1. Response-Schema für jede Route definieren (aktiviert fast-json-stringify) — 2. logger: false nur in Tests, in Produktion pino nutzen — 3. trustProxy: true hinter Reverse-Proxy (nginx/Caddy) — 4. return reply.send() nicht await reply.send() — 5. undici statt axios für ausgehende Requests.

Production-Ready Fastify in 20 Zeilen
import Fastify from 'fastify'
import { Type } from '@sinclair/typebox'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'

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

app.get('/health', {
  schema: {
    response: {
      200: Type.Object({
        status:  Type.Literal('ok'),
        version: Type.String(),
        uptime:  Type.Number()
      })
    }
  }
}, () => ({
  status: 'ok',
  version: process.env.npm_package_version ?? '1.0.0',
  uptime: process.uptime()
}))

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

Fastify APIs mit Claude Code bauen

Starte deinen kostenlosen Trial — Claude Code scaffoldet vollständige Fastify-Projekte mit TypeScript, Swagger, Auth-Plugins und Tests in Minuten.

Kostenlos testen — kein Kreditkarte nötig