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.
Handler-Signatur und Event-Typen
Claude Code erkennt automatisch, welcher Event-Typ (APIGatewayProxyEvent, SQSEvent, S3Event …) zum Trigger passt und generiert die passende Typsignatur.
// 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.
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.
Vollständiges template.yaml mit Globals und lokaler Entwicklung
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
# 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
{
"httpMethod": "GET",
"path": "/users/user-123",
"pathParameters": { "id": "user-123" },
"headers": { "Authorization": "Bearer test-token" },
"requestContext": {
"authorizer": { "claims": { "sub": "user-123" } }
}
}
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 Auth | Custom Authorizer | ✅ Native JWT Auth |
| Lambda Proxy | v1 Format | v2 Format (einfacher) |
HTTP API mit JWT Authorizer (empfohlen für neue Projekte)
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
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.
Repository-Pattern mit DocumentClient
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();
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-Struktur und Deployment
#!/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"
// 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 Dependencies | AWS SDK, Lodash, Zod, date-fns | Selten (monatlich) |
| Business Logic | Interne Utilities, Formatters | Wöchentlich |
| ML Models | ONNX, TFLite, Embeddings | Per Release |
| Custom Runtime | Node.js binary, native addons | Sehr selten |
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.
Vollständiger CDK-Stack mit Lambda, API Gateway und DynamoDB
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}`,
});
}
}
# 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
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 →