Serverless Functions dengan Claude Code: Lambda dan Workers
Panduan serverless functions dengan Claude Code: platform, env/secrets, idempotency, retry, test, dan deploy checklist.
Serverless functions adalah potongan kode pendek yang berjalan untuk setiap event atau HTTP request, tanpa Anda mengelola server yang selalu hidup. Pola ini cocok untuk webhook, API kecil, entry point pemrosesan gambar atau CSV, dan routing di edge. Namun timeout, retry, secrets, permission, log, dan biaya tetap harus dirancang dengan serius.
Claude Code membantu karena handler, event fixture, test, catatan deploy, dan checklist review bisa dibuat dalam satu konteks. Alur yang aman bukan membiarkan AI langsung deploy. Alur yang aman adalah: tulis requirement, pilih runtime dan platform, reproduksi event secara lokal, pisahkan konfigurasi dan secrets, desain idempotency, test failure path, lalu minta manusia meninjau exposure dan biaya.
Gunakan dokumentasi resmi sebagai acuan: AWS Lambda Documentation, Lambda with Node.js, Cloudflare Workers development and testing, dan Workers get started guide. Untuk konteks internal, baca juga AWS Lambda guide, Cloudflare Workers guide, API development guide, dan secrets management guide.
Mulai Dari Use Case
Serverless paling kuat saat pekerjaannya pendek, berbentuk event, dan aman saat diulang.
| Use case | Mengapa cocok | Claude Code bisa draft | Review manusia |
|---|---|---|---|
| Webhook pembayaran atau form | Satu request menjadi satu event | Signature check, fixture, failure response | Secrets, duplicate event, replay |
| Entry resize gambar atau CSV | Kerja berat bisa masuk storage atau queue | Validasi, job ID, log JSON | Ukuran file, timeout, cleanup |
| API JSON internal | Endpoint kecil tanpa server permanen | Handler, test, route | Auth, CORS, exposure, rate limit |
| Edge redirect/cache | Response dekat dengan user | Worker route, cache headers, rollout notes | Cache purge, personal data, SEO |
flowchart LR
A[Requirement prompt] --> B[Pilih Lambda atau Workers]
B --> C[Reproduksi event lokal]
C --> D[Pisahkan env dan secrets]
D --> E[Idempotency dan retry]
E --> F[Test]
F --> G[Deploy ke dev]
G --> H[Log dan cleanup]
Prompt Untuk Claude Code
Jangan hanya meminta “buat serverless API”. Beri input, output, failure behavior, file scope, dan titik keputusan manusia.
Buat serverless function minimal dengan Node.js.
Goal:
- Handle POST /orders dan return accepted order response
- Bisa dijalankan lokal dengan node local-test.mjs
- Asumsikan AWS Lambda HTTP API v2 events
Requirements:
- Jelaskan index.mjs, events/create-order.json, local-test.mjs, index.test.mjs
- Return 400 jika idempotency-key tidak ada
- Return response yang sama jika idempotency-key diulang
- Pisahkan invalid JSON, invalid input, unsupported route
- Log JSON tanpa secrets atau personal data
- Sertakan deployment checklist
Constraints:
- Tidak memakai external npm package
- Production idempotency memakai DynamoDB, KV, atau durable store lain
- IAM, public URL, dan billable resources butuh human confirmation
Lambda Atau Workers
AWS Lambda cocok jika Anda butuh AWS events, IAM, S3, DynamoDB, SQS, atau EventBridge. Cloudflare Workers cocok jika pekerjaan utama adalah HTTP di edge: redirect, API ringan, cache, validasi sederhana, KV/D1/R2. Vercel Functions berguna dalam aplikasi Next.js, tetapi contoh di sini fokus pada Lambda dan Workers.
| Kriteria | AWS Lambda | Cloudflare Workers |
|---|---|---|
| Cocok untuk | Integrasi AWS, API bisnis, async jobs | Edge HTTP, routing, cache, API ringan |
| Local workflow | Node.js, SAM, AWS CLI | Wrangler |
| Permission | IAM role/policy | Bindings, secrets, account permissions |
| Risiko | IAM terlalu luas, biaya VPC/NAT, volume log | Binding drift, runtime limits, KV consistency |
Handler Lambda Yang Bisa Dijalankan
// 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\":\"IDR\"}",
"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
Map hanya demo lokal. Di production, gunakan DynamoDB conditional write, database unique constraint, Cloudflare KV/D1, atau durable store lain.
Test
// 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: "IDR" }),
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
Versi Workers Dengan 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
Pitfall Dan Checklist
Pitfall pertama adalah menganggap event hanya berjalan sekali. Webhook, queue, async event, dan browser bisa retry. Pitfall kedua adalah membocorkan secrets di log atau contoh. Pitfall ketiga adalah menerima permission terlalu luas seperti Resource: "*". Pitfall keempat adalah membuka endpoint public tanpa owner, auth, CORS, rate limit, log retention, dan cleanup date.
| Check | Yang dikonfirmasi |
|---|---|
| Requirements | Input, output, owner, dan failure response terdokumentasi |
| Runtime | Lambda Node.js runtime atau Workers compatibility_date eksplisit |
| Local | Fixture dan node --test lulus |
| Env/secrets | Config dan secrets dipisahkan |
| Idempotency | Retry tidak menggandakan charge atau record |
| Timeout/retry | Kerja lambat masuk queue atau durable job |
| Observability | JSON log, error rate, alert, dan retention jelas |
| Cleanup | Delete command atau langkah dashboard ditulis |
zip function.zip index.mjs
aws lambda update-function-code \
--function-name serverless-orders-dev \
--zip-file fileb://function.zip
npx wrangler deploy
Prompt review terakhir:
Review serverless function ini sebelum publish.
Pisahkan blocking issues, non-blocking improvements, dan human confirmations.
Cek idempotency, timeout/retry, secrets, IAM atau bindings, logs,
local reproducibility, cleanup, official links, dan internal links.
ClaudeCodeLab merapikan pola seperti ini dalam Claude Code products and templates. Jika tim Anda perlu mendesain AWS permissions, CLAUDE.md, review prompts, dan approval deploy untuk repository nyata, lihat Claude Code training and consultation.
Saat workflow ini dicoba, manfaat terbesar datang dari membuat event fixture lebih dulu. Claude Code cepat, tetapi retries, secrets, dan cleanup hanya aman jika constraint itu ditulis sejak prompt pertama.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.