☁ Serverless & Cloud

AWS Lambda Node.js mit Claude Code: Serverless 2026

AWS Lambda mit Node.js und Claude Code: Handler, SAM, API Gateway, DynamoDB, Layers, Cold Starts und CDK-Deployment — vollständiger Serverless-Guide 2026.

📅 6. Mai 2026 ⏱ 12 min Lesezeit ✍ SpockyMagicAI Team Lambda DynamoDB CDK

Serverless ist 2026 kein Hype mehr — es ist Standard. AWS Lambda mit Node.js ist die meistgenutzte Kombination für Event-driven Backends, API-Microservices und datengetriebene Cloud-Pipelines. Claude Code beschleunigt den Aufbau erheblich: von der Handler-Struktur über SAM-Templates bis zum CDK-Stack generiert der KI-Assistent produktionsreifen TypeScript-Code, erkennt Anti-Patterns und schlägt Optimierungen für Cold Starts vor. Dieser Guide zeigt, wie du das Potenzial dieser Kombination voll ausschöpfst.

1. Lambda Basics — Handler-Struktur, Event/Context, AWS SDK v3

Jede Lambda-Function beginnt mit einem Handler — einer exportierten Funktion, die von der AWS-Runtime aufgerufen wird. Mit TypeScript und dem Paket @types/aws-lambda erhältst du vollständige Typsicherheit für alle Event-Typen.

Lambda

Handler-Signatur und Event-Typen

Claude Code erkennt automatisch, welcher Event-Typ (APIGatewayProxyEvent, SQSEvent, S3Event …) zum Trigger passt und generiert die passende Typsignatur.

TypeScript src/handler.ts
// npm install --save-dev @types/aws-lambda @types/node typescript
// npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

import {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
  Context,
} from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

// Client außerhalb des Handlers — bleibt über Warm-Invocations erhalten
const dynamo = new DynamoDBClient({ region: 'eu-central-1' });
const db = DynamoDBDocumentClient.from(dynamo);

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context
): Promise<APIGatewayProxyResult> => {
  // context.callbackWaitsForEmptyEventLoop = false verhindert Timeouts bei DB-Pools
  context.callbackWaitsForEmptyEventLoop = false;

  const userId = event.pathParameters?.id;
  if (!userId) {
    return {
      statusCode: 400,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ error: 'Fehlende userId' }),
    };
  }

  try {
    const { Item } = await db.send(
      new GetCommand({
        TableName: process.env.TABLE_NAME!,
        Key: { pk: userId },
      })
    );

    return Item
      ? { statusCode: 200, body: JSON.stringify(Item) }
      : { statusCode: 404, body: JSON.stringify({ error: 'Nicht gefunden' }) };
  } catch (err) {
    console.error('DynamoDB-Fehler:', err);
    return { statusCode: 500, body: JSON.stringify({ error: 'Interner Fehler' }) };
  }
};

AWS SDK v3 — Modular

Nur verwendete Clients importieren — kleineres Bundle, schnellere Cold Starts. @aws-sdk/client-dynamodb statt des monolithischen v2 aws-sdk.

Context-Objekt nutzen

context.remainingTimeInMillis() für Timeout-Guards, context.functionVersion für Blue/Green-Deployments, context.awsRequestId für Tracing.

Environment Variables

Alle Konfigurationen (TABLE_NAME, QUEUE_URL) via process.env — nie hartcodiert. Claude Code prüft fehlende Env-Vars und schlägt IAM-Permissions vor.

TypeScript Build

tsc --outDir dist + esbuild für optimale Bundle-Größe. Claude Code generiert tsconfig.json mit target: ES2022 für Node.js 22 Runtime.

Claude Code Workflow-Tipp

Schreibe "Erstelle einen Lambda-Handler für SQS-Batch-Processing mit Dead Letter Queue Handling und TypeScript" — Claude Code generiert Handler, IAM-Policy und SAM-Template in einem Schritt.

2. SAM und lokale Entwicklung — template.yaml, sam local start-api, sam build/deploy

