TypeScript mit Fastify: Schema-Validierung und Typinferenz
AI generated
<T>
type
TypeScript · Fastify · JSON Schema · Node.js
TypeScript mit Fastify
Schema-Validierung und automatische Typinferenz

Fastify behandelt JSON Schema nicht als Zusatzfeature, sondern als zentralen Baustein: dasselbe Schema validiert eingehende Requests zur Laufzeit, beschleunigt die Serialisierung der Antwort und liefert mit TypeBox automatisch den passenden TypeScript-Typ, ohne dass Entwickler Typen manuell doppelt pflegen muessen.

17 Min. Lesezeit JSON Schema · TypeBox · Plugins · Hooks Fastify 4/5 · TypeScript 5.x

1. Warum Fastify anders tickt als klassische Node-Frameworks

Fastify wurde von Anfang an mit zwei Zielen entworfen: hoher Durchsatz und strukturierte Validierung ueber JSON Schema. Waehrend Express Validierung komplett dem Entwickler ueberlaesst, ist sie bei Fastify ein eingebauter Bestandteil jeder Routen-Definition. Wer TypeScript mit Fastify kombiniert, bekommt dadurch einen Vorteil, den kein anderes gaengiges Node-Framework in dieser Form bietet: ein einziges Schema liefert gleichzeitig Laufzeit-Validierung, schnellere JSON-Serialisierung und, mit der richtigen Bibliothek, den passenden statischen Typ.

Diese Kombination loest ein Problem, das bei anderen Frameworks haeufig zu Inkonsistenzen fuehrt: Typ und Laufzeitpruefung laufen auseinander, weil sie getrennt gepflegt werden. Bei Fastify gibt es nur eine Quelle der Wahrheit, das Schema selbst, aus dem alles Weitere abgeleitet wird. Dieser Artikel zeigt, wie TypeScript und Fastify von der Routen-Definition bis zu Plugins und Hooks konsequent zusammenspielen.

Der Umstieg von Express auf Fastify lohnt sich besonders bei neuen Projekten, die von Anfang an strukturierte Validierung und hohe Anfragenzahlen im Blick haben. Bestehende Express-Anwendungen migriert man selten komplett, sondern bewertet Fastify meist gezielt fuer neue Services innerhalb einer bestehenden Microservice-Landschaft.

2. JSON Schema als Single Source of Truth

Jede Route in Fastify kann ein Schema-Objekt mit den Schluesseln body, querystring, params, headers und response definieren. Das Schema wird beim Serverstart einmalig zu einer hocheffizienten Validierungsfunktion kompiliert, was deutlich schneller ist als eine Validierung, die bei jedem einzelnen Request neu interpretiert werden muss. Fuer TypeScript mit Fastify ist entscheidend: das response-Schema filtert automatisch alle Felder heraus, die nicht im Schema stehen, was versehentliches Daten-Leaking verhindert, etwa wenn ein internes Passwort-Hash-Feld nie im Schema auftaucht und deshalb nie in der Antwort landet.

Reines JSON Schema als Plain-Object zu schreiben funktioniert, liefert aber keine automatische TypeScript-Typinferenz. Genau hier setzt der naechste Abschnitt an: Mit TypeBox lassen sich JSON-Schema-kompatible Objekte direkt in TypeScript deklarieren, sodass Schema und Typ nicht mehr getrennt gepflegt werden muessen.


// plain JSON Schema route — validates but has no automatic TS type
fastify.get("/health", {
  schema: {
    response: {
      200: {
        type: "object",
        properties: {
          status: { type: "string" },
          uptime: { type: "number" },
        },
        required: ["status", "uptime"],
      },
    },
  },
}, async (request, reply) => {
  return { status: "ok", uptime: process.uptime() };
});

3. Automatische Typinferenz mit TypeBox

TypeBox erzeugt JSON-Schema-konforme Objekte direkt aus TypeScript-Funktionsaufrufen und leitet gleichzeitig einen statischen Typ mit Static<typeof Schema> ab. In Kombination mit dem Plugin @fastify/type-provider-typebox wird der Fastify-Instanz-Typ so erweitert, dass request.body, request.params und request.query automatisch den korrekten, aus dem Schema abgeleiteten Typ bekommen, ohne eine einzige zusaetzliche Typ-Annotation im Handler.

Das Ergebnis: Aendert sich das Schema, aendert sich automatisch auch der Typ im Handler, der Compiler meldet sofort, wenn ein Feld umbenannt oder entfernt wurde und noch an anderer Stelle verwendet wird. Diese enge Kopplung zwischen Laufzeit-Schema und statischem Typ ist der zentrale Unterschied zwischen Fastify und Frameworks, bei denen Validierung und Typisierung zwei getrennte, manuell synchron zu haltende Schritte sind.


