Claude Code Fastify: Blitzschnelle Node.js-APIs, Plugins und Schema-Validierung mit KI
Express hat die Node.js-Welt geprägt — und gleichzeitig viele schlechte Gewohnheiten kultiviert: keine eingebaute Validierung, kein Typsystem, kein strukturiertes Plugin-System. Fastify ist die Antwort darauf: bis zu zehnmal schneller als Express, mit JSON-Schema-Validierung ab Werk, einem durchdachten Plugin-System und nativer TypeScript-Unterstützung.
Claude Code und Fastify ergänzen sich dabei besonders gut. Fastify ist explizit und strukturiert — genau das, womit KI-gestützte Entwicklung am besten funktioniert. Schema-Definitionen, Plugin-Registrierungen, Hook-Ketten: Claude Code liest und schreibt diese Strukturen präzise, erklärt Zusammenhänge und ergänzt fehlende Teile kohärent. Dieser Artikel zeigt, wie das in der Praxis aussieht.
Claude Code Mastery — APIs, Agents und Workflows auf Deutsch
Node.js, Fastify, Datenbanken, Deployment — der Kurs zeigt wie du Claude Code produktiv einsetzt. Einmalig bezahlt, kein Abo.
Zum Kurs — Jetzt starten → ab €29 Basis / €49 Pro · Kein Abo · 14 Tage Rückgaberecht1. Fastify vs. Express: Warum der Unterschied zählt
Bevor der Code kommt, ein kurzer Blick auf die Zahlen. In Benchmarks verarbeitet Fastify auf derselben Hardware oft 70.000–80.000 Requests pro Sekunde, während Express bei 15.000–20.000 liegt. Das liegt nicht an Magie, sondern an zwei konkreten Designentscheidungen: JSON-Serialisierung über fast-json-stringify (schema-basiert, ohne dynamische Introspektion) und einem auf Leistung optimierten Routing-Algorithmus via find-my-way.
Für viele Projekte ist dieser Geschwindigkeitsunterschied sekundär. Wichtiger ist, was Fastify strukturell erzwingt: Schemas für jeden Route-Handler. Express erlaubt dir, beliebige Objekte zurückzugeben und beliebige Bodys zu akzeptieren — Fastify verlangt, dass du sagst, was du erwartest und was du zurückgibst. Das klingt nach mehr Arbeit, ist aber weniger: Validierung, Typsicherheit und OpenAPI-Dokumentation entstehen als Nebenprodukt, ohne Extra-Aufwand.
Claude Code hilft dabei, diese Schemas schnell zu schreiben und konsistent zu halten. Anstatt manuell JSON-Schema-Definitionen zu tippen, beschreibst du dein Datenmodell und Claude Code generiert das passende Schema — inklusive TypeBox-Integration für vollständige TypeScript-Typsicherheit.
Wann Fastify, wann Express? Fastify lohnt sich, sobald du Typsicherheit, Performance oder strukturierte Dokumentation brauchst — also fast immer bei neuen Projekten. Express bleibt sinnvoll, wenn du ein bestehendes Ecosystem mit vielen Express-Middlewares weiternutzt oder ein sehr kleines Proof-of-Concept baust, bei dem Boilerplate nervt.
2. Projekt-Setup mit Claude Code
Ein neues Fastify-Projekt aufsetzen — komplett mit TypeScript, TypeBox und den gängigsten Plugins — dauert mit Claude Code wenige Minuten. Du beschreibst, was du brauchst, und Claude Code erzeugt die Struktur, erklärt sie und passt sie auf Rückfragen an.
claude "Erstelle ein Fastify-Projekt mit TypeScript, TypeBox-Integration,
@fastify/jwt für Authentifizierung und @fastify/swagger für OpenAPI.
Projektname: user-api. Zeige die vollständige Verzeichnisstruktur und
die wichtigsten Konfigurationsdateien."
Claude Code erzeugt dann eine sinnvolle Struktur: src/plugins/ für Fastify-Plugins, src/routes/ für Route-Handler, src/schemas/ für TypeBox-Definitionen und eine src/server.ts als Einstiegspunkt. Was Claude Code nicht tut: willkürlich entscheiden, ohne es zu erklären. Jede Datei kommt mit einer kurzen Begründung, warum sie so strukturiert ist — das ist wichtig für Teams, die den Code später warten müssen.
// src/server.ts — generiert von Claude Code
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>()
// Plugins registrieren
await app.register(import('./plugins/jwt'))
await app.register(import('./plugins/swagger'))
// Routen registrieren
await app.register(import('./routes/users'), { prefix: '/api/v1' })
await app.listen({ port: 3000, host: '0.0.0.0' })
export { app }
3. Das Plugin-System: Fastifys stärkste Seite
Was Fastify von Express grundlegend unterscheidet, ist das enkapsulierte Plugin-System. Jedes Plugin lebt in einer isolierten Instanz — Decorators, Hooks und Routen, die ein Plugin registriert, sind standardmäßig nur innerhalb dieses Plugins sichtbar. Das verhindert die häufigste Express-Krankheit: globale Middlewares die sich gegenseitig stören.
Claude Code versteht dieses System präzise. Wenn du fragst, wie man einen Datenbankzugang kapselt, der in allen Routen verfügbar sein soll, bekommst du kein einfaches app.use()-Äquivalent, sondern die korrekte Fastify-Lösung: ein Plugin mit fastify-plugin um die Enkapsulierung aufzuheben, damit der Decorator in der gesamten Anwendung sichtbar ist.
// src/plugins/database.ts
import fp from 'fastify-plugin'
import { FastifyPluginAsync } from 'fastify'
const databasePlugin: FastifyPluginAsync = async (fastify) => {
// Datenbankverbindung aufbauen
const db = await createDatabaseConnection(process.env.DATABASE_URL)
// Decorator macht db in allen Routen verfügbar
fastify.decorate('db', db)
// Cleanup beim Herunterfahren
fastify.addHook('onClose', async () => {
await db.close()
})
}
// fp() hebt die Enkapsulierung auf — db ist global verfügbar
export default fp(databasePlugin, { name: 'database' })
Claude Code erklärt dabei den Unterschied zwischen einem Plugin mit und ohne fp(): ohne fp() ist der Decorator nur innerhalb des Plugin-Scopes sichtbar — sinnvoll für feature-spezifische Plugins. Mit fp() wird er global — sinnvoll für Infrastruktur wie Datenbankzugänge oder Caches.
Plugins testen mit Claude Code
Ein Plugin zu schreiben ist eine Sache, es zu testen eine andere. Claude Code generiert direkt passende Tests — Fastify hat dafür fastify.inject(), das HTTP-Requests simuliert ohne einen echten Server zu starten:
// tests/database.test.ts
import { test } from 'tap'
import Fastify from 'fastify'
import databasePlugin from '../src/plugins/database'
test('database plugin', async (t) => {
const app = Fastify()
await app.register(databasePlugin)
await app.ready()
t.ok(app.db, 'db decorator ist verfügbar')
t.teardown(() => app.close())
})
4. JSON-Schema-Validierung und TypeBox
Fastifys Validierung basiert auf JSON Schema — einem Standard, der sowohl für Eingabe-Validierung als auch für OpenAPI-Dokumentation genutzt wird. Das Problem: JSON Schema von Hand zu schreiben ist fehleranfällig und nicht typsicher. TypeBox löst das: du definierst dein Schema einmal als TypeScript-Code und bekommst gleichzeitig den JSON-Schema-Output für Fastify und die TypeScript-Typen für deinen Code.
// src/schemas/user.ts
import { Type, Static } from '@sinclair/typebox'
export const UserSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
email: Type.String({ format: 'email' }),
name: Type.String({ minLength: 2, maxLength: 100 }),
role: Type.Union([Type.Literal('admin'), Type.Literal('user')]),
createdAt: Type.String({ format: 'date-time' })
})
export const CreateUserSchema = Type.Object({
email: Type.String({ format: 'email' }),
name: Type.String({ minLength: 2, maxLength: 100 }),
role: Type.Optional(Type.Union([Type.Literal('admin'), Type.Literal('user')]))
})
// TypeScript-Typen aus dem Schema ableiten
export type User = Static<typeof UserSchema>
export type CreateUser = Static<typeof CreateUserSchema>
Claude Code generiert solche TypeBox-Definitionen aus natürlichsprachlichen Beschreibungen. Du beschreibst dein Datenmodell, Claude Code schreibt das Schema — korrekt, vollständig, mit sinnvollen Constraints wie Längen-Limits und Format-Validierungen. Wenn das Datenmodell sich ändert, passt Claude Code das Schema und alle abhängigen Route-Handler konsistent an.
Schema in Route-Handlern einsetzen
// src/routes/users.ts
import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
import { CreateUserSchema, UserSchema } from '../schemas/user'
import { Type } from '@sinclair/typebox'
const usersRoute: FastifyPluginAsyncTypebox = async (fastify) => {
fastify.post('/users', {
schema: {
body: CreateUserSchema,
response: {
201: UserSchema,
400: Type.Object({
error: Type.String(),
message: Type.String()
})
}
}
}, async (request, reply) => {
// request.body ist vollständig typisiert als CreateUser
const user = await fastify.db.createUser(request.body)
return reply.code(201).send(user)
})
}
Beachte: Fastify führt die Validierung automatisch durch. Ein Request mit einem ungültigen Body — fehlende Felder, falsches Format, Längenverletzung — wird mit einem 400-Fehler abgewiesen, bevor dein Handler-Code überhaupt ausgeführt wird. Das spart Zeilen und verhindert Fehler.
5. Hooks: onRequest, preHandler und onSend
Fastify-Hooks sind der Ersatz für Express-Middleware — aber strukturierter und leistungsfähiger. Jeder Hook hat einen klar definierten Zeitpunkt im Request-Lifecycle:
- onRequest — sofort nach Eingang des Requests, vor dem Parsen des Bodys. Sinnvoll für IP-Filtering oder frühe Ablehnung.
- preValidation — vor der Schema-Validierung. Selten gebraucht, aber nützlich um den Body vor der Validierung zu transformieren.
- preHandler — nach der Validierung, vor dem Handler. Der häufigste Ort für Authentifizierung und Autorisierung.
- onSend — bevor die Response gesendet wird. Nützlich für Response-Transformation, Header-Manipulation oder Logging.
- onResponse — nach dem Senden. Für Metriken und Cleanup.
Claude Code kennt diesen Lifecycle und platziert Hooks an der richtigen Stelle. Wenn du fragst, wie man Authentifizierung umsetzt, schlägt Claude Code keinen globalen onRequest-Hook vor — sondern einen preHandler-Hook, der gezielt für geschützte Routen gilt, oder die Nutzung des eingebauten authenticate-Decorators von @fastify/jwt:
// Globaler onRequest-Hook für Request-Logging
fastify.addHook('onRequest', async (request, reply) => {
request.log.info({ method: request.method, url: request.url }, 'Eingehender Request')
})
// onSend-Hook für Response-Header
fastify.addHook('onSend', async (request, reply, payload) => {
reply.header('X-Response-Time', `${Date.now() - request.startTime}ms`)
return payload
})
// preHandler nur für eine spezifische Route
fastify.get('/protected', {
preHandler: [fastify.authenticate]
}, async (request, reply) => {
return { user: request.user }
})
6. JWT-Authentifizierung mit @fastify/jwt
JWT-Authentifizierung in Fastify ist mit dem offiziellen @fastify/jwt-Plugin unkompliziert — und Claude Code erklärt dabei einen häufigen Sicherheitsfehler: den JWT-Secret nicht zu rotieren und ihn direkt im Code zu hinterlegen. Die richtige Variante liest den Secret aus der Umgebung:
// src/plugins/jwt.ts
import fp from 'fastify-plugin'
import fastifyJwt from '@fastify/jwt'
export default fp(async (fastify) => {
fastify.register(fastifyJwt, {
secret: process.env.JWT_SECRET
})
// Decorator für einfache Nutzung in Route-Handlern
fastify.decorate('authenticate', async (request, reply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
})
// Login-Route
fastify.post('/auth/login', {
schema: {
body: Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 })
})
}
}, async (request, reply) => {
const { email, password } = request.body
const user = await fastify.db.findUserByEmail(email)
if (!user || !await verifyPassword(password, user.passwordHash)) {
return reply.code(401).send({ error: 'Ungültige Anmeldedaten' })
}
const token = fastify.jwt.sign(
{ id: user.id, email: user.email, role: user.role },
{ expiresIn: '24h' }
)
return { token }
})
Achtung bei JWT-Secrets: Verwende immer process.env.JWT_SECRET — nie einen hartcodierten String im Code. Ein schwacher oder kompromittierter Secret macht alle ausgestellten Tokens unsicher. Mindestlänge: 32 zufällige Bytes, base64-kodiert.
7. Rate Limiting mit @fastify/rate-limit
Rate Limiting ist eine der Maßnahmen, die Entwickler regelmäßig vergessen — bis die erste DDoS-Welle kommt oder ein Scraper die API lähmt. Mit @fastify/rate-limit ist das in Fastify ein Plugin-Aufruf:
// src/plugins/rate-limit.ts
import fp from 'fastify-plugin'
import rateLimit from '@fastify/rate-limit'
export default fp(async (fastify) => {
await fastify.register(rateLimit, {
// Global: max 100 Requests pro Minute pro IP
max: 100,
timeWindow: '1 minute',
// Fehlerformat anpassen
errorResponseBuilder: (request, context) => ({
error: 'Too Many Requests',
message: `Rate Limit: ${context.max} Requests pro ${context.after}`,
retryAfter: context.after
})
})
})
Spezifische Routen können striktere Limits haben. Login-Endpunkte sollten zum Beispiel auf 5 Versuche pro Minute begrenzt sein, um Brute-Force zu verhindern:
fastify.post('/auth/login', {
config: {
rateLimit: {
max: 5,
timeWindow: '1 minute'
}
},
schema: { /* ... */ }
}, async (request, reply) => {
// Handler-Code
})
Claude Code schlägt solche route-spezifischen Limits proaktiv vor, wenn es eine Login- oder Passwort-Route sieht. Das ist der praktische Unterschied: nicht nur Code generieren, sondern Security-Patterns einhalten, die in der Praxis oft vergessen werden.
8. Swagger / OpenAPI mit @fastify/swagger
Wer Fastify-Schemas konsequent schreibt, bekommt OpenAPI-Dokumentation fast gratis. @fastify/swagger liest die Schema-Definitionen und erzeugt daraus eine vollständige API-Spezifikation. @fastify/swagger-ui stellt sie interaktiv bereit:
// src/plugins/swagger.ts
import fp from 'fastify-plugin'
import swagger from '@fastify/swagger'
import swaggerUi from '@fastify/swagger-ui'
export default fp(async (fastify) => {
await fastify.register(swagger, {
openapi: {
info: {
title: 'User API',
description: 'Benutzerverwaltung mit Fastify',
version: '1.0.0'
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
}
})
await fastify.register(swaggerUi, {
routePrefix: '/docs',
uiConfig: { docExpansion: 'list' }
})
})
Nach dem Start ist die API-Dokumentation unter /docs erreichbar — vollständig, interaktiv und immer aktuell, weil sie direkt aus den Schemas generiert wird. Keine manuelle Dokumentationspflege mehr.
Claude Code ergänzt dabei Route-spezifische Metadaten für bessere Dokumentation:
fastify.get('/users/:id', {
schema: {
description: 'Einzelnen Benutzer abrufen',
tags: ['users'],
params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
response: { 200: UserSchema, 404: ErrorSchema },
security: [{ bearerAuth: [] }]
}
}, async (request, reply) => {
// Handler
})
9. Testing: Tap und Jest im Vergleich
Fastify empfiehlt offiziell Tap für Tests — es ist in der Fastify-Dokumentation durchgängig verwendet und integriert sich gut in den Node.js-Ökosystem. Viele Teams nutzen aber bereits Jest und möchten nicht wechseln. Beide Ansätze funktionieren, Claude Code kennt beide:
Tap (empfohlen für neue Projekte)
import { test } from 'tap'
import { build } from '../src/app'
test('GET /api/v1/users/:id', async (t) => {
const app = await build()
t.teardown(() => app.close())
// Authentifizierungstoken generieren
const token = app.jwt.sign({ id: 'test-user', role: 'admin' })
const response = await app.inject({
method: 'GET',
url: '/api/v1/users/123e4567-e89b-12d3-a456-426614174000',
headers: { authorization: `Bearer ${token}` }
})
t.equal(response.statusCode, 200)
t.matchSnapshot(response.json())
})
Jest (für bestehende Jest-Projekte)
import { build } from '../src/app'
describe('User Routes', () => {
let app
beforeAll(async () => {
app = await build({ logger: false })
await app.ready()
})
afterAll(() => app.close())
it('erstellt einen neuen Benutzer', async () => {
const response = await app.inject({
method: 'POST',
url: '/api/v1/users',
payload: {
email: 'test@example.com',
name: 'Test User'
}
})
expect(response.statusCode).toBe(201)
expect(response.json()).toMatchObject({
email: 'test@example.com',
name: 'Test User'
})
})
})
Wichtig: app.inject() ist in beiden Fällen der Schlüssel. Es simuliert HTTP-Requests ohne echten Server — Tests laufen schneller, sind deterministischer und brauchen keine freien Ports. Claude Code nutzt inject() standardmäßig und erklärt auf Nachfrage, warum direktes Testen über supertest gegen den laufenden Server langsamer und fehleranfälliger ist.
10. Ein vollständiger Workflow mit Claude Code
Wie sieht ein typischer Entwicklungsablauf aus, wenn du Claude Code Fastify-Projekte bauen lässt? Hier ein konkretes Beispiel: eine neue Route für Produktverwaltung.
Schritt 1: Datenmodell beschreiben
claude "Erstelle ein TypeBox-Schema für ein Produkt: id (UUID), name
(2-200 Zeichen), price (Dezimalzahl, min 0), category (string),
inStock (boolean), tags (Array von Strings, optional)."
Schritt 2: CRUD-Routen generieren
claude "Erstelle vollständige CRUD-Routen für das Produkt-Schema:
GET /products (mit Pagination), GET /products/:id, POST /products
(nur Admin), PATCH /products/:id (nur Admin), DELETE /products/:id
(nur Admin). Nutze den bestehenden authenticate-Decorator."
Schritt 3: Tests schreiben lassen
claude "Schreibe Tap-Tests für alle Produkt-Routen. Decke ab:
Happy Path, Validierungsfehler, fehlende Authentifizierung,
fehlende Admin-Rechte, nicht gefundene Ressource."
Das Ergebnis: vollständiger, typsicherer, getesteter Code — in einem Bruchteil der Zeit, die manuelle Implementierung brauchen würde. Claude Code erfindet dabei keine Patterns: es folgt den Fastify-Konventionen, die es aus tausenden Open-Source-Projekten gelernt hat.
Tipp für große Projekte: Bitte Claude Code, die Routing-Struktur zuerst zu planen, bevor es Code schreibt. Ein routes/index.ts der alle Sub-Router registriert, gepaart mit einer klaren Namenskonvention, macht die Codebasis wartbarer als viele kleine unzusammenhängende Dateien.
11. Decorators: Fastifys Dependency-Injection
Decorators sind das Mechanismus, mit dem Fastify Dienste und Utilities zwischen Plugins und Routen teilt. fastify.decorate() hängt eine neue Eigenschaft an die Fastify-Instanz — typsicher, kapselbar und durch das Plugin-System kontrolliert sichtbar.
// Einen Cache-Service als Decorator registrieren
fastify.decorate('cache', {
get: async (key: string) => redis.get(key),
set: async (key: string, value: string, ttl = 3600) =>
redis.setex(key, ttl, value),
del: async (key: string) => redis.del(key)
})
// TypeScript: Decorator-Typ erweitern
declare module 'fastify' {
interface FastifyInstance {
cache: {
get(key: string): Promise<string | null>
set(key: string, value: string, ttl?: number): Promise<void>
del(key: string): Promise<void>
}
}
}
Claude Code schreibt solche TypeScript-Erweiterungen automatisch mit, wenn es einen Decorator generiert. Das ist wichtig: ohne die declare module-Erweiterung weiß TypeScript nichts vom Decorator und wirft Typfehler.
12. Performance-Monitoring und Production-Bereitschaft
Fastifys eingebauter Logger (basierend auf pino) ist deutlich performanter als console.log — structured JSON-Logging ohne Overhead. In Production-Konfigurationen lässt sich der Log-Level über Umgebungsvariablen steuern:
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' } // Lesbar in Development
: undefined // JSON in Production
}
})
Für Production-Deployments generiert Claude Code auch einen Graceful-Shutdown-Handler — einer der häufigsten vergessenen Aspekte bei Node.js-APIs:
const signals = ['SIGTERM', 'SIGINT']
signals.forEach((signal) => {
process.on(signal, async () => {
app.log.info(`${signal} empfangen, fahre herunter...`)
await app.close()
process.exit(0)
})
})
Diese Vollständigkeit — nicht nur den Happy Path zu implementieren, sondern auch Shutdown, Error-Handling und Logging — ist einer der praktischen Vorteile von Claude Code gegenüber einfachen Code-Generatoren: es kennt Production-Patterns und wendet sie an, ohne dass du explizit danach fragen musst.
Zwei verwandte Artikel die auf diesem Thema aufbauen:
- Claude Code Debugging — Stack Traces übergeben, Logs analysieren, Root-Cause finden
- Claude Code für Python-Projekte — wie du Claude Code in Python-Workflows einsetzt
Claude Code Mastery — von der ersten API bis zum produktiven Agenten
Fastify ist ein Kapitel. Der Kurs zeigt den ganzen Weg: Datenbanken, Deployment, Agents, MCP-Server, Multi-Agent-Workflows — vollständig auf Deutsch, einmalig bezahlt.
Jetzt starten → ab €29 Basis / €49 Pro · Kein Abo · 14 Tage RückgaberechtKurs · Claude Code Mastery
Node.js APIs, Agents und Workflows mit KI
Fastify. Datenbanken. Deployment. Agents. MCP. Alles auf Deutsch — einmalig bezahlt, kein Abo, keine Plattformabhängigkeit.
ab €29 Basis / €49 Pro · Kein Abo · 14 Tage Rückgaberecht