Use Cases

Claude Code × AWS API Gateway Komplettleitfaden | REST API Design bis Deployment-Automatisierung

AWS API Gateway-Endpunkte automatisch mit Claude Code entwerfen. Ressourcendesign, Lambda-Integration, Authentifizierung und Deployment — mit echtem Code aus Masas Berufserfahrung.

Wie der API Gateway-Aufwand auf ein Drittel sank

Ich bin Masa und betreibe claudecode-lab.com. Als ich noch als freiberuflicher Backend-Entwickler tätig war, stand AWS API Gateway-Design und -Implementierung ganz oben auf der Liste der „mühsamen, zeitaufwändigen Aufgaben”. Endpunkt-Spezifikationen schreiben, in CloudFormation übersetzen, Lambda-Integrationen manuell verdrahten, CORS konfigurieren — und dabei immer wieder dieselben Fehler machen.

Der Wendepunkt kam vor etwa sechs Monaten, als ich Claude Code bat: „Entwirf eine REST API basierend auf diesem Use Case und implementiere sie in CDK.” Das Ergebnis war überraschend produktionsreif, und seitdem hat sich der Implementierungsaufwand für API Gateway auf ein Drittel reduziert.

CORS-Vergessen, Lambda-Berechtigungsfehler, die 29-Sekunden-Timeout-Falle — seit Claude Code diese Probleme frühzeitig abfängt, sind auch die Review-Befunde drastisch gesunken.

Dieser Artikel zeigt jeden Claude Code × API Gateway-Pattern, den ich tatsächlich in professionellen Projekten einsetze, Schritt für Schritt.


Warum Claude Code beim API Gateway-Design glänzt

API Gateway-Design hat eine besondere Eigenschaft: Es erfordert keine Tiefenkenntnisse, aber viel repetitiven Boilerplate.

  • Endpunkt-Namenskonventionen (Plural-Substantive, Hierarchiestruktur)
  • HTTP-Methodenauswahl (GET/POST/PUT/PATCH/DELETE)
  • Request-/Response-Schema-Definitionen
  • Lambda-Integration Pfadvariablen-Mapping
  • CORS-Header-Konfiguration (immer vergessen)
  • Stage-Variablen-Verwaltung (dev/staging/prod)

Diese folgen klaren Regeln. Jeder kann sie mit Dokumentation schreiben, aber Claude Code zu bitten „Entwirf eine REST API für diesen Use Case und schreibe sie in CDK” liefert in Minuten eine vollständige Implementierung.

Besonders stark ist Claude Code beim Design-Konsistenz-Check. „Weise auf REST-Prinzip-Verletzungen in diesem Endpunkt-Design hin” — und es flaggt sofort Idempotenz-Probleme, URL-Inkonsistenzen und mehr.


Schritt 1: REST API Endpunkt-Design an Claude Code delegieren

Bevor eine einzige Zeile Code geschrieben wird, übernimmt Claude Code das Endpunkt-Design.

Beispiel-Prompt

Erstelle eine REST API-Endpunkt-Spezifikation basierend auf folgenden Use Cases.

Use Cases:
- Task-Management-App (Benutzer verwalten Projekte und Aufgaben)
- Benutzerauthentifizierung erforderlich (Cognito)
- Projekte können Mitglieder haben
- Aufgaben unterstützen Kommentare und Dateianhänge

Ausgabeformat:
- Endpunktliste (Methode, Pfad, Beschreibung)
- Request-/Response-JSON-Schemas
- Kennzeichnung authentifizierungspflichtiger Endpunkte
- REST-Prinzip-Verletzungen markieren

Generiertes Endpunkt-Design

# Task-Management API Endpunkt-Design

## Authentifizierung
POST   /auth/login          - Anmelden (öffentlich)
POST   /auth/refresh        - Token aktualisieren (öffentlich)
DELETE /auth/logout         - Abmelden (Auth erforderlich)

## Projekte
GET    /projects            - Projektliste (Auth erforderlich)
POST   /projects            - Projekt erstellen (Auth erforderlich)
GET    /projects/{id}       - Projektdetail (Auth erforderlich)
PUT    /projects/{id}       - Projekt aktualisieren (Auth erforderlich)
DELETE /projects/{id}       - Projekt löschen (Auth erforderlich)

