Claude Code × AWS Lambda Complete Guide | From Function Generation to Deployment Automation
Develop AWS Lambda functions at lightning speed with Claude Code. Full walkthrough with real code examples: handler generation, IAM policy design, SAM deployment automation, API Gateway/S3/DynamoDB integrations.
Have you ever experienced this with AWS Lambda development? Type definitions for handlers are tedious, you have to look up IAM policies every time, you forgot how to write SAM templates… Claude Code solves all of these at once.
From Lambda function implementation to IAM policy generation, local testing, and production deployment — we’ll walk through every step of blazing-fast AWS Lambda development with Claude Code using real code examples.
Why Claude Code × AWS Lambda?
The “tedious parts” of Lambda development are almost entirely boilerplate.
- Handler function type definitions (APIGatewayProxyHandler, S3Handler …)
- Error handling and response formats
- Least-privilege IAM policy design
- SAM / CloudFormation template writing
- Local test environment configuration
Claude Code generates all of these from a single sentence like “I want a Lambda that does X”. Combined with the AWS CLI and SAM CLI, you can run everything from code generation to deployment in one go.
Environment Setup
# Verify required tools
aws --version # AWS CLI v2
sam --version # SAM CLI 1.100+
node --version # Node.js 20+
# Configure AWS credentials (if not already set)
aws configure
# → Access Key ID, Secret Access Key, Region (us-east-1), Output (json)
<!-- Add to CLAUDE.md -->
## AWS Configuration
- Default region: us-east-1
- Lambda runtime: nodejs20.x
- Deployment tool: AWS SAM
- Source directory: src/functions/
- SAM template: template.yaml
- Stack name: my-app-{dev|staging|prod}
## IAM Policy Guidelines
- Strictly follow the principle of least privilege
- Do not use wildcards (*)
- Specify resource ARNs concretely
Example 1: API Gateway + Lambda (REST API)
The most fundamental pattern — a Lambda that receives HTTP requests and processes them.
Instructions to Claude Code
claude -p "
When a GET request comes to /users/{userId},
retrieve user information from the DynamoDB Users table and return it.
Implement the Lambda function in TypeScript.
- Runtime: Node.js 20
- Table name: UsersTable (received via environment variable)
- Error handling: return appropriate 404/500 responses
- Type definitions: use @types/aws-lambda
- Output to: src/functions/getUser/index.ts
"
Generated Implementation
// src/functions/getUser/index.ts
import {
APIGatewayProxyHandler,
APIGatewayProxyResult,
} from "aws-lambda";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({ region: process.env.AWS_REGION });
const ddb = DynamoDBDocumentClient.from(client);
const response = (statusCode: number, body: unknown): APIGatewayProxyResult => ({
statusCode,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify(body),
});
export const handler: APIGatewayProxyHandler = async (event) => {
const userId = event.pathParameters?.userId;
if (!userId) {
return response(400, { error: "userId is required" });
}
try {
const result = await ddb.send(
new GetCommand({
TableName: process.env.USERS_TABLE!,
Key: { userId },
})
);
if (!result.Item) {
return response(404, { error: `User '${userId}' not found` });
}
return response(200, result.Item);
} catch (err) {
console.error("DynamoDB error:", err);
return response(500, { error: "Internal server error" });
}
};
SAM Template Also Auto-Generated
# template.yaml (excerpt)
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/functions/getUser/index.handler
Runtime: nodejs20.x
Timeout: 10
Environment:
Variables:
USERS_TABLE: !Ref UsersTable
Policies:
- DynamoDBReadPolicy:
TableName: !Ref UsersTable
Events:
GetUserApi:
Type: Api
Properties:
Path: /users/{userId}
Method: GET
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub "${AWS::StackName}-users"
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
Example 2: S3 Event Trigger Lambda
A Lambda triggered by file uploads to run processing.
// src/functions/generateThumbnail/index.ts
import { S3Handler } from "aws-lambda";
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import sharp from "sharp";
const s3 = new S3Client({ region: process.env.AWS_REGION });
export const handler: S3Handler = async (event) => {
const record = event.Records[0];
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " "));
// Skip thumbnails/ prefix (prevent infinite loop)
if (key.startsWith("thumbnails/")) return;
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const buffer = Buffer.from(await Body!.transformToByteArray());
const thumbnail = await sharp(buffer)
.resize(200, 200, { fit: "cover" })
.webp({ quality: 85 })
.toBuffer();
const thumbnailKey = `thumbnails/${key.replace(/\.[^.]+$/, ".webp")}`;
await s3.send(
new PutObjectCommand({
Bucket: bucket,
Key: thumbnailKey,
Body: thumbnail,
ContentType: "image/webp",
})
);
console.log(`Thumbnail created: s3://${bucket}/${thumbnailKey}`);
};
Example 3: Scheduled Lambda (EventBridge)
# EventBridge configuration in template.yaml
SendReminderFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/functions/sendReminder/index.handler
Runtime: nodejs20.x
Timeout: 300
Environment:
Variables:
DATABASE_URL: !Sub "{{resolve:secretsmanager:${AWS::StackName}/database-url}}"
RESEND_API_KEY: !Sub "{{resolve:secretsmanager:${AWS::StackName}/resend-api-key}}"
Events:
DailyReminder:
Type: Schedule
Properties:
Schedule: cron(0 0 * * ? *) # UTC 0:00 = EST 7:00 PM / PST 4:00 PM
Let Claude Code Design IAM Policies
claude -p "
Generate a minimum-privilege IAM policy in JSON for a Lambda that needs:
- GetObject from S3 bucket my-uploads
- PutItem/UpdateItem on DynamoDB table ProcessingJobs
- SendMessage to SQS ProcessingQueue
- Write to CloudWatch Logs
Infer specific ARNs from the resource names provided.
"
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-uploads/*"
},
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:us-east-1:*:table/ProcessingJobs"
},
{
"Effect": "Allow",
"Action": ["sqs:SendMessage"],
"Resource": "arn:aws:sqs:us-east-1:*:ProcessingQueue"
},
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
}
]
}
Local Testing and Production Deployment
# Build
sam build
# Start API locally
sam local start-api --port 3001
# One-off test invocation
sam local invoke GetUserFunction --event events/get-user.json
# Run everything with Claude Code
claude -p "
Run sam build,
verify results with sam local invoke GetUserFunction --event events/test-get-user.json.
If everything looks good, run sam deploy --config-env dev.
"
Top 5 Pitfalls
1. Initialization that ignores cold starts
// ❌ Creating a client inside the handler every time
export const handler = async () => {
const ddb = new DynamoDBClient({}); // ← instantiated on every invocation
};
// ✅ Initialize once at module scope
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler = async () => { /* reuse ddb */ };
2. Leaving the timeout at the default (3 seconds) For DynamoDB + external APIs, set at least 10-30 seconds. Always configure based on actual processing requirements.
3. Writing secrets directly in environment variables
# ❌ Hard-coded in the template
Environment:
Variables:
DB_PASSWORD: "my-secret"
# ✅ Via Secrets Manager
Environment:
Variables:
DB_PASSWORD: !Sub "{{resolve:secretsmanager:myapp/db-password}}"
4. Cold start delays with VPC Lambda Placing a Lambda inside a VPC for RDS connectivity adds several seconds to cold starts. Address with Provisioned Concurrency or RDS Proxy.
5. Oversized deployment packages
Packaging node_modules together can hit the 250 MB limit. Move shared libraries to a Lambda Layer.
Summary
| Task | Claude Code’s Contribution |
|---|---|
| Handler implementation | Generate type definitions, error handling, and logic in one shot |
| SAM template | Auto-output events, IAM, and environment variables |
| IAM policy | Accurately generate least-privilege designs |
| Local testing | Automate sam invoke execution and result evaluation |
| Deployment | Execute build + deploy as a single workflow |
Claude Code takes over the most time-consuming parts of Lambda development — type definitions and template writing. Focusing on business logic can push implementation speed up 3-5x.
Related Articles
- Claude Code × AWS S3 Operation Automation
- Claude Code × AWS DynamoDB Schema Design
- Claude Code Security Best Practices Complete Guide
References
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Just enter your email and we'll send you the single-page A4 cheatsheet right away.
We handle your data with care and never send spam.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
How to Slash New Engineer Onboarding Time with Claude Code
Turn a 3-month ramp-up into 2 weeks. Use Claude Code as the new hire's copilot for codebase, environment, and first PR.
How to Visualize and Systematically Reduce Tech Debt with Claude Code
Unpaid tech debt drains engineering velocity. Learn how to surface, prioritize, and repay it incrementally using Claude Code.
How to Set Up Dev Environments Instantly with Claude Code
New PC or new project, Claude Code handles dev environment setup in minutes. Node.js, Docker, lint configs, CI/CD — all automated.