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.
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.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.