Das AWS Serverless Application Model (SAM) ist der De-facto-Standard für Lambda-Deployments. Claude Code generiert vollständige template.yaml-Dateien inklusive IAM-Roles, API Gateway-Definitionen und DynamoDB-Tabellen — und versteht den Unterschied zwischen SAM-Transform und rohem CloudFormation.

SAM

Vollständiges template.yaml mit Globals und lokaler Entwicklung

YAML template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: nodejs22.x
    Architectures: [arm64]          # Graviton2 — 20% günstiger, schneller
    Timeout: 29
    MemorySize: 512
    Environment:
      Variables:
        TABLE_NAME: !Ref UsersTable
        NODE_OPTIONS: '--enable-source-maps'
    Layers:
      - !Ref SharedDepsLayer

Parameters:
  Stage:
    Type: String
    Default: dev
    AllowedValues: [dev, staging, prod]

Resources:
  UsersApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      Cors:
        AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
        AllowHeaders: "'Content-Type,Authorization'"
        AllowOrigin: "'*'"
      Auth:
        DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !GetAtt UserPool.Arn

  GetUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handler.handler
      CodeUri: ./
      Events:
        GetUser:
          Type: Api
          Properties:
            RestApiId: !Ref UsersApi
            Path: /users/{id}
            Method: GET
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref UsersTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2022
        Sourcemap: true
        EntryPoints: [src/handler.ts]

  UsersTable:
    Type: AWS::DynamoDB::Table
    DeletionPolicy: Retain
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE

  SharedDepsLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: !Sub shared-deps-${Stage}
      ContentUri: layers/shared/
      CompatibleRuntimes: [nodejs22.x]
      CompatibleArchitectures: [arm64]
    Metadata:
      BuildMethod: nodejs22.x

Outputs:
  ApiUrl:
    Value: !Sub 'https://${UsersApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}'

Lokale Entwicklung mit SAM CLI

Bash Terminal
# Build (TypeScript → JavaScript via esbuild)
sam build --parallel --cached

# Lokale API starten (Port 3000, Hot-Reload via --watch)
sam local start-api --env-vars env.json --warm-containers EAGER

# Einzelne Function invoiken mit Test-Event
sam local invoke GetUserFunction \
  --event events/get-user.json \
  --env-vars env.json

# Deployment nach AWS (guided für erste Ausführung)
sam deploy --guided --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND

# Schnell-Deploy ohne Rückfragen
sam deploy --stack-name my-api-dev --parameter-overrides Stage=dev
JSON events/get-user.json
{
  "httpMethod": "GET",
  "path": "/users/user-123",
  "pathParameters": { "id": "user-123" },
  "headers": { "Authorization": "Bearer test-token" },
  "requestContext": {
    "authorizer": { "claims": { "sub": "user-123" } }
  }
}
⚡ Cold Start Optimierung

Arm64/Graviton2 reduziert Cold Starts um ~15–20%. --warm-containers EAGER bei sam local start-api simuliert Warm-Invocations. Für Produktion: Provisioned Concurrency für kritische Endpoints.

3. API Gateway Integration — REST vs HTTP API, CORS, Authorizer, Event-Mapping

AWS bietet zwei API-Gateway-Varianten: REST API (v1, mehr Features) und HTTP API (v2, günstiger und schneller). Claude Code empfiehlt die richtige Variante je nach Use Case und generiert vollständige Event-Mapping-Logik.

Feature REST API (v1) HTTP API (v2)
Latenz~10ms overhead~1ms overhead
Preis$3.50 / Mio Requests$1.00 / Mio Requests
WAF Integration✅ AWS WAF❌ Nicht verfügbar
Usage Plans✅ API Keys
Cognito JWT AuthCustom Authorizer✅ Native JWT Auth
Lambda Proxyv1 Formatv2 Format (einfacher)
API Gateway v2

HTTP API mit JWT Authorizer (empfohlen für neue Projekte)