## Projektmitglieder
GET    /projects/{id}/members       - Mitgliederliste
POST   /projects/{id}/members       - Mitglied hinzufügen
DELETE /projects/{id}/members/{uid} - Mitglied entfernen

## Aufgaben
GET    /projects/{id}/tasks         - Aufgabenliste
POST   /projects/{id}/tasks         - Aufgabe erstellen
GET    /tasks/{taskId}              - Aufgabendetail
PUT    /tasks/{taskId}              - Aufgabe aktualisieren
DELETE /tasks/{taskId}             - Aufgabe löschen

## Kommentare
GET    /tasks/{taskId}/comments     - Kommentarliste
POST   /tasks/{taskId}/comments     - Kommentar erstellen
DELETE /tasks/{taskId}/comments/{commentId} - Kommentar löschen

## Anhänge
POST   /tasks/{taskId}/attachments  - Presigned URL abrufen (für direkten S3-Upload)
GET    /tasks/{taskId}/attachments  - Anhängeliste
DELETE /tasks/{taskId}/attachments/{attachmentId} - Anhang löschen

Schritt 2: Lambda-Integrationskonfiguration automatisch generieren

CDK-Stack (Auszug)

import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

export class TaskApiStack extends cdk.Stack {
  public readonly api: apigateway.RestApi;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ===== Lambda-Funktions-Factory =====
    const createLambda = (name: string, handler: string) =>
      new nodejs.NodejsFunction(this, name, {
        entry: `src/handlers/${handler}.ts`,
        handler: "handler",
        runtime: lambda.Runtime.NODEJS_20_X,
        environment: {
          STAGE: props?.env?.region ?? "dev",
        },
        timeout: cdk.Duration.seconds(29), // API Gateway max. Timeout
        memorySize: 256,
        bundling: {
          minify: true,
          sourceMap: true,
          externalModules: ["@aws-sdk/*"],
        },
      });

    const listProjectsFn = createLambda("ListProjects", "projects/list");
    const createProjectFn = createLambda("CreateProject", "projects/create");
    const getProjectFn = createLambda("GetProject", "projects/get");
    const updateProjectFn = createLambda("UpdateProject", "projects/update");
    const deleteProjectFn = createLambda("DeleteProject", "projects/delete");
    const listTasksFn = createLambda("ListTasks", "tasks/list");
    const createTaskFn = createLambda("CreateTask", "tasks/create");
    const getTaskFn = createLambda("GetTask", "tasks/get");
    const updateTaskFn = createLambda("UpdateTask", "tasks/update");
    const deleteTaskFn = createLambda("DeleteTask", "tasks/delete");

    // ===== REST API-Definition =====
    this.api = new apigateway.RestApi(this, "TaskApi", {
      restApiName: "task-management-api",
      description: "Task Management REST API",
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS, // in Produktion einschränken
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ["Content-Type", "Authorization", "X-Api-Key"],
      },
      deployOptions: {
        stageName: "v1",
        accessLogDestination: new apigateway.LogGroupLogDestination(
          new cdk.aws_logs.LogGroup(this, "ApiAccessLog", {
            retention: cdk.aws_logs.RetentionDays.ONE_MONTH,
          })
        ),
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        dataTraceEnabled: false,
      },
    });

    // ===== Ressourcen & Methoden =====
    const projects = this.api.root.addResource("projects");
    projects.addMethod("GET", new apigateway.LambdaIntegration(listProjectsFn));
    projects.addMethod("POST", new apigateway.LambdaIntegration(createProjectFn));

    const project = projects.addResource("{id}");
    project.addMethod("GET", new apigateway.LambdaIntegration(getProjectFn));
    project.addMethod("PUT", new apigateway.LambdaIntegration(updateProjectFn));
    project.addMethod("DELETE", new apigateway.LambdaIntegration(deleteProjectFn));

    const projectTasks = project.addResource("tasks");
    projectTasks.addMethod("GET", new apigateway.LambdaIntegration(listTasksFn));
    projectTasks.addMethod("POST", new apigateway.LambdaIntegration(createTaskFn));

    const tasks = this.api.root.addResource("tasks");
    const task = tasks.addResource("{taskId}");
    task.addMethod("GET", new apigateway.LambdaIntegration(getTaskFn));
    task.addMethod("PUT", new apigateway.LambdaIntegration(updateTaskFn));
    task.addMethod("DELETE", new apigateway.LambdaIntegration(deleteTaskFn));

    new cdk.CfnOutput(this, "ApiUrl", {
      value: this.api.url,
      description: "API Gateway URL",
    });
  }
}

