1 min read
BackendAPI Design Patterns für 2026
Moderne API Design Patterns. REST Best Practices, GraphQL vs tRPC, API Versioning und Error Handling Standards.
API DesignREST APIGraphQLtRPCAPI VersioningError Handling

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
// 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// 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
// 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<string, unknown>
): 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
// 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
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
// 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<T> {
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=20OpenAPI Specification
// 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
// 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:
- Konsistenz: Einheitliche Naming, Responses, Errors
- Type Safety: Zod/OpenAPI für Contracts
- Performance: Pagination, Caching, Rate Limiting
- Security: Validation, Auth, Headers
Die Wahl zwischen REST/GraphQL/tRPC hängt vom Use Case ab.
Bildprompts
- "API endpoints connecting client and server, clean architecture diagram"
- "REST vs GraphQL vs tRPC comparison, three paths to data"
- "Security shield protecting API gateway, authentication visualization"