Claude Code for AWS Lambda: Functions, API Gateway, and Event Processing — Claude Skills 360 Blog
Blog / Development / Claude Code for AWS Lambda: Functions, API Gateway, and Event Processing
Development

Claude Code for AWS Lambda: Functions, API Gateway, and Event Processing

Published: June 13, 2026
Read time: 9 min read
By: Claude Skills 360

AWS Lambda is the dominant serverless platform, but its patterns are specific: handler structure, context object, cold start optimization, event source differences (API Gateway vs SQS vs SNS vs EventBridge), and the correct way to handle partial batch failures. Claude Code generates Lambda handlers that account for these platform-specific requirements.

This guide covers AWS Lambda with Claude Code: handler patterns, event source integration, cold start optimization, and CDK deployment.

Lambda Handler Patterns

Create a Lambda function that handles API Gateway events.
It should: validate input, call a database, and return proper HTTP responses.
Include proper error handling and structured logging.
// src/handlers/api-handler.ts
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
import { z } from 'zod';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';

// Initialize outside handler — reused across warm invocations
const ddbClient = new DynamoDBClient({ region: process.env.AWS_REGION });
const ddb = DynamoDBDocumentClient.from(ddbClient);

const createOrderInput = z.object({
  userId: z.string().uuid(),
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number().int().positive(),
  })).min(1),
});

export const handler: APIGatewayProxyHandlerV2 = async (event, context) => {
  // Structured logging with request ID for tracing
  const log = (level: string, msg: string, data?: object) =>
    console[level](JSON.stringify({
      level,
      message: msg,
      requestId: context.awsRequestId,
      ...data,
    }));

  log('info', 'Processing order request', {
    path: event.rawPath,
    method: event.requestContext.http.method,
  });

  try {
    // Parse and validate body
    const body = JSON.parse(event.body ?? '{}');
    const input = createOrderInput.safeParse(body);

    if (!input.success) {
      return {
        statusCode: 400,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          error: 'Validation failed',
          details: input.error.flatten().fieldErrors,
        }),
      };
    }

    // Process order
    const orderId = crypto.randomUUID();
    await ddb.send(new PutCommand({
      TableName: process.env.ORDERS_TABLE!,
      Item: {
        PK: `USER#${input.data.userId}`,
        SK: `ORDER#${orderId}`,
        orderId,
        userId: input.data.userId,
        items: input.data.items,
        status: 'PENDING',
        createdAt: new Date().toISOString(),
      },
      // Optimistic locking — prevent duplicate orders
      ConditionExpression: 'attribute_not_exists(PK)',
    }));

    log('info', 'Order created', { orderId, userId: input.data.userId });

    return {
      statusCode: 201,
      headers: {
        'Content-Type': 'application/json',
        'X-Request-Id': context.awsRequestId,
      },
      body: JSON.stringify({ orderId, status: 'PENDING' }),
    };

  } catch (error) {
    log('error', 'Order creation failed', {
      error: (error as Error).message,
      stack: (error as Error).stack,
    });

    return {
      statusCode: 500,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ error: 'Internal server error' }),
    };
  }
};

CLAUDE.md for Lambda Projects

## AWS Lambda Conventions
- Runtime: Node.js 20.x
- SDK: AWS SDK v3 (@aws-sdk/* packages — tree-shakeable)
- Initialize clients OUTSIDE handler (reuse across invocations)
- Structured JSON logging — every log includes requestId
- Environment variables: ALL_CAPS, never hardcode ARNs
- Timeout: API handlers max 29s (API Gateway timeout), SQS handlers based on batch
- Memory: start at 256MB, tune based on CloudWatch metrics
- Deployment: AWS CDK (TypeScript)
- Local testing: AWS SAM (sam local invoke)

SQS Event Processing

Process SQS messages — each message is an order to fulfill.
Handle partial batch failures: some messages succeed, some fail.
Failed messages should go to DLQ, not block successful ones.
// src/handlers/sqs-processor.ts
import { SQSHandler, SQSBatchResponse } from 'aws-lambda';

interface OrderFulfillmentMessage {
  orderId: string;
  customerId: string;
  items: Array<{ productId: string; quantity: number }>;
}

export const handler: SQSHandler = async (event): Promise<SQSBatchResponse> => {
  const itemFailures: { itemIdentifier: string }[] = [];

  // Process each message independently
  await Promise.allSettled(
    event.Records.map(async (record) => {
      const messageId = record.messageId;

      try {
        const message = JSON.parse(record.body) as OrderFulfillmentMessage;

        // Process the message
        await fulfillOrder(message);

        console.log(JSON.stringify({
          level: 'info',
          message: 'Order fulfilled',
          orderId: message.orderId,
          messageId,
        }));

      } catch (error) {
        console.error(JSON.stringify({
          level: 'error',
          message: 'Order fulfillment failed',
          messageId,
          error: (error as Error).message,
        }));

        // Mark this message as failed — Lambda will send it to DLQ
        itemFailures.push({ itemIdentifier: messageId });
      }
    })
  );

  // Return failed items — Lambda handles the rest
  // Successfully processed messages are auto-deleted from the queue
  return { batchItemFailures: itemFailures };
};

The batchItemFailures response tells SQS which specific messages failed — successful messages are deleted from the queue, failed ones are retried (up to maxReceiveCount, then go to DLQ).

Cold Start Optimization

My Lambda has 800ms cold starts. It's unacceptable for user-facing APIs.
What can I do?

Claude Code diagnoses common cold start causes and generates fixes:

1. Bundle size reduction (biggest impact):

// webpack.config.js — tree-shake AWS SDK
module.exports = {
  target: 'node20',
  mode: 'production',
  // Mark AWS SDK as external — it's in Lambda runtime
  externals: [
    '@aws-sdk/client-dynamodb',
    '@aws-sdk/client-s3',
    // ... other SDK packages you use
  ],
};
// Or use esbuild (faster, better tree-shaking)
{
  "scripts": {
    "build": "esbuild src/handlers/*.ts --bundle --platform=node --target=node20 --outdir=dist --external:@aws-sdk/* --minify"
  }
}

2. Lazy-load heavy dependencies:

// Don't import at module level
// import { someHeavyLibrary } from 'heavy-library'; // ❌ always loaded

export const handler = async (event) => {
  // Import only when needed (cache in module scope after first load)
  const { someHeavyLibrary } = await import('heavy-library'); // ✅ lazy loaded
};

3. Provisioned Concurrency for latency-sensitive functions:

// CDK — keep N instances warm
const fn = new lambda.Function(this, 'ApiHandler', { /* ... */ });

