Artikel

Best Practices für das API-Design

Best Practices für das API-Design

Eine API (Application Programming Interface) ist ein Vertrag zwischen einem Dienstanbieter und seinen Verbrauchern. Eine gut gestaltete API ist intuitiv, konsistent und einfach zu verwenden. Eine schlecht gestaltete API führt zu frustrierten Entwicklern, erhöhtem Supportaufwand, Sicherheitslücken und kostspieligen Breaking Changes.

3 Min. LesezeitSprache: DE DeutschKostenlos0 Claps0 Kommentare
TechnologieEngineering-ArtikelPlattform-LeitfädenDesignPracticesWeb DevelopmentTechnologyEngineering ArticlesPlatform Guides
Leseoptionen

Einführung

Eine API (Application Programming Interface) ist ein Vertrag zwischen einem Dienstanbieter und seinen Verbrauchern. Eine gut gestaltete API ist intuitiv, konsistent und einfach zu verwenden. Eine schlecht gestaltete API führt zu frustrierten Entwicklern, erhöhtem Supportaufwand, Sicherheitslücken und kostspieligen Breaking Changes.

APIs sind Produkte – entwerfen Sie sie mit der gleichen Sorgfalt wie Benutzeroberflächen. Unabhängig davon, ob Sie REST, GraphQL oder gRPC erstellen, bleiben die Prinzipien eines guten API-Designs konsistent.


RESTful API-Designprinzipien

1. Verwenden Sie Substantive für Ressourcen

Ressourcen sind die Kerneinheiten in Ihrem System. Benennen Sie sie mit Substantiven, nicht mit Verben:

✅ GET /users              — List users
✅ GET /users/123          — Get user by ID
✅ POST /users             — Create user
✅ PATCH /users/123        — Partially update user
✅ PUT /users/123          — Replace user
✅ DELETE /users/123       — Delete user

❌ GET /getUser            — Verb in URL
❌ POST /createUser        — Verb in URL
❌ POST /users/create      — Verb in URL path
❌ GET /users?action=delete — Action as query parameter

2. Verwenden Sie die richtigen HTTP-Methoden

Methode Zweck Idempotent Sicher Körper
GET Rufen Sie eine Ressource ab Ja Ja Nein
POST Erstellen Sie eine Ressource Nein Nein Ja
PUT Vollständiger Ersatz Ja Nein Ja
PATCH Teilweise Aktualisierung Nein Nein Ja
LÖSCHEN Entfernen Sie eine Ressource Ja Nein Optional

Idempotent: Mehrere identische Anfragen führen zum gleichen Ergebnis wie eine einzelne Anfrage.

Sicher: Keine Nebenwirkungen – kann zwischengespeichert und sicher wiederholt werden.

3. Konsistente Namenskonventionen

naming_rules:
  - "Use plural nouns: /users, /orders, /products"
  - "Use kebab-case for multi-word: /order-items, /shipping-addresses"
  - "Use lowercase throughout"
  - "Nest resources logically: /users/123/orders"
  - "Avoid deep nesting (max 2-3 levels)"
  - "Use query parameters for filtering, sorting, pagination"

Verschachtelungsbeispiel:

/users/123/orders            — Orders belonging to user 123
/users/123/orders/456       — Specific order
/users/123/orders/456/items — Items in that order (okay)
/users/123/orders/456/items/789/details (too deep!)

4. Versionieren Sie Ihre API

Breaking Changes sollten die Version erhöhen:

URI-Versionierung (am häufigsten):

https://api.example.com/v1/users
https://api.example.com/v2/users

Header-Versionierung:

Accept: application/vnd.example.v1+json

Unterbrechen Sie niemals die Abwärtskompatibilität innerhalb derselben Version, es sei denn:

  • Es liegt eine kritische Sicherheitslücke vor.
  • Sie haben eine Einstellungsrichtlinie und haben diese kommuniziert.
  • Die betroffenen Benutzer sind nur interne Benutzer.

5. Verwenden Sie aussagekräftige HTTP-Statuscodes

2xx Success:
  200 OK: Generic success (GET, PATCH)
  201 Created: Resource created (POST)
  204 No Content: Success, no response body (DELETE)

3xx Redirection:
  301 Moved Permanently
  304 Not Modified (caching)

4xx Client Error:
  400 Bad Request: Malformed request, validation error
  401 Unauthorized: Missing or invalid authentication
  403 Forbidden: Authenticated but not authorized
  404 Not Found: Resource does not exist
  405 Method Not Allowed: Wrong HTTP method
  409 Conflict: Resource state conflict (duplicate, version mismatch)
  422 Unprocessable Entity: Semantic validation failure
  429 Too Many Requests: Rate limit exceeded

