Serverless Development Patterns
Your image-processing Lambda fires twice on the same S3 upload, so half your thumbnails get written, deleted, and re-written — and CloudWatch is a wall of unstructured console.log you can’t correlate to a request. Meanwhile p99 latency spikes to 4 seconds every morning because of cold starts, and the “fix” someone shipped (provisioned concurrency on every function) doubled the bill. Serverless removes the servers, not the distributed-systems problems: retries, idempotency, cold starts, and per-platform limits are now your code.
This recipe shows how to drive Cursor, Claude Code, and Codex to generate serverless code that survives those problems — not the happy-path demo that breaks the first time SQS redelivers a message.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- A copy-paste prompt that produces an idempotent AWS Lambda handler (dedupe on retries) with structured CloudWatch logging
- A Cloudflare Worker API-gateway pattern with a real
wrangler.toml, plus how the Cloudflare MCP server inspects KV/R2/D1 without leaving your editor - A cold-start triage workflow that tells you when provisioned concurrency is actually worth the money
- The three-tool split: when to use Cursor’s agent, Claude Code’s headless runs, and Codex Cloud for deploy PRs
- The failure modes that bite in production — duplicate SQS deliveries, DynamoDB hot partitions, Workers CPU limits — and the prompts that pre-empt them
The Workflow
Section titled “The Workflow”Step 1: Scaffold the service (where the three tools differ)
Section titled “Step 1: Scaffold the service (where the three tools differ)”The scaffolding step is where tool ergonomics diverge most. Cursor edits files visually in your repo; Claude Code runs the framework CLI from the terminal and is scriptable in CI; Codex can fan the same task out to a cloud worktree and open a deploy PR. Pick by where the work lives, not by capability — all three drive the same AWS SAM / Serverless Framework / Wrangler CLIs.
Open Agent mode (Cmd/Ctrl + I) in an empty repo and give it a concrete, single service to build:
Set up a Serverless Framework v4 service in TypeScript targeting AWS
eu-central-1. One HTTP Lambda behind API Gateway (GET /health), one S3-triggered Lambda for image processing, and a DynamoDB tablemedia(PKid). Use esbuild bundling, Node 22 runtime, and putstage/regioninserverless.ymlprovider vars. Add adevandprodstage.
The agent writes serverless.yml, the handler stubs, and package.json. Review the diff in the sidebar before accepting — Cursor checkpoints each edit so you can roll back a bad file without losing the rest.
Claude Code runs the framework CLI for you and is the right choice when you want the scaffold reproducible in a script or CI step. Give it the same concrete spec as a positional prompt:
claude "Set up a Serverless Framework v4 service in TypeScript targeting AWS eu-central-1: one HTTP Lambda behind API Gateway (GET /health), one S3-triggered image-processing Lambda, and a DynamoDB table 'media' (PK id). Use esbuild bundling, Node 22 runtime, and stage/region provider vars in serverless.yml."The agent edits the files and runs npx serverless itself; it does not pop a provider-selection wizard — you state the provider in the prompt. Add --permission-mode acceptEdits once you trust the plan so it stops asking before each write.
Codex spans CLI, IDE, and Cloud. For a one-off local scaffold, run it non-interactively and let it edit the workspace:
codex exec --full-auto "Scaffold a Serverless Framework v4 TypeScript service for AWS eu-central-1 with an HTTP /health Lambda, an S3-triggered image Lambda, a DynamoDB 'media' table, esbuild, Node 22, and stage/region provider vars."--full-auto sets --ask-for-approval on-request and --sandbox workspace-write, so Codex can write files but still pauses before anything risky. When you want the scaffold and a reviewable PR, push it to a cloud worktree with codex cloud exec "<same prompt>, then open a PR" — Codex runs in an isolated environment and hands back a branch.
Step 2: Make the handler idempotent (the part demos skip)
Section titled “Step 2: Make the handler idempotent (the part demos skip)”S3, SQS, and EventBridge all deliver at least once. A naive handler reprocesses the same object on a retry. The fix is a dedupe key — for S3, the object’s eTag — checked against a conditional write before doing the work. This is the single most valuable thing to put in your prompt, so make it explicit:
That prompt produces something close to this — note the early return on a duplicate and the structured log, which is what makes it debuggable in production:
// handler.ts — the AI-generated shape worth reviewing forimport { S3Event } from 'aws-lambda';import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
const ddb = new DynamoDBClient({});
export const handler = async (event: S3Event) => { const start = Date.now(); for (const rec of event.Records) { const { bucket, object } = rec.s3; const pk = `${bucket.name}/${object.key}#${object.eTag}`; const log = { requestId: rec.responseElements['x-amz-request-id'], bucket: bucket.name, key: object.key, eTag: object.eTag }; try { await ddb.send(new PutItemCommand({ TableName: process.env.DEDUPE_TABLE!, Item: { pk: { S: pk } }, ConditionExpression: 'attribute_not_exists(pk)', })); } catch (err: any) { if (err.name === 'ConditionalCheckFailedException') { console.log(JSON.stringify({ ...log, status: 'duplicate-skipped' })); continue; // already processed — S3 redelivery } throw err; } // ... resize with sharp, write derived/ objects ... console.log(JSON.stringify({ ...log, status: 'ok', durationMs: Date.now() - start })); }};The thing to verify in the AI’s output: that the dedupe write happens before the side effects, and that the catch block rethrows (so the DLQ actually catches failures) instead of swallowing the error.
Step 3: The Cloudflare Worker API gateway
Section titled “Step 3: The Cloudflare Worker API gateway”On Workers the constraints are different — no Node runtime, a CPU-time budget per request, and bindings instead of SDK clients. Ask for the gateway and the config together so the wrangler.toml bindings match the code:
# wrangler.toml — the AI should produce bindings that match the Workername = "api-gateway"main = "src/index.ts"compatibility_date = "2026-06-01"
[vars]ORIGIN = "https://origin.internal.example.com"
[[kv_namespaces]]binding = "TOKENS"id = "<your-kv-id>"
[[durable_objects.bindings]]name = "RATE_LIMITER"class_name = "RateLimiter"
[[migrations]]tag = "v1"new_sqlite_classes = ["RateLimiter"]Step 4: Inspect live infra with the Cloudflare MCP server
Section titled “Step 4: Inspect live infra with the Cloudflare MCP server”Generating the Worker is half the job; the bugs live in the binding values — a mistyped KV namespace id, a stale D1 row. Without MCP, you alt-tab between the dashboard and your editor. With Cloudflare’s managed remote MCP servers, the AI reads your real account state and the docs in the same conversation. Setup is identical across all three tools — point them at the remote endpoints (full server catalog and OAuth flow in the Cloudflare MCP guide):
{ "mcpServers": { "cloudflare-bindings": { "command": "npx", "args": ["-y", "mcp-remote", "https://bindings.mcp.cloudflare.com/mcp"] }, "cloudflare-observability": { "command": "npx", "args": ["-y", "mcp-remote", "https://observability.mcp.cloudflare.com/mcp"] } }}Before MCP, debugging the stale-cache bug meant copying namespace ids between four browser tabs. After: “Read the TOKENS KV namespace bound to the api-gateway Worker and list the keys written in the last hour” and the AI answers from your live account. For deploys and binding edits from the CLI, the lighter-weight alternative is the Cloudflare wrangler skill — install it with npx skills add cloudflare/skills/wrangler (from the cloudflare/skills repo, listed on skills.sh). A skill is the better fit when you just want the agent to know wrangler commands and conventions; the MCP server wins when you need it to read live state.