Use Cases (Updated: 6/1/2026)

Claude Code AWS Lambda Guide for Teams: Node.js, IAM, API Gateway

Build a Node.js AWS Lambda with Claude Code, local tests, least-privilege IAM, environment variables, API Gateway, and logs.

Claude Code AWS Lambda Guide for Teams: Node.js, IAM, API Gateway

Claude Code is useful for AWS Lambda because one request can produce the handler, event fixture, IAM policy draft, deployment commands, and review checklist. That speed is valuable, but AWS permissions and costs are real production decisions. Let Claude Code draft and check. Do not let it blindly grant IAM permissions, expose a public API, or create billable resources without human review.

This guide walks through a practical team workflow: create a Node.js Lambda, test it locally, keep IAM close to least privilege, configure environment variables, connect API Gateway, read CloudWatch Logs, update the zip deployment package, and run a safe review loop. Lambda is a small runtime that starts when an event arrives. IAM is the permission model that decides what the function can do. API Gateway is the HTTP entry point that forwards requests to Lambda.

Keep the official docs nearby: AWS Lambda getting started, Lambda with Node.js, Lambda environment variables, CloudWatch Logs monitoring, and Claude Code common workflows. The examples below use the Node.js 24 Lambda runtime as of June 1, 2026.

When This Pattern Fits

Lambda works well when you need a small, reliable action without running a server all day. Claude Code helps most when the task has repeatable structure and a reviewer still owns the final decision.

Use caseWhy Lambda fitsLet Claude Code draftHuman review must cover
Webhook receiverPayment, form, and SaaS events are short-livedSignature checks, event fixtures, failure responsesSecrets, retries, duplicate events
Internal JSON APISmall endpoints do not need a persistent app serverHandler, API Gateway commands, log formatAuth, CORS, exposure, cost
Batch entry pointCSV imports, image jobs, and notifications can start smallInput validation, structured logs, error classesTimeout, retry strategy, safe deletion

For a first proof of concept, AWS CLI plus a zip package is fast. For team operations, move the same logic into SAM, CDK, or another reviewed IaC flow.

ApproachBest forWatch out for
AWS consoleOne-time behavior checkManual steps disappear from review history
AWS CLI + zipSmall reproducible proof of conceptIAM and env vars need careful review
SAM / CDKLong-lived team workflowIaC and deployment approvals become mandatory
flowchart LR
  A[Draft with Claude Code] --> B[Run local Node.js test]
  B --> C[Human reviews IAM and environment]
  C --> D[Deploy zip package to dev]
  D --> E[Test through API Gateway]
  E --> F[Read CloudWatch Logs]
  F --> A

1. Create the Node.js Lambda Handler

Start with a dependency-free index.mjs. It accepts both HTTP API v2 style events and older REST API style events, then returns the shape API Gateway expects.

// index.mjs
import crypto from "node:crypto";

const allowedStages = new Set(["dev", "staging", "prod"]);

function readConfig() {
  const stage = process.env.APP_STAGE ?? "dev";
  if (!allowedStages.has(stage)) {
    throw new Error(`Invalid APP_STAGE: ${stage}`);
  }

  return {
    stage,
    tableName: process.env.TABLE_NAME ?? "local-orders",
    logLevel: process.env.LOG_LEVEL ?? "info",
  };
}

function log(level, message, details = {}) {
  console.log(
    JSON.stringify({
      level,
      message,
      service: "orders-api",
      ...details,
    })
  );
}

function json(statusCode, body) {
  return {
    statusCode,
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify(body),
  };
}

function parseBody(event) {
  if (!event.body) return {};
  const raw = event.isBase64Encoded
    ? Buffer.from(event.body, "base64").toString("utf8")
    : event.body;
  return JSON.parse(raw);
}

export async function handler(event = {}, context = {}) {
  const config = readConfig();
  const method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
  const path = event.rawPath ?? event.path ?? "/";
  const requestId = context.awsRequestId ?? "local";

  log("info", "request.start", {
    requestId,
    method,
    path,
    stage: config.stage,
  });

  try {
    if (method === "GET" && path === "/health") {
      return json(200, { ok: true, stage: config.stage });
    }

    if (method === "POST" && path === "/orders") {
      const payload = parseBody(event);
      const orderId = payload.orderId ?? crypto.randomUUID();

      log("info", "order.accepted", {
        requestId,
        orderId,
        tableName: config.tableName,
      });

      return json(202, {
        orderId,
        status: "accepted",
        storedIn: config.tableName,
      });
    }

    return json(404, { error: "not_found", method, path });
  } catch (error) {
    log("error", "request.failed", {
      requestId,
      errorName: error.name,
      errorMessage: error.message,
    });

    return json(500, { error: "internal_error", requestId });
  }
}

