- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
12 KiB
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=20
OpenAPI 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"