5xx Server Error:
  500 Internal Server Error: Unexpected server failure
  502 Bad Gateway: Upstream service failed
  503 Service Unavailable: Temporary overload/maintenance
  504 Gateway Timeout: Upstream service timed out

Anfrage- und Antwortdesign

Konsistenter Antwortumschlag

{
  "data": {
    "id": "user_123",
    "email": "john@example.com",
    "name": "John Doe",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Fehlerantworten

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains validation errors",
    "details": [
      {
        "field": "email",
        "message": "Email is required",
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "age",
        "message": "Age must be between 18 and 120",
        "code": "INVALID_VALUE",
        "constraints": {
          "minimum": 18,
          "maximum": 120
        }
      }
    ]
  }
}

Paginierung

GET /users?page=2&per_page=20&sort=created_at:desc

{
  "data": [ ... ],
  "meta": {
    "page": 2,
    "per_page": 20,
    "total": 100,
    "total_pages": 5,
    "count": 20
  },
  "links": {
    "first": "/users?page=1&per_page=20",
    "prev": "/users?page=1&per_page=20",
    "next": "/users?page=3&per_page=20",
    "last": "/users?page=5&per_page=20"
  }
}

Cursorbasierte Paginierung (empfohlen für große Datensätze):

GET /users?cursor=eyJpZCI6IDUwMH0=&limit=20

{
  "data": [ ... ],
  "meta": {
    "next_cursor": "eyJpZCI6IDUyMH0=",
    "has_more": true
  }
}

Filtern, Sortieren und Suchen

// Filtering
GET /users?role=admin&status=active
GET /orders?created_at[gte]=2024-01-01&total[gt]=100

// Sorting
GET /users?sort=name:asc,created_at:desc

// Searching
GET /users?search=john

// Field selection (sparse fields)
GET /users/123?fields=id,name,email

Teilaktualisierungen (PATCH vs. PUT)

// PUT — Full replacement (requires all fields)
PUT /users/123
{
  "email": "newemail@example.com",
  "name": "John Updated",
  "age": 31
}

// PATCH — Partial update (only send changed fields)
PATCH /users/123
{
  "email": "newemail@example.com"
}

Best Practices für die Sicherheit

Authentifizierung

Empfohlen: OAuth 2.0 + OpenID Connect

POST /oauth/token
Content-Type: application/json

{
  "grant_type": "client_credentials",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "read write"
}

Response:
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write"
}
GET /api/users
Authorization: Bearer eyJhbGciOi...

JWT-Struktur

// Header
{
  "alg": "RS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "user_123",
  "iss": "auth.example.com",
  "iat": 1705312200,
  "exp": 1705315800,
  "scope": "read:users write:users",
  "roles": ["admin"]
}

Ratenbegrenzung

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705315800
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 3600 seconds.",
    "retry_after": 3600
  }
}

Eingabevalidierung

// Zod validation schema for API input
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email('Invalid email format'),
  name: z.string().min(1, 'Name is required').max(100),
  age: z.number().int().min(18).max(120).optional(),
  role: z.enum(['user', 'admin', 'moderator']).default('user'),
  tags: z.array(z.string()).max(10).optional(),
});

// Validation middleware
function validate(schema: ZodSchema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(422).json({
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Validation failed',
          details: result.error.issues.map(i => ({
            field: i.path.join('.'),
            message: i.message
          }))
        }
      });
    }
    req.validatedBody = result.data;
    next();
  };
}

HTTPS und CORS

