Use Cases (Updated: 6/2/2026)

Claude Code × AWS Lambda Complete Guide | From Function Generation to Deployment Automation

Build safer AWS Lambda apps with Claude Code: Node.js 24, SAM, IAM, API Gateway, S3, logs, and pitfalls.

Claude Code × AWS Lambda Complete Guide | From Function Generation to Deployment Automation

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 24+

# 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: nodejs24.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 24
- 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: nodejs24.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: nodejs24.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.

When Lambda Fits and When It Does Not

Lambda fits short, event-shaped work with clear input and output: order lookup APIs, image processing after S3 upload, daily EventBridge reports, and Stripe or GitHub webhooks. It is a weak fit for long-running workers, persistent low-latency connections, large warm models, and stateful WebSocket coordination. Ask Claude Code to explain both why Lambda fits and why it might not fit before it generates code.

Plain-Language Terms

TermPlain meaning
handlerThe function Lambda calls. API Gateway passes an HTTP event; S3 passes an object event.
eventThe JSON payload given to Lambda. API Gateway, S3, and EventBridge shapes are different.
least privilegeGrant only the exact actions and resources the function needs.
cold startThe slower first run when Lambda creates a new execution environment.
idempotencyThe same event can run twice without duplicate payment, email, or database writes.

Copy-Paste Unit Test for Node.js 24

// test/getUser.test.ts
import { describe, expect, it, vi } from "vitest";
import type { APIGatewayProxyEvent } from "aws-lambda";

const mockSend = vi.fn();
vi.mock("@aws-sdk/lib-dynamodb", async () => {
  const actual = await vi.importActual<typeof import("@aws-sdk/lib-dynamodb")>("@aws-sdk/lib-dynamodb");
  return { ...actual, DynamoDBDocumentClient: { from: () => ({ send: mockSend }) } };
});

const { handler } = await import("../src/functions/getUser/index");

function event(userId?: string): APIGatewayProxyEvent {
  return {
    pathParameters: userId ? { userId } : null,
    httpMethod: "GET",
    path: userId ? "/users/" + userId : "/users",
    headers: {},
    multiValueHeaders: {},
    queryStringParameters: null,
    multiValueQueryStringParameters: null,
    body: null,
    isBase64Encoded: false,
    requestContext: {} as APIGatewayProxyEvent["requestContext"],
    resource: "/users/{userId}",
    stageVariables: null,
  };
}

describe("getUser Lambda", () => {
  it("returns 200 when the user exists", async () => {
    process.env.USERS_TABLE = "UsersTable";
    mockSend.mockResolvedValueOnce({ Item: { userId: "u-1", name: "Masa" } });

    const res = await handler(event("u-1"), {} as never, vi.fn());

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).userId).toBe("u-1");
  });

  it("returns 400 when path parameter is missing", async () => {
    const res = await handler(event(), {} as never, vi.fn());
    expect(res.statusCode).toBe(400);
  });
});

Keep API Gateway, S3, and EventBridge Event Shapes Separate

API Gateway requires an HTTP response with statusCode and body. S3 handlers read bucket and key from Records. EventBridge handlers usually inspect source, detail-type, and detail. If you mix these types, local tests can pass while production events fail. Name the trigger explicitly in the Claude Code prompt: HTTP API v2, S3 ObjectCreated, or EventBridge schedule.

Environment Variables, Secrets Manager, and Least-Privilege IAM

Use environment variables for configuration such as USERS_TABLE and APP_STAGE. Store API keys, database passwords, and webhook signing secrets in Secrets Manager, then pass only the Secret ARN to Lambda. IAM should use specific actions such as dynamodb:GetItem and s3:GetObject with concrete resource ARNs. If Claude Code emits Resource: "*" or s3:*, run another least-privilege review.

CloudWatch Logs, Retries, and Idempotency

CloudWatch Logs is the first place to investigate Lambda behavior. If logs are missing, check region, function name, execution role permissions for logs:CreateLogStream and logs:PutLogEvents, and the deployed stack name. S3, EventBridge, and asynchronous invokes can retry, so use event IDs or business IDs as idempotency keys. Duplicate email, duplicate billing, and recursive writes to the same S3 key prefix are common Lambda failures.

Claude Code Security Review Prompt

Review this AWS Lambda change before production.
Check handler shape, API Gateway/S3/EventBridge event type, IAM least privilege, Secrets Manager usage, CloudWatch Logs, retries, idempotency, cold starts, timeout, memory, and cost risk.
Return blockers first, then recommended fixes, missing tests, and human approvals required before deploy.

Monetization, Training, and Consultation CTA

ClaudeCodeLab offers training and consultation for Lambda, IAM, SAM, log audits, and Claude Code review gates. Solo builders can start with the free PDF and Gumroad materials; teams shipping production workloads should use training and consultation to design permissions, deploy approvals, and revenue paths together.

Hands-On Result

I split the examples into a handler and tests that can be checked locally with npm test. I did not run sam deploy against a real AWS account because account ID, region, Secret ARN, and DynamoDB table names are environment-specific. In production, keep a verification note for sam validate --lint, sam build, sam local invoke, CloudWatch Logs, and IAM diff review.

References

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

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.