TypeScript src/http-api-handler.ts
import {
  APIGatewayProxyEventV2WithJWTAuthorizer,
  APIGatewayProxyResultV2,
} from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEventV2WithJWTAuthorizer
): Promise<APIGatewayProxyResultV2> => {
  // JWT Claims direkt verfügbar (kein Custom Authorizer nötig)
  const userId = event.requestContext.authorizer.jwt.claims.sub as string;
  const userEmail = event.requestContext.authorizer.jwt.claims.email as string;

  // HTTP API v2 Event — saubere Struktur
  const { method, path } = event.requestContext.http;
  const body = event.body ? JSON.parse(event.body) : {};

  console.log(`${method} ${path} by ${userId}`);

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN || '*',
    },
    body: JSON.stringify({ userId, userEmail, received: body }),
  };
};

CORS-Konfiguration und Preflight

TypeScript src/cors-middleware.ts
const corsHeaders = {
  'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN || 'https://agentic-movers.com',
  'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Requested-With',
  'Access-Control-Max-Age': '86400',
};

// Preflight-Handler (OPTIONS) — separater Lambda oder kombiniert
export const withCors = (handler: Function) => async (event: any) => {
  if (event.requestContext?.http?.method === 'OPTIONS') {
    return { statusCode: 204, headers: corsHeaders, body: '' };
  }
  const result = await handler(event);
  return { ...result, headers: { ...result.headers, ...corsHeaders } };
};

4. DynamoDB — DocumentClient, PutCommand, GetCommand, QueryCommand

DynamoDB ist die native Datenbank für Lambda-Backends: serverless, automatisch skalierend, millisekunden-schnell. Das AWS SDK v3 DynamoDBDocumentClient abstrahiert das Low-Level-Marshalling von AttributeValues — Claude Code generiert typsichere Wrapper für alle CRUD-Operationen.

DynamoDB

Repository-Pattern mit DocumentClient

TypeScript src/users-repository.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  QueryCommand,
  UpdateCommand,
  DeleteCommand,
  TransactWriteCommand,
} from '@aws-sdk/lib-dynamodb';

interface User {
  pk: string;           // Partition Key: "USER#userId"
  sk: string;           // Sort Key: "PROFILE"
  email: string;
  name: string;
  createdAt: string;    // ISO8601
  ttl?: number;         // Unix-Timestamp für automatisches Löschen
}

class UsersRepository {
  private db: DynamoDBDocumentClient;
  private tableName: string;

  constructor() {
    const client = new DynamoDBClient({
      region: process.env.AWS_REGION || 'eu-central-1',
      maxAttempts: 3,
    });
    this.db = DynamoDBDocumentClient.from(client, {
      marshallOptions: { removeUndefinedValues: true },
    });
    this.tableName = process.env.TABLE_NAME!;
  }

  async getUser(userId: string): Promise<User | null> {
    const { Item } = await this.db.send(
      new GetCommand({
        TableName: this.tableName,
        Key: { pk: `USER#${userId}`, sk: 'PROFILE' },
        ConsistentRead: false,  // Eventually consistent = günstiger
      })
    );
    return (Item as User) || null;
  }

  async createUser(user: Omit<User, 'pk' | 'sk' | 'createdAt'>): Promise<User> {
    const now = new Date().toISOString();
    const item: User = {
      ...user,
      pk: `USER#${crypto.randomUUID()}`,
      sk: 'PROFILE',
      createdAt: now,
    };

    await this.db.send(
      new PutCommand({
        TableName: this.tableName,
        Item: item,
        // Conditional Write — kein Überschreiben existierender User
        ConditionExpression: 'attribute_not_exists(pk)',
      })
    );
    return item;
  }

  async queryUsersByEmail(email: string): Promise<User[]> {
    const { Items } = await this.db.send(
      new QueryCommand({
        TableName: this.tableName,
        IndexName: 'EmailIndex',
        KeyConditionExpression: 'email = :email',
        ExpressionAttributeValues: { ':email': email },
        Limit: 10,
        ScanIndexForward: false,  // Neueste zuerst
      })
    );
    return (Items as User[]) || [];
  }
}