This first version does not write to DynamoDB. That is intentional. For beginners, the first milestone is receiving an event, parsing JSON, returning a valid response, and producing logs that can be searched later.

2. Test Locally With an Event Fixture

An event fixture is a saved sample input. Ask Claude Code to generate one, then keep it in the repo so reviewers can reproduce failures.

{
  "version": "2.0",
  "routeKey": "POST /orders",
  "rawPath": "/orders",
  "requestContext": {
    "http": {
      "method": "POST",
      "path": "/orders"
    }
  },
  "headers": {
    "content-type": "application/json"
  },
  "body": "{\"orderId\":\"demo-1001\",\"amount\":3200,\"currency\":\"JPY\"}",
  "isBase64Encoded": false
}

Save it as events/create-order.json, then add a tiny local runner.

// local-test.mjs
import { readFile } from "node:fs/promises";
import { handler } from "./index.mjs";

process.env.APP_STAGE = "dev";
process.env.TABLE_NAME = "orders-dev";
process.env.LOG_LEVEL = "debug";

const eventPath = process.argv[2] ?? "events/create-order.json";
const event = JSON.parse(await readFile(eventPath, "utf8"));

const result = await handler(event, { awsRequestId: "local-001" });
console.log(JSON.stringify(result, null, 2));

Run it before touching AWS:

node local-test.mjs events/create-order.json

If the local result does not return statusCode: 202, deploying will only make the problem slower to debug. Local fixtures also make Claude Code more useful because it can reason from a concrete failing input instead of guessing.

3. Keep IAM Close to Least Privilege

Do not attach AdministratorAccess to a Lambda execution role. Start with logs, then add data access only when the handler actually needs it.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CreateOwnLogGroupIfMissing",
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:ap-northeast-1:123456789012:*"
    },
    {
      "Sid": "WriteOwnLambdaLogs",
      "Effect": "Allow",
      "Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/claude-orders-dev:*"
    },
    {
      "Sid": "ReadWriteOnlyOrdersTable",
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query"],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/orders-dev"
    }
  ]
}

Treat this as a draft, not a production answer. Replace the account, region, function name, and table name. Delete the DynamoDB block until you actually call DynamoDB. For a deeper permission review, read the internal AWS IAM guide.

4. Deploy a Zip Package With AWS CLI

The next commands create real AWS resources and can generate cost. Use a dev account, confirm the region, and know how to delete everything before you run them.

export AWS_REGION=ap-northeast-1
export FUNCTION_NAME=claude-orders-dev
export ROLE_NAME=claude-orders-dev-lambda-role
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

zip function.zip index.mjs

Create trust-policy.json so Lambda can assume the role.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Save the previous IAM draft as lambda-policy.json, then create the role and function.

aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name claude-orders-dev-inline \
  --policy-document file://lambda-policy.json

ROLE_ARN=$(aws iam get-role \
  --role-name "$ROLE_NAME" \
  --query "Role.Arn" \
  --output text)

aws lambda create-function \
  --function-name "$FUNCTION_NAME" \
  --runtime nodejs24.x \
  --handler index.handler \
  --zip-file fileb://function.zip \
  --role "$ROLE_ARN" \
  --architectures arm64 \
  --timeout 10 \
  --memory-size 128 \
  --environment "Variables={APP_STAGE=dev,TABLE_NAME=orders-dev,LOG_LEVEL=info}" \
  --region "$AWS_REGION"

Update code without recreating the function:

zip function.zip index.mjs

aws lambda update-function-code \
  --function-name "$FUNCTION_NAME" \
  --zip-file fileb://function.zip \
  --region "$AWS_REGION"

Be careful with environment variable updates. With update-function-configuration, the Variables map is replaced as a whole. Fetch the current configuration before changing it, and keep secrets in Secrets Manager or Parameter Store instead of plain environment variables.

aws lambda update-function-configuration \
  --function-name "$FUNCTION_NAME" \
  --environment "Variables={APP_STAGE=dev,TABLE_NAME=orders-dev,LOG_LEVEL=debug}" \
  --region "$AWS_REGION"

