Die moderne API-Schicht: GraphQL, REST und tRPC
Die Debatte ist vorbei. Sie benötigen Typsicherheit. So entwerfen Sie eine skalierbare API-Ebene, die das Frontend nicht unterbricht, wenn das Backend nicht mehr funktioniert.
Warum Maison Code darüber spricht
Bei Maison Code Paris fungieren wir als das architektonische Gewissen unserer Kunden. Wir übernehmen oft „moderne“ Stacks, die ohne grundlegendes Verständnis für Skalierung gebaut wurden.
Wir diskutieren dieses Thema, weil es einen kritischen Wendepunkt in der technischen Reife darstellt. Die korrekte Implementierung unterscheidet ein fragiles MVP von einer widerstandsfähigen Plattform auf Unternehmensniveau.
Das Problem „Undefiniert ist keine Funktion“.
20 Jahre lang lebten Frontend- und Backend-Entwickler in Silos. Backend-Entwickler: „Ich habe die Benutzer-API aktualisiert. Sie gibt „fullName“ anstelle von „first_name“ zurück.“ Frontend-Entwickler: „Okay.“ (Vergisst, den Code zu aktualisieren). Produktion: Absturz. „user.first_name“ ist undefiniert. Dies wird als Integrationslücke bezeichnet. Dokumentation (Swagger/OpenAPI) hilft, aber Dokumentation lügt. Es wird veraltet. Die moderne Lösung ist End-to-End Type Safety. Der Code selbst verhindert diese Nichtübereinstimmungsfehler.
Warum Maison Code API-Architektur diskutiert
Bei Maison Code erben wir Projekte, bei denen die API-Ebene ein Durcheinander von Spaghetti-Endpunkten ist.
/api/v1/get-user-final-final-2.
Dadurch wird die Feature-Geschwindigkeit auf ein Kriechtempo verlangsamt.
Wir implementieren Typsichere Architekturen.
Wir ermöglichen Frontend-Autonomie. Das Frontend-Team sollte das Backend-Team nicht damit belästigen müssen, ein Feld zu einer JSON-Antwort hinzuzufügen.
Wir schreiben darüber, weil skalierbare Teams auf skalierbaren Verträgen basieren.
If your schema is loose, your product is loose.
1. Die Konkurrenten
1. REST (mit OpenAPI)
Der Klassiker.
- Vorteile: Einfach, zwischenspeicherbar (HTTP 200), universell.
- Nachteile: Over-Fetching (zu viele Daten erhalten) und Under-Fetching (n+1 Anfragen).
- Modern Twist: Verwenden Sie OpenAPI (Swagger), um TypeScript-Typen automatisch zu generieren.
npx openapi-typescript schema.json -o schema.d.ts. Wenn sich nun das Backend ändert, schlägt der Frontend-Build fehl.
2. GraphQL
Der Poweruser.
- Vorteile: Der Kunde verlangt genau das, was er braucht. „graphql query { Benutzer { Name, Avatar (Größe: KLEIN) } } „
- Nachteile: Komplexes Caching (alles ist POST 200), Komplexität (Resolver, Dataloader).
- Best für: Komplexe, grafikintensive Apps (soziale Netzwerke, Dashboards) und Headless CMS-Integrationen (Shopify, Contentful).
3. tRPC (TypeScript Remote Procedure Call)
Der Geschwindigkeitsdämon.
-
Kontext: Wenn Sie sowohl das Frontend als auch das Backend besitzen (z. B. Next.js Monorepo).
-
Magie: Sie importieren die Backend-Funktion direkt in den Frontend-Code. „Typoskript // Backend export const appRouter = router({ getUser: publicProcedure.query(() => { id: 1, name: ‘Alex’ }) });
// Frontend const user = trpc.getUser.useQuery(); console.log(user.data.name); // Getippt! „
-
Es gibt kein API-Schema. Der Code ist das Schema.
-
Nachteile: An TypeScript-Monorepos gebunden.
2. Muster: Das BFF (Backend für Frontend)
In Microservice-Architekturen möchten Sie nicht, dass das Frontend zehn verschiedene Dienste aufruft (Benutzerdienst, Warenkorbdienst, Produktdienst). Es ist langsam (10 Hin- und Rückfahrten) und chaotisch. Lösung: Eine BFF-Ebene (normalerweise GraphQL oder Next.js API Routes). Das Frontend ruft die BFF einmal auf. Die BFF ruft die 10 Dienste auf, aggregiert die Daten, entfernt Geheimnisse und sendet einen sauberen JSON zurück. Dadurch kann das Frontend „dumm“ und das Backend „komplex“ sein, ohne die Leistung zu beeinträchtigen.
3. Validierung der Eingaben: Zod
Vertraue niemals dem Kunden. Unabhängig davon, ob Sie REST oder GraphQL verwenden, müssen Sie Eingaben validieren. Zod ist der Standard.
„Typoskript import { z } from ‘zod’;
const UserSchema = z.object({ E-Mail: z.string().email(), Alter: z.number().min(18) });
app.post(‘/user’, (req, res) => { const result = UserSchema.safeParse(req.body); if (!result.success) { return res.status(400).json(result.error); } // Sicher fortfahren }); „ Dieses Zod-Schema kann mit dem Frontend geteilt werden, um automatisch Formularvalidierungslogik zu generieren (mithilfe von „react-hook-form“).
4. Die Föderationsschicht (Apollo)
Für Unternehmensanwendungen reicht ein GraphQL-Server nicht aus. Sie haben das „Product Team“ in Berlin und das „Checkout Team“ in New York. Sie können nicht eine Codebasis teilen. Lösung: GraphQL Federation. Jedes Team erstellt seinen eigenen Untergraphen. Ein „Gateway“ fügt sie zu einem „Supergraph“ zusammen. Das Frontend fragt das Gateway ab. Es sieht aus wie eine API, wird aber von 50 Microservices unterstützt. Dies ist die Architektur von Netflix und Airbnb.
5. Strategien zur Fehlerbehandlung
„200 OK“, aber „Fehler: [„Nicht gefunden“]“. Dies ist die GraphQL-Falle. Strategie:
- Netzwerkfehler: (DNS, 500s). Versuchen Sie es erneut mit exponentiellem Backoff.
- Logikfehler: (Benutzer nicht gefunden). Gibt einen nullbaren Typ oder einen Union-Typ zurück.
„graphql
Union UserResult = Benutzer | UserNotFound | Erlaubnis verweigert
„
Dadurch wird das Frontend gezwungen, den Fehlerfall explizit im Code zu behandeln.
if (result.__typename === 'UserNotFound') ...Typsichere Fehlerbehandlung.
6. Caching-Strategien (Stale-While-Revalidate)
REST-Caches einfach über HTTP-Header („Cache-Control: max-age=3600“). GraphQL ist schwieriger. Wir verwenden Stale-While-Revalidate (SWR) auf dem Client (TanStack Query).
- Zwischengespeicherte Daten sofort anzeigen (veraltet).
- Neue Daten im Hintergrund abrufen (erneut validieren).
- Aktualisieren Sie die Benutzeroberfläche, falls sie geändert wurde. Dadurch fühlt sich die App „sofort“ an, auch wenn das Netzwerk langsam ist.
7. Die Sicherheitsschicht (Ratenbegrenzung und JWT)
Eine API ohne Sicherheit ist eine offene Tür. Ratenbegrenzung: DDoS verhindern.
- Verwenden Sie „upstash/ratelimit“ (Redis) auf dem Edge.
- „10 Anfragen pro 10 Sekunden pro IP“. Authentifizierung:
- Hören Sie auf, Cookies für APIs zu verwenden. Verwenden Sie JWT (JSON Web Tokens) im Header „Authorization: Bearer“.
- Staatenlos. Skalierbar.
- Stellen Sie jedoch sicher, dass Sie die Token-Rotation (Token aktualisieren) sicher handhaben. Validierung:
- Bereinigen Sie Eingaben gegen SQL-Injection (verwenden Sie ORMs).
- Bereinigen Sie die Ausgaben gegen XSS.
8. Legacy-Migrationsintegration (The Strangler Fig)
Sie verfügen über eine monolithische Legacy-API (Java/PHP). Sie möchten eine moderne Node.js-API. Schreiben Sie nicht alles auf einmal um. Du wirst scheitern. Verwenden Sie das Würgefeigenmuster.
- Setzen Sie einen Proxy (Nginx / Cloudflare) voran.
- Leiten Sie „/api/v1/users“ an die neue API weiter.
- Leiten Sie alles andere an die Legacy-API weiter.
- Migrieren Sie die Endpunkte langsam nacheinander.
- Deaktivieren Sie die Legacy-API, wenn der Datenverkehr auf Null sinkt. Dadurch können Sie sofort Mehrwert liefern, ohne dass es zu einer „Big Bang“-Umschreibung kommen muss.
9. Dokumente als Code (Stripe-Standard)
Wie sorgt Stripe dafür, dass seine Dokumente so gut sind? Sie generieren sie aus dem Code. Schreiben Sie keine API-Dokumente in Word oder Confluence. Schreiben Sie sie in das Schema.
- OpenAPI: Fügen Sie „Beschreibung“-Felder zu Ihrem YAML hinzu.
- GraphQL: Fügen Sie „Docstrings“ zu Ihrem Schema hinzu.
- Tools: Verwenden Sie Scalar oder Redoc, um schöne HTML-Dokumente aus dem Schema zu rendern.
- CI/CD: Dokumente bei jeder Zusammenführung automatisch bereitstellen. Veraltete Dokumente sind schlimmer als keine Dokumente.
10. Die Sicht des Skeptikers
„GraphQL ist tot. Verwenden Sie einfach fetch.“ Gegenpunkt: GraphQL ist im Unternehmen lebendig und wohlauf. Darauf laufen Shopify, GitHub und Facebook. „Nur Abrufen“ funktioniert für Blogs. Dies funktioniert nicht für eine E-Commerce-Produktseite, die in einer Anfrage den Status „Preis“, „Varianten“, „Bestand“, „Bewertungen“, „Empfehlungen“ und „Benutzer-Wunschliste“ benötigt. Wenn Sie das mit REST machen, haben Sie entweder 6 Anfragen (langsam) oder einen monströsen „/get-product-page-data“-Endpunkt (nicht wartbar). GraphQL löst das Orchestrierungsproblem.
11. Interner Datenverkehr: Protokollpuffer (gRPC)
JSON ist für Menschen lesbar. Es ist auch langsam. Es wiederholt die Schlüssel: „{“name“: „alex“, „alter“: 10}“. „Name“ wird jedes Mal auf dem Kabel gesendet. gRPC verwendet Protobuf (Binär). Es sendet „0x12 0x04 0x61 0x6c 0x65 0x78“. Es ist 30 % kleiner und 5x schneller zu analysieren. Wir nutzen gRPC für die interne Service-to-Service-Kommunikation (Microservices). Wir verwenden GraphQL/JSON für die Client-zu-Server-Kommunikation. Das richtige Werkzeug für den richtigen Job.
12. Das N+1-Problem (Datenlader)
Der klassische GraphQL-Killer. Abfrage: „Benutzer { Beiträge { Titel } }“.
- 1 Abfrage für Benutzer (SELECT * FROM user).
- 100 Abfragen für Beiträge (SELECT * FROM posts WHERE user_id = 1… 100).
- Insgesamt: 101 DB-Aufrufe.
Lösung: DataLoader.
Es bündelt die 100 Anfragen in EINE.
SELECT * FROM posts WHERE user_id IN (1, 2, ... 100). Dadurch wird die DB-Last um 99 % reduziert. Wenn Sie keine DataLoader verwenden, wird Ihr GraphQL-Server schmelzen.
13. Fazit
Die API-Schicht ist das Nervensystem Ihrer Anwendung. Wenn es schwach ist (nicht typisiert, ungetestet), versagt der Körper. Wenn es stark ist (tRPC, GraphQL Federation), bewegt sich der Körper beweglich. Hören Sie auf, Namen richtig zu erraten. Fangen Sie an, sie durchzusetzen. Vertrag zuerst. Code Zweiter.
API bricht das Frontend?
Wir entwerfen typsichere API-Ebenen mit GraphQL und tRPC, um Integrationsfehler ohne Fehler zu gewährleisten.