Backend · Claude Code

Claude Code + REST API: Von der Spec zum laufenden Endpoint

OpenAPI Design, Endpoint-Implementierung, Auth-Middleware, Integration Tests — der vollständige Backend-Workflow mit Claude Code. Mit echten TypeScript-Code-Beispielen.

2. Mai 2026 Backend ca. 10 min Lesezeit

Wer als Backend-Entwickler täglich REST APIs baut, kennt das Muster: Spec schreiben, Routen anlegen, Validierung ergänzen, Auth-Middleware einhängen, Tests schreiben, Doku pflegen. Jeder dieser Schritte kostet Zeit — und keiner davon ist wirklich kreativ. Genau hier verändert Claude Code REST API-Entwicklung grundlegend.

Claude Code ist kein einfacher Code-Completion-Service. Es versteht den gesamten Kontext deines Projekts: bestehende Routen, Typen, Fehlerbehandlungs-Patterns, Abhängigkeiten. Dieser Artikel zeigt fünf konkrete Workflows für die REST API Entwicklung mit KI — und ein vollständiges Beispiel, das du sofort in dein Projekt übernehmen kannst.

Von der natürlichen Sprache zur laufenden API

Der klassische API-Entwicklungszyklus dauert je nach Komplexität Stunden bis Tage. Mit Claude Code API Entwicklung verkürzt sich dieser Zyklus drastisch — nicht weil der Code magisch entsteht, sondern weil die mechanischen Übersetzungsschritte entfallen.

Das Kernprinzip: Du beschreibst das Was und das Warum. Claude Code übernimmt das Wie — konsistent, typsicher, mit Best Practices die du sonst manuell einhalten müsstest.

Der typische Flow sieht so aus:

  1. Intent beschreiben — "Ich brauche eine CRUD-API für Produkte, PostgreSQL, Express, TypeScript"
  2. OpenAPI Spec generieren lassen — vollständig, mit allen Response-Schemas
  3. Endpoints implementieren lassen — inklusive Validierung und Error Handling
  4. Middleware ergänzen — Auth, Rate Limiting, Logging in einem Prompt
  5. Tests schreiben lassen — Jest + Supertest, alle Edge Cases abgedeckt

5 konkrete Backend-Workflows mit Claude Code

Vollbeispiel: CRUD Endpoint für /api/products

Ein konkreter Prompt für einen vollständigen Produkt-CRUD:

"Implementiere einen vollständigen CRUD-Endpoint für /api/products in Express mit TypeScript. Nutze Zod für Input-Validierung, strukturierte JSON-Error-Responses (code, message, details), JWT-Auth für POST/PUT/DELETE. Produkt hat: id (UUID), name (string, 1-100 Zeichen), price (positive Zahl), category (enum: 'electronics' | 'clothing' | 'food'), createdAt. Schreib auch die TypeScript-Types und einen Integration-Test mit Supertest."

Route Definition (Express + TypeScript)

// src/routes/products.ts
import { Router, Request, Response } from 'express';
import { z } from 'zod';
import { ProductService } from '../services/product.service';
import { requireAuth } from '../middleware/auth';
import { asyncHandler } from '../middleware/async-handler';

export const productRouter = Router();

// GET /api/products — public
productRouter.get('/', asyncHandler(async (req: Request, res: Response) => {
  const { category, page = 1, limit = 20 } = req.query;
  const products = await ProductService.list({
    category: category as string | undefined,
    page: Number(page),
    limit: Math.min(Number(limit), 100),
  });
  res.json({ data: products.items, meta: products.meta });
}));

// GET /api/products/:id — public
productRouter.get('/:id', asyncHandler(async (req, res) => {
  const product = await ProductService.findById(req.params.id);
  if (!product) {
    return res.status(404).json({
      code: 'NOT_FOUND',
      message: `Product ${req.params.id} not found`,
    });
  }
  res.json({ data: product });
}));

// POST /api/products — requires auth
productRouter.post('/', requireAuth, asyncHandler(async (req, res) => {
  const body = CreateProductSchema.parse(req.body); // throws ZodError on invalid
  const product = await ProductService.create(body);
  res.status(201).json({ data: product });
}));

// PUT /api/products/:id — requires auth
productRouter.put('/:id', requireAuth, asyncHandler(async (req, res) => {
  const body = UpdateProductSchema.parse(req.body);
  const product = await ProductService.update(req.params.id, body);
  if (!product) return res.status(404).json({ code: 'NOT_FOUND', message: 'Product not found' });
  res.json({ data: product });
}));

