Backend-Entwicklung
Kernprinzipien
- Zustandslose Dienste — Sitzungszustand nicht im Speicher speichern; Redis oder eine Datenbank verwenden
- Schnell scheitern — Eingaben an der Grenze validieren, Fehler früh zurückgeben
- Idempotenz — POST/PUT-Endpunkte, die mehrfach sicher aufgerufen werden können
- Beobachtbarkeit — Jeder Dienst benötigt Logs, Metriken und Traces
API-Endpunktstruktur
Request-Lebenszyklus
Request → Rate Limiting → Authentifizierung → Autorisierung → Validierung → Geschäftslogik → Antwort
Diese Reihenfolge niemals überspringen. Auth vor Geschäftslogik. Validierung vor Verarbeitung.
Antwortstandards
// Erfolg
{ "data": { ... }, "meta": { "page": 1, "total": 100 } }
// Fehler
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"details": [{ "field": "email", "issue": "Invalid format" }]
}
}
Authentifizierungsmuster
JWT-Best Practices
- Kurze Access-Token-TTL (15 Min) + langes Refresh-Token (7 Tage)
- Refresh-Token in httpOnly-Cookie speichern (nicht localStorage)
- Signatur, Ablauf, Aussteller und Zielgruppe validieren
- Refresh-Tokens bei Nutzung rotieren
- Widerrufsliste verwalten oder nur kurzlebige Token verwenden
Session-basierte Authentifizierung
- Session-ID in httpOnly, Secure, SameSite-Cookie speichern
- Session-Daten in Redis mit TTL speichern
- Session-ID nach Login neu generieren (Session-Fixierung-Prävention)
Autorisierung
// Autorisierung auf Service-Ebene prüfen, nicht nur auf Routenebene
async function getOrder(userId: string, orderId: string) {
const order = await db.orders.findById(orderId)
if (!order) throw new NotFoundError()
// Autorisierungsprüfung — Benutzer kann nur eigene Bestellungen sehen
if (order.userId !== userId) throw new ForbiddenError()
return order
}
Hintergrundjobs & Warteschlangen
Warteschlangen verwenden für:
- E-Mail-Versand
- Bild-/Dateiverarbeitung
- Externe API-Aufrufe, die nicht synchron sein müssen
- Langlaufende Aufgaben
Job-Muster
Feuern-und-Vergessen: In die Warteschlange einreihen und sofort zurückkehren
Geplant: Zu einer bestimmten Zeit ausführen (Cron)
Verzögert: Nach X Minuten/Stunden ausführen
Wiederholung: Bei Fehlschlag erneut versuchen (mit Backoff)
Immer implementieren:
- Dead-Letter-Warteschlange für fehlgeschlagene Jobs
- Idempotenzschlüssel, um doppelte Verarbeitung zu verhindern
- Timeout pro Job
- Maximale Wiederholungsanzahl
Fehlerbehandlung
// Strukturierte Fehlerhierarchie
class AppError extends Error {
constructor(
public message: string,
public statusCode: number,
public code: string
) { super(message) }
}
class ValidationError extends AppError {
constructor(details: ValidationDetail[]) {
super('Validation failed', 400, 'VALIDATION_ERROR')
this.details = details
}
}
// Globaler Fehler-Handler
app.use((err, req, res, next) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({ error: err })
}
// Unerwartete Fehler protokollieren
logger.error('Unexpected error', { err, req })
res.status(500).json({ error: { code: 'INTERNAL_ERROR' } })
})
Middleware-Checkliste
Jede Produktions-API benötigt:
- Request-Logging (Methode, Pfad, Status, Dauer)
- Rate Limiting (pro IP, pro Benutzer, pro Endpunkt)
- CORS-Konfiguration (explizite Whitelist)
- Helmet / Sicherheits-Header
- Request-ID-Weitergabe (für verteiltes Tracing)
- Body-Größenbegrenzungen
- Timeout-Behandlung
Umgebungskonfiguration
// Niemals Konfiguration hartcodieren — immer aus Umgebungsvariablen
const config = {
db: {
url: process.env.DATABASE_URL, // erforderlich
poolSize: parseInt(process.env.DB_POOL_SIZE ?? '10')
},
jwt: {
secret: process.env.JWT_SECRET, // erforderlich
expiresIn: process.env.JWT_TTL ?? '15m'
}
}
// Erforderliche Konfiguration beim Start validieren — schnell scheitern
const required = ['DATABASE_URL', 'JWT_SECRET']
for (const key of required) {
if (!process.env[key]) throw new Error(`Missing required env: ${key}`)
}