Software_development_department aws-serverless

Provides AWS serverless architecture patterns for Lambda, API Gateway, DynamoDB, SQS, and SAM/CDK. Use when working with AWS serverless files (serverless.yml, CDK stacks) or when the user mentions Lambda, API Gateway, serverless, or AWS SAM.

install
source · Clone the upstream repo
git clone https://github.com/tranhieutt/software_development_department
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tranhieutt/software_development_department "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/aws-serverless" ~/.claude/skills/tranhieutt-software-development-department-aws-serverless && rm -rf "$T"
manifest: .claude/skills/aws-serverless/SKILL.md
source content

AWS Serverless

Critical rules (non-obvious)

  • Initialize clients OUTSIDE handler — Lambda reuses execution environments across invocations; creating clients inside costs 100-500ms per cold start
  • context.callbackWaitsForEmptyEventLoop = false
    — prevents Node.js from hanging on open async handles (DB connections, etc.)
  • SQS
    VisibilityTimeout
    = 6× Lambda timeout
    — if Lambda takes 30s, set 180s; otherwise messages return to queue mid-processing
  • FunctionResponseTypes: [ReportBatchItemFailures]
    — partial batch failure; without this, any single failure retries the entire batch
  • Never use
    *
    in
    Access-Control-Allow-Origin
    with
    credentials: true
    — browsers block it; use explicit origin

Lambda handler pattern

// Initialize once (reused across invocations = faster after cold start)
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, GetCommand } = require("@aws-sdk/lib-dynamodb");

const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

exports.handler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false;  // don't hang on open handles
  try {
    const body = typeof event.body === "string" ? JSON.parse(event.body) : event.body;
    const result = await docClient.send(new GetCommand({
      TableName: process.env.TABLE_NAME,
      Key: { id: body.id },
    }));
    return { statusCode: 200, headers: { "Content-Type": "application/json" }, body: JSON.stringify(result.Item) };
  } catch (err) {
    console.error(JSON.stringify({ error: err.message, requestId: context.awsRequestId }));
    return { statusCode: err.statusCode ?? 500, body: JSON.stringify({ error: err.message }) };
  }
};

SAM template: HTTP API + DynamoDB

# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: nodejs20.x
    Timeout: 30
    MemorySize: 256
    Environment:
      Variables:
        TABLE_NAME: !Ref ItemsTable

Resources:
  HttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      CorsConfiguration:
        AllowOrigins: ["https://yourdomain.com"]  # never * with credentials
        AllowMethods: [GET, POST, DELETE]
        AllowHeaders: ["*"]

  GetItemFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/get.handler
      Events:
        GetItem:
          Type: HttpApi
          Properties:
            ApiId: !Ref HttpApi
            Path: /items/{id}
            Method: GET
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref ItemsTable

  ItemsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST

Outputs:
  ApiUrl:
    Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com"

SQS async processing with partial batch failure

# In template.yaml
ProcessorFunction:
  Type: AWS::Serverless::Function
  Properties:
    Events:
      SQSEvent:
        Type: SQS
        Properties:
          Queue: !GetAtt ProcessingQueue.Arn
          BatchSize: 10
          FunctionResponseTypes:
            - ReportBatchItemFailures  # critical: retry only failed items

ProcessingQueue:
  Type: AWS::SQS::Queue
  Properties:
    VisibilityTimeout: 180  # 6x Lambda timeout (30s)
    RedrivePolicy:
      deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
      maxReceiveCount: 3

DeadLetterQueue:
  Type: AWS::SQS::Queue
  Properties:
    MessageRetentionPeriod: 1209600  # 14 days
// Handler with partial batch failure reporting
exports.handler = async (event) => {
  const batchItemFailures = [];
  for (const record of event.Records) {
    try {
      await processMessage(JSON.parse(record.body));
    } catch (err) {
      console.error(`Failed ${record.messageId}:`, err.message);
      batchItemFailures.push({ itemIdentifier: record.messageId });
    }
  }
  return { batchItemFailures };  // only failed items are retried
};

Sharp edges

IssueSeverityFix
Cold start > 1sHighMove SDK init outside handler; use
--no-install-suggests
in Docker layers
Timeout without responseHighAlways set explicit timeout < Lambda timeout in downstream calls
Memory = CPU allocationHigh1792MB = 1 full vCPU; increase memory for CPU-bound tasks
VPC cold start adds 1-10sMediumUse VPC Endpoints instead of public NAT to reduce ENI setup
Infinite Lambda→SQS loopHighNever write to same SQS queue that triggers Lambda without a dead-letter
S3 trigger infinite loopHighUse separate source/destination buckets or prefix filters

Commands

sam build
sam local invoke GetItemFunction --event events/get-item.json
sam local start-api    # local API Gateway emulation
sam deploy --guided    # first deploy (creates samconfig.toml)
sam deploy             # subsequent deploys