// CORS configuration
const corsOptions = {
  origin: [
    'https://app.example.com',
    'https://admin.example.com',
  ],
  methods: ['GET', 'POST', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-RateLimit-Remaining'],
  credentials: true,
  maxAge: 86400  // Preflight cache (24 hours)
};
app.use(cors(corsOptions));

API-Dokumentation

OpenAPI 3.0-Spezifikation

openapi: 3.0.3
info:
  title: User Management API
  version: 2.0.0
  description: API for managing users in the system
  contact:
    email: api-support@example.com

servers:
  - url: https://api.example.com/v2
    description: Production server

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '422':
          description: Validation error

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        created_at:
          type: string
          format: date-time

Interaktive Dokumentation

# Serve Swagger UI for API documentation
npm install -g swagger-ui-express
# Visit: https://api.example.com/docs

API-Tests

Vertragstests

// Pact consumer-driven contract test
const { Pact } = require('@pact-foundation/pact');

describe('User API contract', () => {
  const provider = new Pact({
    consumer: 'Frontend App',
    provider: 'User Service',
    port: 1234,
  });

  beforeAll(() => provider.setup());

  describe('GET /users/123', () => {
    beforeAll(() => {
      return provider.addInteraction({
        state: 'User 123 exists',
        uponReceiving: 'a request for user 123',
        withRequest: {
          method: 'GET',
          path: '/users/123',
          headers: { Authorization: 'Bearer valid-token' }
        },
        willRespondWith: {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
          body: {
            data: {
              id: '123',
              email: 'user@example.com',
              name: 'Test User'
            }
          }
        }
      });
    });

    it('returns the user', async () => {
      const response = await fetch('http://localhost:1234/users/123', {
        headers: { Authorization: 'Bearer valid-token' }
      });
      expect(response.status).toBe(200);
    });

    afterEach(() => provider.verify());
  });

  afterAll(() => provider.finalize());
});

GraphQL vs. REST

Aspekt RUHE GraphQL
Datenabruf Mehrere Endpunkte, feste Antwort Einzelner Endpunkt, vom Kunden definiert
Übermäßiges Abrufen Gibt möglicherweise mehr Daten als nötig zurück Fordern Sie genau das an, was Sie brauchen
Unzureichendes Abrufen Möglicherweise sind mehrere Anfragen erforderlich (N+1) Einzelne Anfrage für verschachtelte Daten
Caching Integriert (HTTP-Caching, CDN) Benutzerdefiniert (Apollo-Cache, Relay)
Werkzeuge Reife (Curl, Postbote, Browser) Wachsend (GraphiQL, Apollo DevTools)
Lernkurve Niedrig Mittel
Versionierung URI-Versionierung Felder hinzufügen, alte verwerfen
Datei-Upload Native (mehrteilig) Erfordert eine besondere Handhabung
Echtzeit WebSocket, SSE Abonnements

Wann man GraphQL verwenden sollte

  • Komplexe, verschachtelte Datenanforderungen (Dashboards, mobile Apps).
  • Mehrere Clienttypen (Web, Mobil, IoT), die unterschiedliche Daten benötigen.
  • Sich schnell entwickelnde Frontend-Anforderungen.

Wann sollte REST verwendet werden?

  • Einfache CRUD-Operationen.
  • Öffentliche APIs mit breiter Verbraucherbasis.
  • Caching ist wichtig (CDN für GET-Endpunkte).
  • Datei-Uploads und Binärdaten.

API-Lebenszyklusmanagement

Versionierungsstrategie

version_policy:
  - v1: Initial release (stable, deprecated)
  - v2: Current version (active development)
  - v3: Planned (future)

deprecation:
  notice_period: 6 months
  communication: email, API response header
  sunset_header: true  # Deprecation: true

transition:
  new_features: v2 only
  bug fixes: v1 critical only
  migrations: provide migration guide

Abschreibungsheader

GET /v1/users/123
Sunset: Sat, 30 Nov 2024 00:00:00 GMT
Deprecation: true
Link: </v2/users/123>; rel="successor-version"

Fazit

Ein gutes API-Design folgt diesen Prinzipien:

  1. Konsistenz – Benennung, Fehlerformate, Paginierung und Authentifizierung sollten denselben Mustern folgen.
  2. Entwicklererfahrung – Klare Dokumentation, vorhersehbares Verhalten, hilfreiche Fehlermeldungen.
  3. Standardmäßige Sicherheit – HTTPS, Authentifizierung, Ratenbegrenzung, Eingabevalidierung, CORS.
  4. Abwärtskompatibilität – Versionsbewusst, ordnungsgemäß veraltet, bahnbrechende Änderungen kommunizieren.
  5. Leistung – Paginierung, Caching-Header, Felder mit geringer Dichte, Komprimierung.

Eine API ist ein Produkt. Investieren Sie in sein Design, dokumentieren Sie es gründlich, versionieren Sie es sorgfältig und behandeln Sie jede bahnbrechende Änderung mit der nötigen Ernsthaftigkeit.

Empfohlene Artikel

6 Coding-Patterns, die ich von Senior Engineers gelernt habe
EditorialDE
8 Min.Kostenlos

6 Coding-Patterns, die ich von Senior Engineers gelernt habe

TechnologieCodeCoding
0 Claps
Lesen
Einen privaten KI-Assistenten für Notizen bauen
EmpfohlenDE
9 Min.Kostenlos

Einen privaten KI-Assistenten für Notizen bauen

Mache aus Markdown-Notizen einen durchsuchbaren Assistenten, ohne private Entwürfe offenzulegen.

TechnologiePlattform-LeitfädenKI-SchreibenAIAutomationData
0 Claps
Lesen
Rechnungsdaten mit OCR automatisiert auslesen
EditorialDE
8 Min.Kostenlos

Rechnungsdaten mit OCR automatisiert auslesen

Baue eine Pipeline, die Rechnungen liest, Felder extrahiert und unsichere Werte markiert.

TechnologieEngineering-ArtikelKI-SchreibenAIAutomationData
0 Claps
Lesen

Kommentare

0 Kommentare

Noch keine freigegebenen Kommentare sichtbar. Neue Antworten können moderiert werden.