// typebox-routes.ts — schema and type from a single source
import { Type, Static } from "@sinclair/typebox";
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
import Fastify from "fastify";

const CreateItemBody = Type.Object({
  name: Type.String({ minLength: 2, maxLength: 120 }),
  price: Type.Number({ minimum: 0 }),
  tags: Type.Optional(Type.Array(Type.String())),
});

// Static type is derived automatically, never written by hand
type CreateItemBodyType = Static<typeof CreateItemBody>;

const ItemResponse = Type.Object({
  id: Type.String(),
  name: Type.String(),
  price: Type.Number(),
});

const app = Fastify().withTypeProvider<TypeBoxTypeProvider>();

app.post("/items", {
  schema: {
    body: CreateItemBody,
    response: { 201: ItemResponse },
  },
}, async (request, reply) => {
  // request.body is fully typed as CreateItemBodyType — no manual cast
  const { name, price } = request.body;
  reply.code(201);
  return { id: "item_1", name, price };
});

4. Plugins und Encapsulation typsicher registrieren

Fastify strukturiert Anwendungen ueber ein Plugin-System mit Encapsulation-Kontext: jedes Plugin bekommt eine eigene Instanz der Fastify-Anwendung, Dekorationen und Hooks eines Plugins sind standardmaessig nicht ausserhalb sichtbar, ausser man registriert sie explizit als global. Fuer TypeScript mit Fastify ist wichtig, Plugins mit fastify-plugin zu kennzeichnen, wenn ihre Dekorationen tatsaechlich ueber die Kapselungsgrenze hinaus verfuegbar sein sollen, sonst meldet der Compiler an anderer Stelle einen fehlenden Dekorator.

Die Typisierung eines Plugins erfolgt ueber FastifyPluginAsync mit einem optionalen generischen Parameter fuer Plugin-Optionen. Diese Optionen werden beim Registrieren typgeprueft, sodass ein falsch benannter oder fehlender Konfigurationsschluessel schon beim Kompilieren auffaellt, statt erst zur Laufzeit einen unklaren Fehler zu produzieren.


// db-plugin.ts — typed Fastify plugin with typed options
import fp from "fastify-plugin";
import type { FastifyPluginAsync } from "fastify";

interface DbPluginOptions {
  connectionString: string;
  poolSize?: number;
}

declare module "fastify" {
  interface FastifyInstance {
    db: { query: (sql: string) => Promise<unknown> };
  }
}

const dbPlugin: FastifyPluginAsync<DbPluginOptions> = async (
  fastify,
  opts
) => {
  const pool = createPool(opts.connectionString, opts.poolSize ?? 10);
  fastify.decorate("db", {
    query: (sql: string) => pool.query(sql),
  });

  fastify.addHook("onClose", async () => {
    await pool.end();
  });
};

// fp() lifts the plugin out of its own encapsulation context
export default fp(dbPlugin, { name: "db-plugin" });

5. Typsichere Hooks: onRequest und preHandler

Fastify definiert eine feste Reihenfolge an Lifecycle-Hooks: onRequest, preParsing, preValidation, preHandler und weitere nach dem Handler. Jeder Hook-Typ bekommt in TypeScript eine eigene Signatur, die sich leicht von generischer Middleware unterscheidet, wie sie etwa aus Express bekannt ist. Ein preHandler-Hook eignet sich fuer Aufgaben, die nach der Validierung, aber vor dem eigentlichen Route-Handler laufen sollen, etwa das Nachladen zusaetzlicher Daten basierend auf bereits validierten Request-Feldern.

Ein haeufiger Fehler beim Einstieg in Fastify: Ein Hook wird global mit fastify.addHook registriert, obwohl er nur fuer eine bestimmte Route gelten soll. Da globale Hooks alle Routen der aktuellen Encapsulation-Ebene betreffen, kann ein zu weit gefasster Hook unerwartete Seiteneffekte in völlig unabhaengigen Routen erzeugen. Die korrekte Alternative ist ein routenspezifischer Hook direkt in der Schema-Definition der einzelnen Route.

6. Fehlerbehandlung mit typisierten Fastify-Errors

