Das Erstellen einer gut gestalteten REST API ist eine der wichtigsten Fähigkeiten für moderne Entwickler. Eine großartige API ist intuitiv, konsistent, sicher und einfach zu warten. Dieser umfassende Leitfaden behandelt alles von URL-Benennungskonventionen und HTTP-Methoden bis hin zu Authentifizierung, Paginierung, Rate Limiting und häufigen Fehlern.
URL-Benennungskonventionen
Das Fundament einer guten REST API beginnt mit gut gestalteten URLs. Ihre Endpunkte sollten vorhersagbar, lesbar und konsistent sein.
Verwenden Sie Substantive, keine Verben
REST-Ressourcen sollten durch Substantive identifiziert werden. Die HTTP-Methode gibt bereits die Aktion an.
# Good - nouns as resources
GET /users # Get all users
GET /users/123 # Get user 123
POST /users # Create a new user
DELETE /users/123 # Delete user 123
# Bad - verbs in URLs
GET /getUsers
POST /createUser
DELETE /deleteUser/123Verwenden Sie Pluralformen für Sammlungen
Verwenden Sie immer Plural-Substantive für Sammlungsendpunkte. Dies sorgt für Konsistenz.
# Good - consistent plurals
GET /users # Collection
GET /users/123 # Single resource in collection
GET /products # Collection
GET /products/456 # Single resource
# Bad - inconsistent or singular
GET /user # Ambiguous: one user or all users?
GET /user/123
GET /product-listVerschachtelte Ressourcen für Beziehungen
Verwenden Sie Verschachtelung, um Beziehungen zwischen Ressourcen auszudrücken, aber gehen Sie nicht tiefer als zwei Ebenen.
# Good - clear parent-child relationship (max 2 levels)
GET /users/123/orders # Orders belonging to user 123
GET /users/123/orders/456 # Specific order of user 123
# Bad - too deeply nested
GET /users/123/orders/456/items/789/reviews
# Better: use a direct resource endpoint
GET /order-items/789/reviews
# Or: flatten with query parameters
GET /reviews?order_item_id=789URL-Muster: Gut vs Schlecht
| Gut | Schlecht | Grund |
|---|---|---|
| GET /users | GET /getUsers | HTTP method already implies the action |
| GET /users/123 | GET /user?id=123 | Use path parameters for resource identity |
| POST /users | POST /createUser | POST already means create |
| PUT /users/123 | POST /updateUser | PUT/PATCH for updates, use resource path |
| DELETE /users/123 | POST /deleteUser | Use DELETE method, not POST with verb |
| GET /users/123/orders | GET /getUserOrders?userId=123 | Use nested resources for relationships |
| GET /products?category=electronics | GET /electronics/products | Use query params for filtering |
| GET /users?status=active&sort=name | GET /active-users-sorted-by-name | Use query params, not encoded URLs |
| PATCH /users/123 | POST /users/123/update | Use HTTP methods, not URL verbs |
| GET /users/123/avatar | GET /getAvatarForUser/123 | Keep it resource-oriented |
HTTP-Methoden
Jede HTTP-Methode hat eine spezifische Semantik. Die korrekte Verwendung macht Ihre API vorhersagbar.
| Methode | Zweck | Idempotent | Request Body | Beispiel |
|---|---|---|---|---|
| GET | Retrieve a resource or collection | Yes | No | GET /users/123 |
| POST | Create a new resource | No | Yes | POST /users |
| PUT | Replace a resource entirely | Yes | Yes | PUT /users/123 |
| PATCH | Partially update a resource | No* | Yes | PATCH /users/123 |
| DELETE | Remove a resource | Yes | Optional | DELETE /users/123 |
* PATCH can be idempotent depending on implementation. If the patch describes the final state (e.g., {"name": "John"}), it is idempotent. If it describes an operation (e.g., {"op": "increment", "path": "/count"}), it is not.
HTTP-Statuscodes
Die Verwendung der richtigen Statuscodes hilft Clients, Antworten angemessen zu verarbeiten.
| Code | Name | Wann verwenden |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, or DELETE. Return the resource or confirmation. |
| 201 | Created | Successful POST that created a new resource. Include Location header with the new resource URL. |
| 204 | No Content | Successful DELETE or PUT/PATCH when no response body is needed. |
| 400 | Bad Request | The request is malformed — invalid JSON, missing required fields, wrong data types. |
| 401 | Unauthorized | No valid authentication credentials provided. The client must authenticate first. |
| 403 | Forbidden | The client is authenticated but does not have permission for this action. |
| 404 | Not Found | The requested resource does not exist. Also use for hidden resources (instead of 403) for security. |
| 409 | Conflict | The request conflicts with the current state — duplicate email, version mismatch, etc. |
| 422 | Unprocessable Entity | The request is well-formed but fails validation — email format invalid, password too short. |
| 429 | Too Many Requests | Rate limit exceeded. Include Retry-After header with seconds until the client can retry. |
| 500 | Internal Server Error | An unexpected server error occurred. Log the details server-side but do not expose them to clients. |
| 503 | Service Unavailable | The server is temporarily unavailable — maintenance mode, overloaded. Include Retry-After header. |
Fehlerbehandlung
Konsistente Fehlerantworten machen Ihre API einfacher zu nutzen. Jeder Fehler sollte einen strukturierten JSON-Body zurückgeben.
Standard-Fehlerantwortformat
Verwenden Sie einen konsistenten Fehlerumschlag für alle Fehlerantworten.
// Standard error response
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The user with ID 123 was not found.",
"status": 404,
"timestamp": "2026-01-15T10:30:00Z",
"request_id": "req_abc123def456",
"documentation_url": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
}
}
// Another example: authentication error
{
"error": {
"code": "TOKEN_EXPIRED",
"message": "The access token has expired. Please refresh your token.",
"status": 401,
"timestamp": "2026-01-15T10:30:00Z",
"request_id": "req_xyz789"
}
}Validierungsfehler mit Felddetails
Bei 422-Validierungsfehlern fügen Sie feldspezifische Fehlerinformationen hinzu.
// 422 Validation error with field-level details
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request body contains invalid fields.",
"status": 422,
"details": [
{
"field": "email",
"message": "Must be a valid email address.",
"rejected_value": "not-an-email"
},
{
"field": "password",
"message": "Must be at least 8 characters long.",
"rejected_value": "short"
},
{
"field": "age",
"message": "Must be a positive integer.",
"rejected_value": -5
}
],
"timestamp": "2026-01-15T10:30:00Z",
"request_id": "req_val456"
}
}Authentifizierung und Autorisierung
Die Wahl der richtigen Authentifizierungsstrategie hängt von Ihrem Anwendungsfall ab.
API-Schlüssel
Einfache String-Token, die in Headern oder Query-Parametern übergeben werden. Am besten für Server-zu-Server-Kommunikation.
# API Key in header (recommended)
GET /api/users HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_abc123def456ghi789
# API Key in query parameter (less secure - visible in logs)
GET /api/users?api_key=sk_live_abc123def456ghi789JWT Bearer Token
Selbständige Token mit Benutzer-Claims. Ideal für zustandslose Authentifizierung in Microservices.
# JWT Bearer Token in Authorization header
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
# Token refresh flow
POST /api/auth/refresh HTTP/1.1
Content-Type: application/json
{
"refresh_token": "rt_abc123def456"
}
# Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_new789xyz"
}OAuth 2.0
Der Industriestandard für delegierte Autorisierung. Ermöglicht Drittanwendungen den Zugriff im Namen eines Benutzers.
# OAuth 2.0 Authorization Code Flow
# Step 1: Redirect user to authorization server
GET https://auth.example.com/authorize?
response_type=code&
client_id=your_client_id&
redirect_uri=https://yourapp.com/callback&
scope=read:users write:users&
state=random_csrf_token
# Step 2: Exchange authorization code for tokens
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_FROM_CALLBACK&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://yourapp.com/callback
# Step 3: Use the access token
GET /api/users/me HTTP/1.1
Authorization: Bearer ACCESS_TOKEN_HEREAuthentifizierungsvergleich
| Funktion | API Key | JWT | OAuth 2.0 |
|---|---|---|---|
| Complexity | Low | Medium | High |
| Stateless | Yes | Yes | Depends |
| User context | No | Yes | Yes |
| Token expiration | Manual rotation | Built-in (exp claim) | Built-in |
| Scope/permissions | Limited | Via claims | Fine-grained scopes |
| Third-party access | No | No | Yes (delegated) |
| Best for | Server-to-server | SPAs, Mobile apps | Third-party integrations |
| Revocation | Delete key | Blacklist needed | Revoke at auth server |
Paginierungsmuster
Jeder Endpunkt, der eine Ressourcenliste zurückgibt, sollte Paginierung unterstützen.
Offset-basierte Paginierung
Der einfachste Ansatz. Verwendet Seitennummer und Limit-Parameter.
# Offset-based pagination request
GET /api/users?page=2&limit=20
# How it works internally
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20; -- Skip first 20, return next 20
# Pros: Simple, allows jumping to any page
# Cons: Inconsistent with concurrent modifications,
# slow on large offsets (OFFSET 100000)Cursor-basierte Paginierung
Verwendet ein opakes Cursor-Token zur Markierung der Position im Datensatz.
# Cursor-based pagination request
GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
# The cursor is an opaque token (often base64-encoded)
# that points to a specific position in the dataset
# How it works internally (cursor decodes to {"id": 123})
SELECT * FROM users
WHERE id > 123
ORDER BY id ASC
LIMIT 20;
# Pros: Consistent results, fast on large datasets
# Cons: Cannot jump to arbitrary pages,
# only next/previous navigationPaginierungsantwortformat
Fügen Sie Metadaten zum Paginierungsstatus in jede paginierte Antwort ein.
// Offset-based pagination response
{
"data": [
{ "id": 21, "name": "Alice" },
{ "id": 22, "name": "Bob" }
// ... 18 more items
],
"meta": {
"current_page": 2,
"per_page": 20,
"total_pages": 15,
"total_count": 294
},
"links": {
"self": "/api/users?page=2&limit=20",
"first": "/api/users?page=1&limit=20",
"prev": "/api/users?page=1&limit=20",
"next": "/api/users?page=3&limit=20",
"last": "/api/users?page=15&limit=20"
}
}
// Cursor-based pagination response
{
"data": [
{ "id": 124, "name": "Charlie" },
{ "id": 125, "name": "Diana" }
// ... 18 more items
],
"meta": {
"has_more": true,
"next_cursor": "eyJpZCI6MTQzfQ",
"prev_cursor": "eyJpZCI6MTI0fQ"
},
"links": {
"next": "/api/users?cursor=eyJpZCI6MTQzfQ&limit=20",
"prev": "/api/users?cursor=eyJpZCI6MTI0fQ&limit=20&direction=prev"
}
}API-Versionierungsstrategien
APIs entwickeln sich weiter. Versionierung ermöglicht Breaking Changes ohne bestehende Clients zu stören.
| Strategie | Beispiel | Vorteile | Nachteile |
|---|---|---|---|
| URL Path | /v1/users, /v2/users | Explicit, easy to route and cache, clear in logs and docs | URL pollution, harder to sunset old versions |
| Custom Header | Accept-Version: v2 or X-API-Version: 2 | Clean URLs, version hidden from casual users | Harder to test (need tools to set headers), not cacheable by default |
| Query Parameter | /users?version=2 | Easy to test in browser, no URL path changes | Can be accidentally omitted, harder to route, pollutes query string |
| Content Negotiation | Accept: application/vnd.api.v2+json | Most RESTful approach, per-resource versioning | Complex, hard to test, poorly supported by many tools |
# URL Path versioning (recommended)
GET /v1/users/123 # Version 1
GET /v2/users/123 # Version 2 with breaking changes
# Express.js router example
const v1Router = express.Router();
const v2Router = express.Router();
v1Router.get('/users/:id', v1UserController.get);
v2Router.get('/users/:id', v2UserController.get);
app.use('/v1', v1Router);
app.use('/v2', v2Router);Rate Limiting
Rate Limiting schützt Ihre API vor Missbrauch und gewährleistet faire Nutzung.
Rate-Limit-Header
Fügen Sie diese Header in jede Antwort ein, damit Clients ihre Nutzung überwachen können.
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 1000 # Max requests per window
X-RateLimit-Remaining: 847 # Remaining requests in window
X-RateLimit-Reset: 1706176800 # Unix timestamp when window resets
X-RateLimit-Window: 3600 # Window duration in seconds (1 hour)
# Some APIs also include:
RateLimit-Policy: 1000;w=3600 # IETF draft standard format429 Zu viele Anfragen
Wenn ein Client das Limit überschreitet, geben Sie einen 429-Status mit Retry-After-Header zurück.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 45
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706176800
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "You have exceeded the rate limit of 1000 requests per hour.",
"status": 429,
"retry_after": 45,
"documentation_url": "https://api.example.com/docs/rate-limiting"
}
}Backoff-Strategien
Clients sollten exponentielles Backoff implementieren, wenn sie 429-Antworten erhalten.
// Exponential backoff with jitter
async function fetchWithRetry(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
// Use Retry-After header if available
const retryAfter = response.headers.get('Retry-After');
let delay;
if (retryAfter) {
delay = parseInt(retryAfter, 10) * 1000;
} else {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
const baseDelay = Math.pow(2, attempt) * 1000;
// Add jitter (random 0-1s) to prevent thundering herd
const jitter = Math.random() * 1000;
delay = baseDelay + jitter;
}
console.log(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Max retries exceeded');
}Sicherheitsheader
Sicherheitsheader schützen Ihre API vor gängigen Web-Schwachstellen.
| Header | Empfohlener Wert | Zweck |
|---|---|---|
| Access-Control-Allow-Origin | https://yourapp.com (specific origin) | CORS: Controls which domains can call your API. Never use * in production with credentials. |
| Access-Control-Allow-Methods | GET, POST, PUT, PATCH, DELETE | CORS: Specifies which HTTP methods are allowed for cross-origin requests. |
| Access-Control-Allow-Headers | Authorization, Content-Type | CORS: Specifies which request headers are allowed in cross-origin requests. |
| Strict-Transport-Security | max-age=31536000; includeSubDomains | HSTS: Forces browsers to use HTTPS for all future requests to your domain. |
| Content-Security-Policy | default-src 'none'; frame-ancestors 'none' | CSP: Prevents XSS and data injection attacks. For APIs, restrict everything. |
| X-Content-Type-Options | nosniff | Prevents browsers from MIME-type sniffing. Ensures responses are treated as their declared content type. |
| X-Frame-Options | DENY | Prevents your API responses from being embedded in iframes (clickjacking protection). |
| Cache-Control | no-store, no-cache, must-revalidate | Prevents caching of sensitive API responses. Adjust per endpoint as needed. |
// Express.js security headers middleware
const helmet = require('helmet');
app.use(helmet());
// Custom CORS configuration
const cors = require('cors');
app.use(cors({
origin: ['https://yourapp.com', 'https://admin.yourapp.com'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400 // Cache preflight for 24 hours
}));
// Additional security headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Cache-Control', 'no-store');
next();
});API-Dokumentation mit OpenAPI
Gute Dokumentation ist für die API-Akzeptanz unerlässlich. OpenAPI ist der Standard zur Beschreibung von REST APIs.
- Schreiben Sie zuerst die OpenAPI-Spezifikation (Design-First-Ansatz), dann implementieren Sie die API.
- Fügen Sie detaillierte Beschreibungen für jeden Endpunkt, Parameter und Antwortschema hinzu.
- Stellen Sie realistische Beispielwerte für alle Request- und Response-Bodies bereit.
- Dokumentieren Sie Fehlerantworten für jeden Endpunkt.
- Verwenden Sie Tags, um verwandte Endpunkte zu gruppieren.
- Versionieren Sie Ihre OpenAPI-Spezifikation zusammen mit Ihrem API-Code.
- Richten Sie interaktive Dokumentation (Swagger UI oder Redoc) ein.
# OpenAPI 3.1 specification example
openapi: 3.1.0
info:
title: User Management API
version: 1.0.0
description: API for managing users and their resources
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List all users
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: A paginated list of users
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
example:
data:
- id: 1
name: "Alice Johnson"
email: "alice@example.com"
meta:
current_page: 1
total_pages: 10
'401':
$ref: '#/components/responses/Unauthorized'
post:
summary: Create a new user
tags: [Users]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
example:
name: "Bob Smith"
email: "bob@example.com"
password: "securePassword123"
responses:
'201':
description: User created successfully
'422':
$ref: '#/components/responses/ValidationError'Häufige REST API Fehler
Selbst erfahrene Entwickler machen diese Fehler. Überprüfen Sie diese Liste.
| # | Fehler | Warum ist es falsch | Lösung |
|---|---|---|---|
| 1 | Using verbs in URLs | REST is resource-oriented; HTTP methods express actions | Use nouns: /users not /getUsers |
| 2 | Returning 200 for errors | Clients rely on status codes for error handling | Use proper status codes: 400, 404, 422, 500 |
| 3 | Not paginating list endpoints | Returns grow unbounded, causing timeouts and memory issues | Always paginate with sensible defaults (limit=20) |
| 4 | Exposing internal errors | Stack traces reveal implementation details to attackers | Log details server-side, return generic error messages |
| 5 | Ignoring CORS headers | Browser-based clients cannot call your API | Configure CORS for allowed origins and methods |
| 6 | Not versioning the API | Breaking changes affect all clients simultaneously | Use URL path versioning: /v1/users |
| 7 | Using POST for everything | Loses HTTP method semantics, breaks caching and idempotency | Use GET, POST, PUT, PATCH, DELETE appropriately |
| 8 | Inconsistent naming conventions | Mixed case or formats confuse API consumers | Pick one (snake_case or camelCase) and be consistent |
| 9 | No rate limiting | API is vulnerable to abuse and DDoS attacks | Implement rate limiting with clear headers |
| 10 | Missing Content-Type header | Clients cannot parse responses correctly | Always set Content-Type: application/json |
Probieren Sie diese verwandten Entwicklertools
Häufig gestellte Fragen
Was ist der Unterschied zwischen REST und RESTful?
REST ist ein von Roy Fielding definierter Architekturstil. RESTful ist ein Adjektiv, das APIs beschreibt, die REST-Einschränkungen einhalten. In der Praxis werden die Begriffe austauschbar verwendet.
Soll ich PUT oder PATCH zum Aktualisieren von Ressourcen verwenden?
Verwenden Sie PUT, wenn der Client einen vollständigen Ersatz der Ressource sendet. Verwenden Sie PATCH für partielle Updates. PATCH ist bandbreiteneffizienter.
Wie sollte ich die API-Versionierung handhaben?
URL-Pfad-Versionierung (z.B. /v1/users) ist der gängigste und expliziteste Ansatz. Er ist leicht zu verstehen, zu routen und zu cachen.
Was ist der beste Weg zur Authentifizierung in REST APIs?
Für die meisten Anwendungen bieten JWT Bearer Token eine gute Balance zwischen Sicherheit und Einfachheit. Verwenden Sie kurzlebige Zugriffstoken mit Refresh-Token-Rotation.
Wie entwerfe ich verschachtelte Ressourcen in REST APIs?
Verwenden Sie Verschachtelung für Eltern-Kind-Beziehungen: /users/123/orders. Begrenzen Sie die Verschachtelung auf maximal zwei Ebenen.
Sollten REST API Antworten null-Felder enthalten oder weglassen?
Das hängt von Ihrem Vertrag ab. Null-Felder einzuschließen macht das Schema vorhersagbar. Sie wegzulassen reduziert die Payload-Größe. Seien Sie konsistent und dokumentieren Sie es.