// DELETE /api/products/:id — requires auth
productRouter.delete('/:id', requireAuth, asyncHandler(async (req, res) => {
  await ProductService.delete(req.params.id);
  res.status(204).send();
}));

Input Validation mit Zod

// src/schemas/product.schema.ts
import { z } from 'zod';

const CategoryEnum = z.enum(['electronics', 'clothing', 'food']);

export const CreateProductSchema = z.object({
  name:     z.string().min(1).max(100),
  price:    z.number().positive('Price must be greater than 0'),
  category: CategoryEnum,
});

export const UpdateProductSchema = CreateProductSchema.partial();

// TypeScript types — abgeleitet aus dem Schema, immer in sync
export type CreateProductDTO = z.infer<typeof CreateProductSchema>;
export type UpdateProductDTO = z.infer<typeof UpdateProductSchema>;

export interface Product {
  id:        string;   // UUID v4
  name:      string;
  price:     number;
  category:  'electronics' | 'clothing' | 'food';
  createdAt: Date;
}

Globales Error Handling

// src/middleware/error-handler.ts
import { ZodError } from 'zod';
import { ErrorRequestHandler } from 'express';

export const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => {
  // Zod validation error → 400
  if (err instanceof ZodError) {
    return res.status(400).json({
      code:    'VALIDATION_ERROR',
      message: 'Invalid request body',
      details: err.issues.map(i => ({ field: i.path.join('.'), message: i.message })),
    });
  }

  // JWT / Auth errors → 401
  if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError') {
    return res.status(401).json({ code: 'UNAUTHORIZED', message: 'Invalid or expired token' });
  }

  // Known app errors (AppError class)
  if (err.isOperational) {
    return res.status(err.statusCode).json({ code: err.code, message: err.message });
  }

  // Unhandled → 500, log details server-side only
  console.error('[ERROR]', { requestId: req.headers['x-request-id'], error: err });
  res.status(500).json({ code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' });
};

Integration Test mit Jest + Supertest

// tests/api/products.test.ts
import request from 'supertest';
import { app } from '../../src/app';
import { generateTestToken } from '../helpers/auth';
import { db } from '../helpers/db';

let authToken: string;

beforeAll(async () => {
  await db.migrate(); // test DB setup
  authToken = await generateTestToken({ role: 'admin' });
});
afterEach(() => db.truncate('products'));
afterAll(() => db.close());

describe('POST /api/products', () => {
  it('201 — creates product with valid payload', async () => {
    const res = await request(app)
      .post('/api/products')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ name: 'Laptop Pro', price: 1299.99, category: 'electronics' });

    expect(res.status).toBe(201);
    expect(res.body.data).toMatchObject({ name: 'Laptop Pro', category: 'electronics' });
    expect(res.body.data.id).toBeDefined();
  });

  it('400 — rejects negative price', async () => {
    const res = await request(app)
      .post('/api/products')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ name: 'Test', price: -10, category: 'food' });

    expect(res.status).toBe(400);
    expect(res.body.code).toBe('VALIDATION_ERROR');
    expect(res.body.details[0].field).toBe('price');
  });

  it('401 — rejects missing token', async () => {
    const res = await request(app)
      .post('/api/products')
      .send({ name: 'Test', price: 10, category: 'food' });

    expect(res.status).toBe(401);
  });
});

describe('GET /api/products/:id', () => {
  it('404 — returns structured error for unknown id', async () => {
    const res = await request(app).get('/api/products/00000000-0000-0000-0000-000000000000');
    expect(res.status).toBe(404);
    expect(res.body.code).toBe('NOT_FOUND');
  });
});

Error-Handling Patterns die Claude Code kennt

Eines der häufigsten Probleme in selbst geschriebenen APIs ist inkonsistentes Error-Handling. Claude Code kennt alle Standard-HTTP-Fehlermuster und setzt sie konsistent um — wenn du es explizit anweist.

Status Code Wann Logging
400 VALIDATION_ERROR Zod/Schema-Fehler, fehlende Pflichtfelder, falscher Typ Kein Server-Log (Client-Fehler)
401 UNAUTHORIZED Fehlendes Token, abgelaufenes JWT, ungültige Signatur Warn-Log mit IP
403 FORBIDDEN Token gültig, aber fehlende Berechtigung (z.B. User löscht fremde Ressource) Warn-Log mit User-ID
404 NOT_FOUND Ressource existiert nicht, ID unbekannt Kein Log (normal)
429 RATE_LIMITED Zu viele Requests, Rate-Limit überschritten Metric-Counter (kein Error-Log)
500 INTERNAL_ERROR Unerwartete Ausnahmen, DB-Verbindungsfehler Error-Log mit Stack-Trace + Request-ID

