Use Cases

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

TaskClaude Code’s Contribution
Handler implementationGenerate type definitions, error handling, and logic in one shot
SAM templateAuto-output events, IAM, and environment variables
IAM policyAccurately generate least-privilege designs
Local testingAutomate sam invoke execution and result evaluation
DeploymentExecute 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.

References

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

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

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.

Masa

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.