Serverless Functions com Claude Code: Lambda e Workers
Guia pratico para criar serverless functions com Claude Code: prompt, plataforma, env/secrets, idempotencia, retries, testes e deploy.
Serverless functions sao pequenos trechos de codigo executados a cada evento ou requisicao HTTP, sem manter um servidor sempre ligado. Elas combinam bem com webhooks, APIs pequenas, entradas para processamento de imagens ou CSV, e regras na borda. Mesmo assim, timeout, retry, secrets, permissoes, logs e custos continuam sendo decisoes de producao.
Claude Code ajuda porque consegue manter handler, fixture de evento, testes, notas de deploy e checklist de revisao no mesmo contexto. O fluxo seguro nao e deixar a IA publicar sozinha. O fluxo seguro e: escrever requisitos, escolher runtime e plataforma, reproduzir o evento localmente, separar configuracao e secrets, desenhar idempotencia, testar falhas e pedir revisao humana para exposicao e custo.
Use a documentacao oficial como base: AWS Lambda Documentation, Lambda com Node.js, Cloudflare Workers development and testing e Workers get started guide. Para aprofundar, veja o guia AWS Lambda, o guia Cloudflare Workers, o guia de desenvolvimento de APIs e o guia de gerenciamento de secrets.
Comece Pelo Caso De Uso
Serverless funciona melhor quando o trabalho e curto, orientado a evento e pode ser repetido sem corromper dados.
| Caso de uso | Por que encaixa | Claude Code pode rascunhar | Revisao humana |
|---|---|---|---|
| Webhook de pagamento ou formulario | Uma requisicao vira um evento | Verificacao de assinatura, fixture, erros | Secrets, duplicados, replay |
| Entrada de resize ou CSV | Trabalho pesado vai para storage ou fila | Validacao, job ID, logs JSON | Tamanho de arquivo, timeout, cleanup |
| API JSON interna | Pequenos endpoints sem servidor persistente | Handler, testes, route | Auth, CORS, exposicao, rate limit |
| Redirect/cache na borda | Resposta perto do usuario | Worker route, cache headers, rollout | Purge de cache, dados pessoais, SEO |
flowchart LR
A[Prompt de requisitos] --> B[Escolher Lambda ou Workers]
B --> C[Reproduzir evento local]
C --> D[Separar env e secrets]
D --> E[Idempotencia e retry]
E --> F[Testes]
F --> G[Deploy em dev]
G --> H[Logs e cleanup]
Prompt Para Claude Code
Crie uma serverless function minima em Node.js.
Objetivo:
- Processar POST /orders e retornar uma resposta accepted
- Rodar localmente com node local-test.mjs
- Assumir eventos AWS Lambda HTTP API v2
Requisitos:
- Explicar index.mjs, events/create-order.json, local-test.mjs e index.test.mjs
- Retornar 400 quando idempotency-key faltar
- Retornar a mesma resposta quando idempotency-key repetir
- Separar invalid JSON, invalid input e unsupported route
- Logs em JSON sem secrets nem dados pessoais
- Incluir checklist pre-deploy
Restricoes:
- Sem pacotes npm externos
- Em producao, idempotencia deve usar DynamoDB, KV ou outro storage duravel
- IAM, URLs publicas e recursos pagos exigem confirmacao humana
Lambda Ou Workers
AWS Lambda e natural quando voce usa eventos AWS, IAM, S3, DynamoDB, SQS ou EventBridge. Cloudflare Workers e melhor quando o trabalho central e HTTP na borda: redirects, APIs leves, cache, validacao simples, KV/D1/R2. Vercel Functions ajuda em apps Next.js, mas aqui os exemplos ficam em Lambda e Workers.
| Criterio | AWS Lambda | Cloudflare Workers |
|---|---|---|
| Melhor para | Integracoes AWS, APIs de negocio, jobs async | HTTP edge, routing, cache, APIs leves |
| Local | Node.js, SAM, AWS CLI | Wrangler |
| Permissoes | IAM role e policy | Bindings, secrets, permissoes da conta |
| Risco comum | IAM amplo, custo VPC/NAT, logs demais | Drift de bindings, limites runtime, KV consistency |
Handler Lambda Executavel
// index.mjs
import crypto from "node:crypto";
const localIdempotencyStore = new Map();
function json(statusCode, body) {
return {
statusCode,
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
};
}
function readHeader(headers = {}, name) {
const target = name.toLowerCase();
const found = Object.entries(headers).find(([key]) => key.toLowerCase() === target);
return found?.[1];
}
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 method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
const path = event.rawPath ?? event.path ?? "/";
const requestId = context.awsRequestId ?? crypto.randomUUID();
console.log(JSON.stringify({ level: "info", message: "request.start", requestId, method, path }));
if (method !== "POST" || path !== "/orders") {
return json(404, { error: "not_found" });
}
const idempotencyKey = readHeader(event.headers, "idempotency-key");
if (!idempotencyKey) {
return json(400, { error: "idempotency_key_required" });
}
if (localIdempotencyStore.has(idempotencyKey)) {
return json(200, { ...localIdempotencyStore.get(idempotencyKey), replay: true });
}
let body;
try {
body = parseBody(event);
} catch {
return json(400, { error: "invalid_json" });
}
if (!Number.isFinite(body.amount) || body.amount <= 0 || typeof body.currency !== "string") {
return json(400, { error: "invalid_order" });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
localIdempotencyStore.set(idempotencyKey, accepted);
console.log(JSON.stringify({ level: "info", message: "order.accepted", requestId, orderId: accepted.orderId }));
return json(202, accepted);
}
{
"version": "2.0",
"routeKey": "POST /orders",
"rawPath": "/orders",
"headers": {
"content-type": "application/json",
"idempotency-key": "demo-key-001"
},
"requestContext": {
"http": {
"method": "POST",
"path": "/orders"
}
},
"body": "{\"amount\":3200,\"currency\":\"BRL\"}",
"isBase64Encoded": false
}
// local-test.mjs
import { readFile } from "node:fs/promises";
import { handler } from "./index.mjs";
const eventPath = process.argv[2] ?? "events/create-order.json";
const event = JSON.parse(await readFile(eventPath, "utf8"));
const first = await handler(event, { awsRequestId: "local-001" });
const second = await handler(event, { awsRequestId: "local-002" });
console.log("first:", first.statusCode, first.body);
console.log("second:", second.statusCode, second.body);
node local-test.mjs events/create-order.json
A Map e apenas demo local. Em producao, use escrita condicional no DynamoDB, unique constraint no banco, Cloudflare KV/D1 ou outro storage duravel.
Testes
// index.test.mjs
import crypto from "node:crypto";
import test from "node:test";
import assert from "node:assert/strict";
import { handler } from "./index.mjs";
function event(overrides = {}) {
return {
rawPath: "/orders",
headers: { "idempotency-key": crypto.randomUUID() },
requestContext: { http: { method: "POST" } },
body: JSON.stringify({ amount: 1200, currency: "BRL" }),
isBase64Encoded: false,
...overrides,
};
}
test("requires idempotency-key", async () => {
const result = await handler(event({ headers: {} }), {});
assert.equal(result.statusCode, 400);
});
test("accepts a valid order", async () => {
const result = await handler(event(), {});
assert.equal(result.statusCode, 202);
assert.equal(JSON.parse(result.body).status, "accepted");
});
test("rejects invalid JSON", async () => {
const result = await handler(event({ body: "not-json" }), {});
assert.equal(result.statusCode, 400);
});
node --test index.test.mjs
Versao Workers Com KV
// src/worker.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method !== "POST" || url.pathname !== "/orders") {
return Response.json({ error: "not_found" }, { status: 404 });
}
if (request.headers.get("x-webhook-secret") !== env.WEBHOOK_SECRET) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
const idempotencyKey = request.headers.get("idempotency-key");
if (!idempotencyKey) {
return Response.json({ error: "idempotency_key_required" }, { status: 400 });
}
const existing = await env.IDEMPOTENCY_KV.get(idempotencyKey, "json");
if (existing) {
return Response.json({ ...existing, replay: true });
}
const body = await request.json();
if (!Number.isFinite(body.amount) || typeof body.currency !== "string") {
return Response.json({ error: "invalid_order" }, { status: 400 });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
await env.IDEMPOTENCY_KV.put(idempotencyKey, JSON.stringify(accepted), {
expirationTtl: 86400,
});
return Response.json(accepted, { status: 202 });
},
};
npm create cloudflare@latest serverless-orders-worker
cd serverless-orders-worker
npx wrangler kv namespace create IDEMPOTENCY_KV
npx wrangler secret put WEBHOOK_SECRET
npx wrangler dev
Armadilhas E Checklist
A primeira armadilha e assumir execucao exatamente uma vez. Webhooks, filas, eventos async e navegadores podem repetir. A segunda e vazar secrets em logs ou exemplos. A terceira e aceitar permissao ampla como Resource: "*". A quarta e publicar uma URL sem dono, auth, CORS, rate limit, retencao de logs e plano de limpeza.
| Check | Confirmar |
|---|---|
| Requisitos | Input, output, owner e erros documentados |
| Runtime | Lambda Node.js runtime ou Workers compatibility_date explicito |
| Local | Fixture e node --test passam |
| Env/secrets | Config e secrets separados |
| Idempotencia | Retry nao duplica cobranca ou cadastro |
| Timeout/retry | Trabalho lento vai para fila ou job duravel |
| Observabilidade | Logs JSON, error rate, alertas e retention definidos |
| Cleanup | Comandos de exclusao ou passos no dashboard escritos |
zip function.zip index.mjs
aws lambda update-function-code \
--function-name serverless-orders-dev \
--zip-file fileb://function.zip
npx wrangler deploy
Prompt final:
Revise esta serverless function antes de publicar.
Separe blocking issues, non-blocking improvements e human confirmations.
Verifique idempotencia, timeout/retry, secrets, IAM ou bindings, logs,
reproducibilidade local, cleanup, links oficiais e links internos.
ClaudeCodeLab organiza esses padroes em produtos e templates de Claude Code. Para desenhar permissoes AWS, CLAUDE.md, prompts de revisao e aprovacao de deploy em um repositorio real, veja a pagina de consultoria e treinamento Claude Code.
No teste pratico, o maior ganho veio de criar o fixture de evento primeiro. Claude Code e rapido, mas retries, secrets e cleanup so ficam confiaveis quando essas restricoes aparecem no primeiro prompt.
Artigos relacionados
Guia prático de AWS Lambda com Claude Code: Node.js, IAM e API Gateway
Crie uma Lambda Node.js com Claude Code: testes locais, IAM mínimo, variáveis, API Gateway e logs CloudWatch.
Cloudflare Workers com Claude Code: guia prático de API edge
Crie uma API Workers com Claude Code, Wrangler, KV/D1/R2, cache, rate limiting, logs e headers seguros.
Edge Computing com Claude Code: Workers, cache e rotas por região
Projete Edge runtime com Claude Code: Workers, região, cache keys, riscos e comandos de verificação.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.