export const usersRepo = new UsersRepository();
Single-Table Design mit Claude Code

Claude Code versteht Single-Table Design-Patterns (pk/sk-Komposition, GSIs, Adjacency Lists) und generiert auf Wunsch das komplette Datenmodell inklusive Access Pattern Matrix für typische CRUD- und Relationship-Queries.

5. Lambda Layers — Shared Dependencies, Layer ARN, Runtime-spezifische Layers

Lambda Layers ermöglichen das Auslagern gemeinsamer Dependencies aus dem Function-Code-Package. Statt jede Function mit node_modules (100+ MB) zu deployen, laden alle Functions einen gemeinsamen Layer — schnellere Deployments, kleinere Packages, konsistente Versionen.

Layer

Layer-Struktur und Deployment

Bash Layer Build Script
#!/bin/bash
# Layer-Verzeichnis muss nodejs/node_modules Struktur haben

mkdir -p layers/shared/nodejs
cd layers/shared/nodejs

# Nur Production Dependencies
cat > package.json <<EOF
{
  "dependencies": {
    "@aws-sdk/client-dynamodb": "^3.600.0",
    "@aws-sdk/lib-dynamodb": "^3.600.0",
    "@aws-sdk/client-s3": "^3.600.0",
    "zod": "^3.23.0",
    "date-fns": "^3.6.0"
  }
}
EOF

npm install --omit=dev --platform=linux --arch=arm64

# Layer zippen
cd ../../..
zip -r layers/shared-layer.zip layers/shared/

# Layer publizieren
LAYER_ARN=$(aws lambda publish-layer-version \
  --layer-name shared-deps \
  --zip-file fileb://layers/shared-layer.zip \
  --compatible-runtimes nodejs22.x \
  --compatible-architectures arm64 \
  --query LayerVersionArn \
  --output text)

echo "Layer ARN: $LAYER_ARN"
TypeScript src/layer-usage.ts
// Layer-Module werden genauso importiert wie lokale node_modules
// Lambda mountet Layer unter /opt/nodejs/node_modules
import { z } from 'zod';             // aus Layer
import { format } from 'date-fns';  // aus Layer

const UserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2).max(100),
  birthDate: z.string().datetime().optional(),
});

export type UserInput = z.infer<typeof UserSchema>;

export const validateUser = (data: unknown): UserInput => {
  return UserSchema.parse(data);
};
Layer-Typ Inhalt Update-Frequenz
Shared DependenciesAWS SDK, Lodash, Zod, date-fnsSelten (monatlich)
Business LogicInterne Utilities, FormattersWöchentlich
ML ModelsONNX, TFLite, EmbeddingsPer Release
Custom RuntimeNode.js binary, native addonsSehr selten
Layer-Größenlimits