Fastify liefert bei Schema-Validierungsfehlern automatisch eine strukturierte Fehlerantwort mit Statuscode 400, ohne dass der Entwickler dafuer Code schreiben muss. Fuer eigene Fehlerklassen bietet @fastify/error eine Fabrikfunktion, die typisierte Fehlerklassen mit fester Statuscode-Zuordnung erzeugt. Diese Fehler lassen sich zentral ueber setErrorHandler abfangen, dessen Typ-Signatur den Fehler, den Request und die Reply typsicher entgegennimmt.

Wichtig fuer TypeScript mit Fastify ist, den Fehlerhandler so zu gestalten, dass er zwischen Validierungsfehlern des Schemas, eigenen Fastify-Errors und unerwarteten Laufzeitfehlern unterscheidet. Ein zentraler instanceof-Check pro Fehlerklasse verhindert, dass interne Stacktraces versehentlich an den Client zurueckgegeben werden, waehrend erwartete Fehler weiterhin klare, strukturierte Antworten liefern.


// errors.ts — typed error factory with @fastify/error
import createError from "@fastify/error";

export const ItemNotFoundError = createError<[]>(
  "ITEM_NOT_FOUND",
  "Item not found",
  404
);

export const RateLimitedError = createError<[number]>(
  "RATE_LIMITED",
  "Too many requests, retry after %d seconds",
  429
);

// route.ts
app.get("/items/:id", async (request, reply) => {
  const item = await itemService.findById(request.params.id);
  if (!item) {
    throw new ItemNotFoundError();
  }
  return item;
});

7. Performance: Serialisierung und Schema-Compile

Der Grund fuer den hohen Durchsatz von Fastify liegt nicht nur in der Architektur, sondern konkret in der Serialisierung: Das response-Schema wird zu einer spezialisierten Serialisierungsfunktion kompiliert, die deutlich schneller ist als das generische JSON.stringify. Diese Funktion muss nicht raten, welche Felder im Objekt existieren, sie kennt die Struktur bereits aus dem Schema und kann den JSON-String direkt in der richtigen Reihenfolge zusammensetzen.

Fuer TypeScript-Projekte bedeutet das: Ein sorgfaeltig gepflegtes Response-Schema ist nicht nur ein Sicherheits-, sondern auch ein Performance-Feature. Fehlt das Schema, faellt Fastify auf das langsamere JSON.stringify zurueck, ohne dass dies im Code sofort sichtbar waere. Ein regelmaessiger Check, ob alle produktiven Routen ein Response-Schema definieren, gehoert deshalb in jede Code-Review-Checkliste fuer Fastify-Projekte.

8. Decorators typsicher erweitern

Fastify erlaubt es, die Fastify-Instanz, den Request oder die Reply mit eigenen Eigenschaften zu dekorieren, etwa fuer einen Datenbank-Client oder einen authentifizierten Benutzer. Damit TypeScript diese Dekorationen kennt, muss das jeweilige Interface per Modul-Deklaration erweitert werden, wie im Plugin-Beispiel oben gezeigt. Ohne diese Erweiterung meldet der Compiler bei jedem Zugriff auf die Dekoration einen Fehler, selbst wenn decorate() zur Laufzeit korrekt aufgerufen wurde.

Ein wichtiger Unterschied zu einfachen globalen Variablen: Fastify prueft zur Laufzeit, ob ein Dekorationsname bereits vergeben ist, und wirft in diesem Fall einen Fehler beim Serverstart. Diese Kombination aus Laufzeitpruefung und statischer Typisierung reduziert das Risiko widerspruechlicher Dekorationen erheblich, besonders in groesseren Codebasen mit mehreren unabhaengig entwickelten Plugins.

Kriterium Fastify Express NestJS
Validierung eingebaut Ja, ueber JSON Schema Nein, nur via Middleware Ja, ueber Pipes/class-validator
Serialisierungs-Performance Sehr hoch, kompiliertes Schema Standard JSON.stringify Standard JSON.stringify
Architektur-Vorgabe Plugins, flexibel Keine, frei waehlbar Module, Decorators, DI-Pflicht
Einstiegshuerde Mittel (Schema-Konzept) Niedrig Hoch (Decorators, Module)

9. Fastify im Vergleich zu Express und NestJS

Im direkten Vergleich zeigt sich, dass Fastify genau in der Mitte zwischen der Flexibilitaet von Express und der starken Architektur-Vorgabe von NestJS liegt. Wer maximale Kontrolle ueber die Anwendungsstruktur haben moechte, aber trotzdem eingebaute Schema-Validierung und hohe Performance benoetigt, findet in Fastify einen guten Mittelweg. NestJS erzwingt eine bestimmte Architektur ueber Module und Decorators, was in grossen Teams fuer Konsistenz sorgt, aber eine steilere Lernkurve mit sich bringt.