Wichtig: Bei 500-Fehlern loggt der Server den vollständigen Stack-Trace — die API-Response enthält aber nur eine generische Meldung ohne interne Details. Das verhindert Information Leakage.

API Security: Was Claude Code automatisch berücksichtigt

Wenn du Claude Code explizit auf Sicherheit hinweist ("implementiere mit Security Best Practices"), berücksichtigt es standardmäßig folgende Patterns:

SQL Injection Prevention

Ausschließlich parametrisierte Queries oder Query-Builder (Prisma, Drizzle). Niemals String-Konkatenation für SQL. Raw-Queries nur mit expliziten Platzhaltern.

Input Sanitization

Zod-Schemas mit strikten Constraints als erste Verteidigungslinie. Keine Rohdaten aus req.body direkt in DB-Queries. Whitelist statt Blacklist.

JWT Validation Patterns

Signatur-Verifizierung, Ablaufzeit-Check, Audience/Issuer-Validierung. Token-Rotation-Support. Kein Speichern von Secrets im Code.

Rate Limiting + CORS

Endpoint-spezifische Rate Limits (Auth-Endpoints strenger). CORS-Whitelist statt Wildcard. Security-Header via Helmet.js.

Praxis-Tipp: Füge "OWASP Top 10 berücksichtigen" zu jedem API-Prompt hinzu. Claude Code kennt die aktuellen OWASP-Richtlinien und passt den Code entsprechend an — inklusive Output-Encoding und sicherem Session-Management.

Prompt-Template für komplette API-Entwicklung

Dieses Template hat sich in der Praxis für vollständige Backend-API-Entwicklung mit Claude Code bewährt. Kopiere es und ersetze die Platzhalter:

"Implementiere eine vollständige REST API für [RESSOURCE] in [TECH-STACK].

Stack: Node.js, TypeScript, Express 5, Zod, [Drizzle ORM / Prisma] mit PostgreSQL.

Ressource: [FELDER UND TYPEN, z.B. id: UUID, name: string, ...)

Endpoints:
- GET /api/[ressource] — paginated list, filter by [FELDER]
- GET /api/[ressource]/:id — single item
- POST /api/[ressource] — create, requires JWT Auth
- PUT /api/[ressource]/:id — update, requires JWT + owner check
- DELETE /api/[ressource]/:id — soft delete, requires JWT + admin role

Anforderungen:
- Zod-Schemas für alle Inputs, TypeScript-Types abgeleitet
- Strukturierte Error-Responses: { code, message, details? }
- 400/401/403/404/429/500 alle explizit behandelt
- JWT-Middleware mit Rollen (user, admin)
- Rate Limiting: 100 req/min global, 10 req/min auf POST
- OWASP Top 10 berücksichtigen
- Jest + Supertest Integration Tests für alle Endpoints und Edge Cases
- asyncHandler wrapper, kein try/catch in Route-Handlern

Erstelle: routes/, schemas/, middleware/, tests/, types/. Kein DB-Setup, nur die API-Schicht."

Warum dieses Template funktioniert

Der Prompt spezifiziert Constraints statt Implementierungsdetails. Claude Code entscheidet dann selbst, welches Zod-Refinement für den Email-Check am sinnvollsten ist — du gibst die Grenzen vor, nicht den Weg. Das Ergebnis ist konsistenter Code, weil Claude Code alle Entscheidungen im gleichen Kontext trifft, nicht schrittweise in isolierten Prompts.

Wichtig: Bei komplexen APIs immer den gesamten Kontext im ersten Prompt liefern — Nachträgliche Ergänzungen ("füge noch Rate Limiting hinzu") führen oft zu inkonsistentem Code, weil der ursprüngliche Design-Kontext verloren geht.

REST API Entwicklung auf das nächste Level bringen

Sieh selbst, wie schnell du vollständige, typsichere Endpoints mit Claude Code umsetzt — starte kostenlos und bring deine erste API in unter einer Stunde live.

Jetzt kostenlos starten →

Kein Kreditkarte. Kein Setup-Aufwand. Direkt loslegen.