Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
476 lines
12 KiB
Markdown
476 lines
12 KiB
Markdown
# 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<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
|
||
|
||
```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<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
|
||
|
||
```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/)
|