Fuer TypeScript-Teams, die bereits Erfahrung mit JSON Schema oder OpenAPI haben, ist der Umstieg auf Fastify meist unkompliziert, weil sich bestehende Schemas direkt wiederverwenden lassen. Teams ohne diese Erfahrung sollten den zusaetzlichen Lernaufwand fuer TypeBox oder aehnliche Bibliotheken einplanen, bevor sie sich fuer Fastify als Standardframework entscheiden.

Mironsoft

TypeScript-Backends, API-Architektur und Performance-Optimierung

Fastify-Backend mit sauberer Schema-Struktur?

Wir bauen Fastify-Services mit TypeBox-Schemas, typsicheren Plugins und optimierten Response-Schemas fuer maximalen Durchsatz und minimale Fehlerquote im Betrieb.

Schema-Design

TypeBox-Schemas fuer Body, Response und Query aus einer Quelle

Plugin-Architektur

Saubere Encapsulation und typisierte Decorators fuer wachsende Teams

Performance-Audit

Response-Schemas pruefen und Serialisierungs-Engpaesse beseitigen

10. Zusammenfassung

TypeScript mit Fastify unterscheidet sich fundamental vom Ansatz anderer Node-Frameworks, weil JSON Schema hier nicht nachtraeglich ergaenzt, sondern von Anfang an als zentraler Baustein mitgedacht wird. Ein Schema validiert eingehende Daten, beschleunigt die Serialisierung der Antwort und liefert, kombiniert mit TypeBox, automatisch den passenden statischen Typ. Plugins kapseln Funktionalitaet ueber einen eigenen Encapsulation-Kontext, waehrend Decorators und Hooks typsicher ueber Modul-Deklarationen erweitert werden.

Der groesste Vorteil zeigt sich in der Konsistenz: Schema und Typ koennen nie auseinanderlaufen, weil beide aus derselben Quelle stammen. Fuer Teams, die bereits mit OpenAPI oder JSON Schema arbeiten, ist der Umstieg auf Fastify naheliegend. Fuer Teams ohne diese Vorerfahrung lohnt sich die anfaengliche Lernkurve, weil sie langfristig weniger Inkonsistenzen zwischen Validierung und Typisierung produziert als ein manuell gepflegter Stack aus Express und separaten Validierungsbibliotheken.

TypeScript mit Fastify: Das Wichtigste auf einen Blick

JSON Schema als Quelle

Ein Schema fuer Body, Query, Params und Response liefert Validierung und Serialisierung gleichzeitig.

TypeBox-Typinferenz

Static<typeof Schema> leitet den TypeScript-Typ automatisch aus dem Schema ab, ohne Duplikation.

Plugins & Encapsulation

fastify-plugin fuer geteilte Dekorationen, sonst bleiben Hooks und Decorators lokal gekapselt.

Performance

Kompiliertes Response-Schema schlaegt generisches JSON.stringify deutlich in Durchsatz-Tests.

11. FAQ: TypeScript mit Fastify

1Warum JSON Schema statt separater Validierung?
Wird beim Start kompiliert, ist schneller und dient gleichzeitig der Serialisierung der Antwort.
2Was bringt TypeBox?
Leitet automatisch den TypeScript-Typ aus dem Schema ab, Schema und Typ laufen nie auseinander.
3Wie filtert das Response-Schema Felder?
Nur im Schema deklarierte Felder landen in der JSON-Antwort, andere werden automatisch entfernt.
4Globaler vs. routenspezifischer Hook?
Globaler Hook betrifft die ganze Encapsulation-Ebene, routenspezifischer nur eine einzelne Route.
5Wozu dient fastify-plugin?
Hebt ein Plugin aus der eigenen Kapselung heraus, damit Dekorationen ueberall verfuegbar sind.
6Wie erweitere ich FastifyInstance typsicher?
Ueber Modul-Deklaration des Interfaces kombiniert mit decorate() zur Laufzeit.
7Werden Validierungsfehler automatisch behandelt?
Ja, Fastify antwortet automatisch mit 400 und strukturierter Fehlermeldung.
8Wie erstelle ich eigene Fehlerklassen?
Mit createError aus @fastify/error, abgefangen ueber einen zentralen setErrorHandler.
9Ist Fastify schneller als Express?
Meist ja, besonders bei komplexen Antwortobjekten dank kompilierter Serialisierung.
10Lohnt sich Fastify fuer bestehende Express-Projekte?
Volle Migration selten sinnvoll, eher fuer neue Services innerhalb bestehender Landschaften.