# API Design Patterns für 2026 **Meta-Description:** Moderne API Design Patterns. REST Best Practices, GraphQL vs tRPC, API Versioning und Error Handling Standards. **Keywords:** API Design, REST API, GraphQL, tRPC, API Versioning, Error Handling, OpenAPI, API Security --- ## Einführung Gutes API Design entscheidet über Developer Experience und Wartbarkeit. 2026 stehen mehrere Paradigmen zur Verfügung: **REST, GraphQL, tRPC** – jedes mit eigenen Stärken für verschiedene Use Cases. --- ## API Paradigmen im Vergleich ``` ┌─────────────────────────────────────────────────────────────┐ │ API PARADIGMEN 2026 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ REST │ │ ├── Resource-orientiert │ │ ├── HTTP Verben (GET, POST, PUT, DELETE) │ │ ├── Stateless │ │ ├── Caching-freundlich │ │ └── Best for: Public APIs, Microservices │ │ │ │ GraphQL │ │ ├── Schema-first │ │ ├── Single Endpoint │ │ ├── Client-driven Queries │ │ ├── Subscriptions (Real-time) │ │ └── Best for: Complex Data, Mobile Apps │ │ │ │ tRPC │ │ ├── End-to-End Type Safety │ │ ├── No Code Generation │ │ ├── Zod Validation │ │ ├── React Query Integration │ │ └── Best for: TypeScript Monorepos │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## REST Best Practices ```typescript // 1. Resource Naming (Plural Nouns) // ✅ Good GET /users GET /users/:id POST /users PUT /users/:id DELETE /users/:id // ❌ Bad GET /getUser POST /createUser GET /user-list // 2. Nested Resources GET /users/:userId/posts GET /users/:userId/posts/:postId POST /users/:userId/posts // 3. Query Parameters für Filtering/Sorting GET /posts?status=published&author=123&sort=-createdAt&limit=10&offset=20 // 4. HTTP Status Codes // 200 OK - Success // 201 Created - Resource created // 204 No Content - Success, no body (DELETE) // 400 Bad Request - Validation error // 401 Unauthorized - Authentication required // 403 Forbidden - Permission denied // 404 Not Found - Resource not found // 409 Conflict - Duplicate/Conflict // 422 Unprocessable Entity - Semantic error // 429 Too Many Requests - Rate limited // 500 Internal Server Error - Server error ``` ```typescript // Next.js App Router REST API // app/api/users/route.ts import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; const createUserSchema = z.object({ email: z.string().email(), name: z.string().min(2) }); // GET /api/users export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const limit = parseInt(searchParams.get('limit') || '10'); const offset = parseInt(searchParams.get('offset') || '0'); const users = await db.user.findMany({ take: limit, skip: offset }); const total = await db.user.count(); return NextResponse.json({ data: users, pagination: { total, limit, offset, hasMore: offset + limit < total } }); } // POST /api/users export async function POST(request: NextRequest) { try { const body = await request.json(); const data = createUserSchema.parse(body); const user = await db.user.create({ data }); return NextResponse.json( { data: user }, { status: 201 } ); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Validation Error', details: error.errors }, { status: 400 } ); } throw error; } } ``` --- ## Error Response Standard ```typescript // RFC 7807 Problem Details interface ProblemDetails { type: string; // URI Reference für Error Type title: string; // Kurze Beschreibung status: number; // HTTP Status Code detail?: string; // Ausführliche Beschreibung instance?: string; // URI der fehlerhaften Ressource [key: string]: unknown; // Erweiterungen } // Beispiel Implementation function createErrorResponse( status: number, title: string, detail?: string, extras?: Record ): NextResponse { const body: ProblemDetails = { type: `https://api.example.com/errors/${status}`, title, status, detail, instance: `/api/request-id/${crypto.randomUUID()}`, timestamp: new Date().toISOString(), ...extras }; return NextResponse.json(body, { status, headers: { 'Content-Type': 'application/problem+json' } }); } // Verwendung if (!user) { return createErrorResponse( 404, 'User Not Found', `User with ID ${id} does not exist` ); } // Validation Error mit Details return createErrorResponse( 400, 'Validation Error', 'The request body contains invalid data', { errors: [ { field: 'email', message: 'Invalid email format' }, { field: 'name', message: 'Name is required' } ] } ); ``` --- ## API Versioning Strategies ```typescript // 1. URL Versioning (Empfohlen für Breaking Changes) // /api/v1/users // /api/v2/users // app/api/v1/users/route.ts export async function GET() { // V1 Response Format return NextResponse.json({ users: [...] }); } // app/api/v2/users/route.ts export async function GET() { // V2 Response Format (z.B. neue Felder) return NextResponse.json({ data: [...], meta: { version: 'v2' } }); } // 2. Header Versioning // Accept: application/vnd.api+json;version=2 export async function GET(request: NextRequest) { const accept = request.headers.get('Accept') || ''; const version = accept.match(/version=(\d+)/)?.[1] || '1'; if (version === '2') { return handleV2(request); } return handleV1(request); } // 3. Query Parameter (für Clients ohne Header-Kontrolle) // /api/users?version=2 export async function GET(request: NextRequest) { const version = request.nextUrl.searchParams.get('version') || '1'; // ... } ``` --- ## Rate Limiting ```typescript import { Ratelimit } from '@upstash/ratelimit'; import { Redis } from '@upstash/redis'; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10s'), // 10 Requests pro 10s analytics: true }); // Middleware export async function middleware(request: NextRequest) { const ip = request.ip ?? '127.0.0.1'; const { success, limit, reset, remaining } = await ratelimit.limit(ip); if (!success) { return NextResponse.json( { error: 'Too Many Requests', retryAfter: Math.ceil((reset - Date.now()) / 1000) }, { status: 429, headers: { 'X-RateLimit-Limit': limit.toString(), 'X-RateLimit-Remaining': remaining.toString(), 'X-RateLimit-Reset': reset.toString(), 'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString() } } ); } const response = NextResponse.next(); response.headers.set('X-RateLimit-Limit', limit.toString()); response.headers.set('X-RateLimit-Remaining', remaining.toString()); return response; } ``` --- ## Pagination Patterns ```typescript // 1. Offset Pagination (Einfach, aber langsam bei großen Datasets) interface OffsetPagination { data: User[]; pagination: { total: number; limit: number; offset: number; hasMore: boolean; }; } // 2. Cursor Pagination (Empfohlen für große Datasets) interface CursorPagination { data: T[]; pagination: { cursor: string | null; hasMore: boolean; }; } async function getUsersWithCursor(cursor?: string, limit = 20) { const users = await db.user.findMany({ take: limit + 1, // +1 um hasMore zu prüfen cursor: cursor ? { id: cursor } : undefined, orderBy: { createdAt: 'desc' } }); const hasMore = users.length > limit; const data = hasMore ? users.slice(0, -1) : users; const nextCursor = hasMore ? data[data.length - 1].id : null; return { data, pagination: { cursor: nextCursor, hasMore } }; } // 3. Keyset Pagination (Für sortierte Daten) // /api/posts?after=2024-01-15T10:00:00Z&limit=20 ``` --- ## OpenAPI Specification ```typescript // Mit Zod + zod-to-openapi import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'; const app = new OpenAPIHono(); const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string() }).openapi('User'); const route = createRoute({ method: 'get', path: '/users/{id}', request: { params: z.object({ id: z.string().uuid() }) }, responses: { 200: { content: { 'application/json': { schema: UserSchema } }, description: 'User found' }, 404: { content: { 'application/json': { schema: z.object({ error: z.string() }) } }, description: 'User not found' } } }); app.openapi(route, (c) => { const { id } = c.req.valid('param'); // ... }); // OpenAPI Spec generieren app.doc('/doc', { openapi: '3.1.0', info: { title: 'My API', version: '1.0.0' } }); ``` --- ## API Security Checklist ```typescript // 1. Authentication // - JWT mit kurzer Expiration // - Refresh Token Rotation // - Secure Cookie Storage // 2. Authorization // - RBAC oder ABAC // - Resource-level Permissions // - Rate Limiting per User // 3. Input Validation // - Zod für alle Inputs // - Sanitize User Input // - File Upload Restrictions // 4. Headers const securityHeaders = { 'Content-Security-Policy': "default-src 'self'", 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'X-XSS-Protection': '1; mode=block' }; // 5. CORS const corsConfig = { origin: ['https://app.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], credentials: true, maxAge: 86400 }; ``` --- ## Fazit Gutes API Design 2026 bedeutet: 1. **Konsistenz**: Einheitliche Naming, Responses, Errors 2. **Type Safety**: Zod/OpenAPI für Contracts 3. **Performance**: Pagination, Caching, Rate Limiting 4. **Security**: Validation, Auth, Headers Die Wahl zwischen REST/GraphQL/tRPC hängt vom Use Case ab. --- ## Bildprompts 1. "API endpoints connecting client and server, clean architecture diagram" 2. "REST vs GraphQL vs tRPC comparison, three paths to data" 3. "Security shield protecting API gateway, authentication visualization" --- ## Quellen - [REST API Design Best Practices](https://restfulapi.net/) - [RFC 7807 - Problem Details](https://datatracker.ietf.org/doc/html/rfc7807) - [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) - [tRPC Documentation](https://trpc.io/)