const alias = fn.addAlias('live');
alias.addAutoScaling({
  minCapacity: 2,  // Always warm
  maxCapacity: 10,
});

4. Reduce initialization time:

// Initialize DB connection pool at module level (reused)
// But use lazy initialization for less-frequently-called functions
let dbPool: Pool | null = null;

function getDb(): Pool {
  if (!dbPool) {
    dbPool = new Pool({ connectionString: process.env.DATABASE_URL });
  }
  return dbPool;
}

CDK Deployment

Deploy this Lambda function with: API Gateway, an SQS queue backed by DLQ,
CloudWatch alarms, and proper IAM permissions.
// lib/order-processing-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigatewayv2';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';

export class OrderProcessingStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // DLQ for failed messages
    const dlq = new sqs.Queue(this, 'OrderFulfillmentDLQ', {
      retentionPeriod: cdk.Duration.days(14),
    });

    // Main queue with DLQ
    const queue = new sqs.Queue(this, 'OrderFulfillmentQueue', {
      visibilityTimeout: cdk.Duration.seconds(60),
      deadLetterQueue: {
        queue: dlq,
        maxReceiveCount: 3, // Retry 3 times before DLQ
      },
    });

    // API handler Lambda
    const apiHandler = new lambda.Function(this, 'ApiHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'api-handler.handler',
      code: lambda.Code.fromAsset('dist'),
      timeout: cdk.Duration.seconds(29),
      memorySize: 256,
      environment: {
        ORDERS_TABLE: 'Orders',
        FULFILLMENT_QUEUE_URL: queue.queueUrl,
      },
    });

    queue.grantSendMessages(apiHandler);

    // SQS processor Lambda
    const sqsProcessor = new lambda.Function(this, 'SqsProcessor', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'sqs-processor.handler',
      code: lambda.Code.fromAsset('dist'),
      timeout: cdk.Duration.seconds(60),
      reservedConcurrentExecutions: 10, // Limit DB connections
    });

    sqsProcessor.addEventSource(new SqsEventSource(queue, {
      batchSize: 10,
      reportBatchItemFailures: true, // Enable partial failures
    }));

    // CloudWatch alarm for DLQ messages
    new cloudwatch.Alarm(this, 'DlqAlarm', {
      metric: dlq.metricNumberOfMessagesSent(),
      threshold: 1,
      evaluationPeriods: 1,
      alarmDescription: 'Messages landed in fulfillment DLQ',
    });

    // HTTP API (cheaper than REST API)
    const httpApi = new apigateway.HttpApi(this, 'OrderApi');

    httpApi.addRoutes({
      path: '/orders',
      methods: [apigateway.HttpMethod.POST],
      integration: new apigateway.integrations.HttpLambdaIntegration(
        'CreateOrder', apiHandler
      ),
    });

    new cdk.CfnOutput(this, 'ApiEndpoint', {
      value: httpApi.apiEndpoint,
    });
  }
}

For the broader serverless patterns beyond Lambda including Cloudflare Workers and Vercel Edge, see the serverless guide. For Infrastructure as Code using Terraform as an alternative to CDK, see the Terraform guide. The Claude Skills 360 bundle includes AWS Lambda skill sets for handler patterns, CDK constructs, and observability. Start with the free tier to try Lambda handler generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free