Schritt 3: Authentifizierung (Cognito / Lambda Authorizer / API Key)

Muster 1: Cognito User Pool Authorizer (BtoC)

import * as cognito from "aws-cdk-lib/aws-cognito";

const userPool = new cognito.UserPool(this, "UserPool", {
  userPoolName: "task-app-users",
  selfSignUpEnabled: true,
  signInAliases: { email: true },
  passwordPolicy: {
    minLength: 8,
    requireLowercase: true,
    requireUppercase: true,
    requireDigits: true,
    requireSymbols: false,
  },
  accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
});

const cognitoAuthorizer = new apigateway.CognitoUserPoolsAuthorizer(
  this,
  "CognitoAuthorizer",
  {
    cognitoUserPools: [userPool],
    identitySource: "method.request.header.Authorization",
    resultsCacheTtl: cdk.Duration.minutes(5),
  }
);

projects.addMethod("GET", new apigateway.LambdaIntegration(listProjectsFn), {
  authorizer: cognitoAuthorizer,
  authorizationType: apigateway.AuthorizationType.COGNITO,
});

Muster 2: Lambda Authorizer

// src/handlers/auth/authorizer.ts
import {
  APIGatewayAuthorizerResult,
  APIGatewayTokenAuthorizerHandler,
} from "aws-lambda";
import * as jwt from "jsonwebtoken";

export const handler: APIGatewayTokenAuthorizerHandler = async (event) => {
  const token = event.authorizationToken.replace("Bearer ", "");

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
      sub: string;
      email: string;
      role: string;
    };

    return generatePolicy(decoded.sub, "Allow", event.methodArn, {
      userId: decoded.sub,
      email: decoded.email,
      role: decoded.role,
    });
  } catch {
    throw new Error("Unauthorized");
  }
};

function generatePolicy(
  principalId: string,
  effect: "Allow" | "Deny",
  resource: string,
  context?: Record<string, string>
): APIGatewayAuthorizerResult {
  return {
    principalId,
    policyDocument: {
      Version: "2012-10-17",
      Statement: [
        {
          Action: "execute-api:Invoke",
          Effect: effect,
          Resource: resource.replace(/\/[^/]+\/[^/]+$/, "/*/*"),
        },
      ],
    },
    context,
  };
}

Schritt 4: CDK-Infrastruktur-Implementierung

Stage-Konfigurationstrennung

// lib/config.ts
export type Stage = "dev" | "staging" | "prod";

export const stageConfig: Record<Stage, {
  logLevel: "ERROR" | "INFO" | "DEBUG";
  throttleRateLimit: number;
  throttleBurstLimit: number;
  corsOrigins: string[];
  enableDataTrace: boolean;
}> = {
  dev: {
    logLevel: "DEBUG",
    throttleRateLimit: 10,
    throttleBurstLimit: 20,
    corsOrigins: ["http://localhost:3000"],
    enableDataTrace: true,
  },
  staging: {
    logLevel: "INFO",
    throttleRateLimit: 50,
    throttleBurstLimit: 100,
    corsOrigins: ["https://staging.example.com"],
    enableDataTrace: false,
  },
  prod: {
    logLevel: "ERROR",
    throttleRateLimit: 1000,
    throttleBurstLimit: 2000,
    corsOrigins: ["https://example.com"],
    enableDataTrace: false,
  },
};

Deployment-Befehle

# Entwicklungsumgebung
CDK_ENV=dev npx cdk deploy Api-dev --require-approval never

# Staging
CDK_ENV=staging npx cdk deploy Api-staging

# Produktion (mit Bestätigung)
CDK_ENV=prod npx cdk deploy Api-prod

# Diff vor Produktion immer prüfen
CDK_ENV=prod npx cdk diff Api-prod