Max 5 Layer pro Function, gesamt max 250 MB (entpackt). node_modules vorher prüfen: du -sh layers/shared/nodejs/node_modules/*. Claude Code schlägt automatisch Layer-Splitting vor wenn das Limit näher kommt.

6. CDK Deployment — aws-cdk-lib, LambdaFunction, RestApi, DynamoDBTable, Stack

Das AWS Cloud Development Kit (CDK) ist die mächtigste Option für komplexe Serverless-Infrastrukturen. Infrastructure as Code in TypeScript — Claude Code generiert vollständige CDK-Stacks inklusive IAM, Monitoring, Alarms und CI/CD-Pipelines.

CDK

Vollständiger CDK-Stack mit Lambda, API Gateway und DynamoDB

TypeScript lib/api-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNode from 'aws-cdk-lib/aws-lambda-nodejs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { Construct } from 'constructs';

interface ApiStackProps extends cdk.StackProps {
  stage: 'dev' | 'staging' | 'prod';
}

export class ApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: ApiStackProps) {
    super(scope, id, props);

    // DynamoDB Table mit Backup und Point-in-Time Recovery
    const table = new dynamodb.TableV2(this, 'UsersTable', {
      partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
      billing: dynamodb.Billing.onDemand(),
      pointInTimeRecovery: props.stage === 'prod',
      removalPolicy: props.stage === 'prod'
        ? cdk.RemovalPolicy.RETAIN
        : cdk.RemovalPolicy.DESTROY,
      globalSecondaryIndexes: [{
        indexName: 'EmailIndex',
        partitionKey: { name: 'email', type: dynamodb.AttributeType.STRING },
        projectionType: dynamodb.ProjectionType.ALL,
      }],
      timeToLiveAttribute: 'ttl',
    });

    // Shared Layer
    const sharedLayer = new lambda.LayerVersion(this, 'SharedLayer', {
      code: lambda.Code.fromAsset('layers/shared'),
      compatibleRuntimes: [lambda.Runtime.NODEJS_22_X],
      compatibleArchitectures: [lambda.Architecture.ARM_64],
    });

    // Lambda Function mit NodejsFunction (esbuild integriert)
    const getUserFn = new lambdaNode.NodejsFunction(this, 'GetUser', {
      entry: 'src/handler.ts',
      handler: 'handler',
      runtime: lambda.Runtime.NODEJS_22_X,
      architecture: lambda.Architecture.ARM_64,
      memorySize: 512,
      timeout: cdk.Duration.seconds(29),
      layers: [sharedLayer],
      environment: {
        TABLE_NAME: table.tableName,
        STAGE: props.stage,
      },
      bundling: {
        minify: true,
        sourceMap: true,
        target: 'es2022',
        externalModules: ['@aws-sdk/*'],  // In Lambda Runtime vorhanden
      },
    });

    // IAM Permissions
    table.grantReadData(getUserFn);

    // API Gateway REST API
    const api = new apigateway.RestApi(this, 'UsersApi', {
      restApiName: `users-api-${props.stage}`,
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
      },
      deployOptions: {
        stageName: props.stage,
        metricsEnabled: true,
        tracingEnabled: true,   // AWS X-Ray
      },
    });

    const users = api.root.addResource('users');
    const userById = users.addResource('{id}');
    userById.addMethod('GET',
      new apigateway.LambdaIntegration(getUserFn)
    );

    // CloudWatch Alarm für Fehlerrate
    new cloudwatch.Alarm(this, 'ErrorAlarm', {
      metric: getUserFn.metricErrors({ period: cdk.Duration.minutes(5) }),
      threshold: 10,
      evaluationPeriods: 2,
      alarmDescription: 'Lambda errors > 10 in 5 min',
    });

    // Outputs
    new cdk.CfnOutput(this, 'ApiUrl', {
      value: api.url,
      exportName: `api-url-${props.stage}`,
    });
  }
}
Bash CDK Commands
# CDK installieren
npm install -g aws-cdk
npm install aws-cdk-lib constructs

# Bootstrap (einmalig pro Account/Region)
cdk bootstrap aws://ACCOUNT_ID/eu-central-1

# Diff anzeigen (was ändert sich?)
cdk diff ApiStack-dev

# Deploy
cdk deploy ApiStack-dev --require-approval never

# Alle Stacks deployen
cdk deploy --all

# Stack löschen
cdk destroy ApiStack-dev
CDK vs SAM — Wann was?

SAM: Einfache Lambda-APIs, schnelle Prototypen, lokale Entwicklung (sam local). CDK: Komplexe Multi-Stack-Infrastrukturen, wiederverwendbare Constructs, CI/CD-Pipelines, Teams die TypeScript bevorzugen. Claude Code kann zwischen beiden wechseln und generiert auf Anfrage Migrationsskripte.

Serverless mit KI-Unterstützung entwickeln

Claude Code versteht AWS-Infrastruktur, generiert produktionsreifen TypeScript-Code und optimiert deine Lambda-Functions für Performance und Kosten — teste es kostenlos.

Kostenlos starten →