5. Verify API Gateway and CloudWatch Logs

Invoke Lambda directly first.

aws lambda invoke \
  --function-name "$FUNCTION_NAME" \
  --payload fileb://events/create-order.json \
  --region "$AWS_REGION" \
  response.json

cat response.json

Then create an HTTP API in API Gateway. This exposes a public endpoint, so review auth, CORS, rate limits, and deletion plans before running it.

API_ID=$(aws apigatewayv2 create-api \
  --name claude-orders-dev-api \
  --protocol-type HTTP \
  --target "arn:aws:lambda:${AWS_REGION}:${ACCOUNT_ID}:function:${FUNCTION_NAME}" \
  --query "ApiId" \
  --output text)

aws lambda add-permission \
  --function-name "$FUNCTION_NAME" \
  --statement-id AllowHttpApiInvoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:${AWS_REGION}:${ACCOUNT_ID}:${API_ID}/*/*" \
  --region "$AWS_REGION"

API_ENDPOINT=$(aws apigatewayv2 get-api \
  --api-id "$API_ID" \
  --query "ApiEndpoint" \
  --output text)

curl -s "$API_ENDPOINT/health"
curl -s -X POST "$API_ENDPOINT/orders" \
  -H "content-type: application/json" \
  -d '{"amount":3200,"currency":"JPY"}'

Read the logs immediately after testing.

aws logs tail "/aws/lambda/${FUNCTION_NAME}" \
  --follow \
  --region "$AWS_REGION"

If no logs appear, check the region, function name, and logs:CreateLogStream plus logs:PutLogEvents permissions. If API Gateway returns 500, confirm the Lambda response contains statusCode, headers, and body. For more depth, continue with the internal AWS CloudWatch guide and AWS API Gateway guide.

Claude Prompt for a Lambda Review

Use Claude Code to explain risk before it edits or deploys anything.

You are reviewing a Node.js AWS Lambda change for a team repository.

Scope:
- Files: index.mjs, events/*.json, IAM policy JSON, deployment commands in docs
- Runtime: nodejs24.x
- Entry point: index.handler

Review for:
1. API Gateway event compatibility and response shape
2. Input validation and JSON parsing failures
3. Environment variables that overwrite existing values
4. IAM actions that are wider than needed
5. CloudWatch logs that expose secrets or personal data
6. AWS resources that can create unexpected cost
7. Local test commands that a reviewer can copy and run

Return:
- blocking issues
- non-blocking improvements
- exact commands to verify locally
- questions a human must answer before deploying

Follow the safe loop: explore, plan, edit, test locally, deploy only to dev, inspect logs, and ask a human to approve IAM and cost changes. The AWS deployment guide shows how to move from this CLI proof of concept toward CI/CD and IaC.

Common Pitfalls

The first mistake is deploying before local reproduction. If the event shape is wrong, a fixture catches it faster than CloudWatch.

The second mistake is putting secrets directly in environment variables. Environment variables are convenient for configuration, but secrets need separate access control and rotation.

The third mistake is wide IAM. dynamodb:* and Resource: "*" make demos pass, but they also expand the blast radius. Ask Claude Code to list every permission and justify it line by line.

The fourth mistake is exposing an API Gateway endpoint without an owner. Decide authentication, CORS, throttling, logging, and the cleanup date before sharing the URL.

The fifth mistake is ignoring cost. Lambda may look small, but API Gateway, CloudWatch Logs, DynamoDB, NAT, and external API calls can all create charges.

ClaudeCodeLab packages this kind of review into practical Claude Code templates and training material. If your team wants help designing AWS permissions, CLAUDE.md, review prompts, and deployment approval rules around a real repository, use the Claude Code consultation and training page.

Clean up proof-of-concept resources when you are done.

aws apigatewayv2 delete-api --api-id "$API_ID" --region "$AWS_REGION"
aws lambda delete-function --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name claude-orders-dev-inline
aws iam delete-role --role-name "$ROLE_NAME"

After trying the workflow in this article, the biggest practical gain was having Claude Code keep the handler, fixture, IAM draft, and CLI commands in one reviewable context. The unsafe part was not code generation; it was permission and exposure decisions. The reliable pattern is local fixture first, dev deployment second, CloudWatch evidence third, and human review for IAM and cost before anything reaches production.

#Claude Code #AWS Lambda #serverless #AWS #Node.js #IAM
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.