4 häufige Fallstricke

Fallstrick 1: Fehlende CORS-Konfiguration (häufigster Fehler)

Symptom: fetch() im Browser wirft einen CORS error. Postman funktioniert.

Ursache: Auch bei aktiviertem CORS in API Gateway muss Lambda (Proxy-Integration) CORS-Header in der Antwort zurückgeben.

// ❌ Unzureichend
defaultCorsPreflightOptions: {
  allowOrigins: apigateway.Cors.ALL_ORIGINS,
}

// ✅ CORS-Header immer in Lambda-Antworten setzen
return {
  statusCode: 200,
  headers: {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "https://example.com",
    "Access-Control-Allow-Credentials": "true",
  },
  body: JSON.stringify(data),
};

Fallstrick 2: Fehlende Lambda-Aufrufberechtigung

Symptom: API gibt {"message": "Internal server error"} zurück. CloudWatch-Logs zeigen AccessDeniedException.

// Berechtigung manuell hinzufügen wenn nötig
listProjectsFn.addPermission("ApiGatewayInvoke", {
  principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
  sourceArn: this.api.arnForExecuteApi("GET", "/projects", "v1"),
});

Fallstrick 3: Die 29-Sekunden-Timeout-Grenze

Symptom: Anfragen, die länger als 30 Sekunden dauern, scheitern plötzlich mit {"message": "Endpoint request timed out"}.

Ursache: Das Integrations-Timeout von API Gateway ist auf 29 Sekunden begrenzt. Dies kann nicht erhöht werden.

// ✅ Asynchrones Muster: Job-ID sofort zurückgeben, dann Status abfragen
export const startExport: APIGatewayProxyHandler = async (event) => {
  const jobId = crypto.randomUUID();

  await sqsClient.send(
    new SendMessageCommand({
      QueueUrl: process.env.JOB_QUEUE_URL!,
      MessageBody: JSON.stringify({ jobId, params: JSON.parse(event.body!) }),
    })
  );

  return {
    statusCode: 202,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ jobId, status: "processing" }),
  };
};

Fallstrick 4: Fehlender Request-Body in Lambda

Symptom: Lambda empfängt null für event.body.

// ✅ Proxy-Integration immer explizit setzen
project.addMethod(
  "POST",
  new apigateway.LambdaIntegration(createProjectFn, {
    proxy: true,
  })
);

Zusammenfassung

AufgabeClaude Code-MehrwertSchwierigkeit
REST API-DesignSpecs aus Use Cases generieren, REST-Prinzip-CheckNiedrig
Lambda-Integration CDKProxy-Integration + Ressourcen-Definitionen gebündeltNiedrig
Cognito-AuthUser Pool, Authorizer, Client-Config generierenMittel
Lambda AuthorizerJWT-Validierung + Policy-GenerierungMittel
Stage-VerwaltungEnv-Configs trennen, Deploy-Befehle generierenMittel
Asynchrone VerarbeitungSQS-Integration + Polling-MusterHoch
CORS-KonfigurationLambda-seitige Header ohne AuslassungenNiedrig

Praxisergebnis: API Gateway Design-to-CDK-Deploy-Zeit von 3 Tagen auf 1 Tag reduziert. Die Cognito Authorizer-Einrichtung, die früher über eine Stunde AWS-Doku-Lesen erforderte, löst sich jetzt mit einem einzigen Claude Code-Prompt.


Verwandte Artikel

Referenzen

#claude-code #aws #api-gateway #lambda #typescript #rest-api

Bring deinen Claude-Code-Workflow aufs nächste Level

50 in der Praxis erprobte Prompt-Vorlagen zum direkten Copy-and-paste in Claude Code.

Kostenlos

Kostenloses PDF: Claude-Code-Spickzettel in 5 Minuten

Trag einfach deine E-Mail-Adresse ein – wir senden dir den A4-Spickzettel als PDF sofort zu.

Wir behandeln deine Daten sorgfältig und senden niemals Spam.

Masa

Über den Autor

Masa

Ingenieur, der Claude Code intensiv nutzt. Betreibt claudecode-lab.com, ein Tech-Medium in 10 Sprachen mit über 2.000 Seiten.