Cloudflare Workers — Xây dựng ứng dụng Full-Stack Serverless miễn phí trên Edge
Posted on: 4/22/2026 10:13:57 AM
Table of contents
- 1. Tổng quan hệ sinh thái Cloudflare Workers
- 2. Bindings — Cơ chế kết nối cốt lõi
- 3. D1 — Serverless SQL Database trên Edge
- 4. R2 — Object Storage không tính phí Egress
- 5. KV — Global Edge Cache với Eventual Consistency
- 6. Durable Objects — Stateful Computing trên Edge
- 7. Queues — Xử lý bất đồng bộ
- 8. Dynamic Workers — Tương lai của Stateful Serverless (2026)
- 9. Kiến trúc Full-Stack thực tế: Blog Platform
- 10. Deploy lên Production — từ zero đến live trong 5 phút
- 11. Best Practices cho Production
- 12. So sánh tổng thể với AWS / Azure / GCP
- Kết luận
Khi nói về serverless, nhiều developer nghĩ ngay đến AWS Lambda hay Azure Functions. Nhưng có một nền tảng đang âm thầm trở thành lựa chọn hàng đầu cho các ứng dụng edge-first: Cloudflare Workers. Với hệ sinh thái hoàn chỉnh gồm Workers (compute), D1 (SQL database), R2 (object storage), KV (key-value), Durable Objects (stateful coordination) và Queues (message queue) — bạn có thể xây dựng một ứng dụng full-stack hoàn chỉnh mà không cần bất kỳ server truyền thống nào, và phần lớn chạy miễn phí. Bài viết này sẽ đi sâu vào kiến trúc, cách các service kết nối với nhau, và hướng dẫn xây dựng ứng dụng production-ready trên Cloudflare.
1. Tổng quan hệ sinh thái Cloudflare Workers
Cloudflare Workers chạy trên V8 isolates — cùng engine JavaScript mà Chrome sử dụng — nhưng không phải container hay VM. Mỗi request được xử lý trong một isolate riêng biệt với thời gian cold start dưới 1ms, triển khai trên hơn 330 data centers toàn cầu. Đây là điểm khác biệt cốt lõi so với Lambda (container-based, cold start 100ms-vài giây).
graph TB
USER["👤 User Request"] --> EDGE["Cloudflare Edge
(nearest PoP)"]
EDGE --> WORKER["⚡ Worker
(V8 Isolate)"]
WORKER --> D1["🗄️ D1
SQL Database"]
WORKER --> R2["📦 R2
Object Storage"]
WORKER --> KV["🔑 KV
Key-Value Store"]
WORKER --> DO["🔒 Durable Objects
Stateful Coordination"]
WORKER --> QUEUE["📨 Queues
Async Processing"]
QUEUE --> CONSUMER["⚡ Consumer Worker"]
style WORKER fill:#e94560,stroke:#fff,color:#fff
style EDGE fill:#2c3e50,stroke:#e94560,color:#fff
style D1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style R2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style KV fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style DO fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style QUEUE fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style USER fill:#2c3e50,stroke:#e94560,color:#fff
style CONSUMER fill:#e94560,stroke:#fff,color:#fff
Hình 1: Kiến trúc tổng thể hệ sinh thái Cloudflare Workers — mọi service đều kết nối qua bindings
1.1. Free Tier — Chi tiết giới hạn miễn phí
Điểm mạnh lớn nhất của Cloudflare là free tier rộng rãi, đủ để chạy production cho ứng dụng vừa và nhỏ:
| Service | Free Tier | Paid ($5/tháng) | So sánh AWS |
|---|---|---|---|
| Workers | 100K requests/ngày, 10ms CPU/request | 10M requests/tháng, 30s CPU | Lambda: 1M requests/tháng free |
| D1 | 5M rows read/ngày, 100K rows write/ngày, 5GB storage | 25B reads/tháng, 50M writes/tháng | Aurora Serverless: không có free tier |
| R2 | 10GB storage, 1M Class A ops, 10M Class B ops/tháng | $0.015/GB, $0 egress | S3: 5GB free, $0.09/GB egress |
| KV | 100K reads/ngày, 1K writes/ngày, 1GB storage | 10M reads/tháng, 1M writes/tháng | DynamoDB: 25 RCU/WCU free |
| Durable Objects | 100K requests/ngày, 5GB SQLite storage | 1M requests/tháng | Không có tương đương trực tiếp |
| Queues | 10K operations/ngày | 1M operations/tháng | SQS: 1M requests/tháng free |
R2 — Zero egress fees
R2 là dịch vụ object storage không tính phí egress. Với AWS S3, bạn phải trả $0.09/GB cho mỗi GB dữ liệu truyền ra internet. Với R2, bandwidth hoàn toàn miễn phí. Đối với các ứng dụng phục vụ nhiều file tĩnh (hình ảnh, video, documents), R2 có thể tiết kiệm hàng trăm đến hàng nghìn USD mỗi tháng so với S3.
2. Bindings — Cơ chế kết nối cốt lõi
Trong hệ sinh thái Cloudflare, binding là cách Worker truy cập các resource khác. Không giống việc kết nối qua endpoint/URL như AWS SDK, bindings được inject trực tiếp vào runtime của Worker thông qua biến env. Điều này mang lại hai lợi ích: không có network latency giữa Worker và storage (cùng data center), và không cần quản lý credentials.
# wrangler.toml — khai báo bindings
name = "my-app"
main = "src/index.ts"
compatibility_date = "2026-04-01"
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "xxxx-xxxx-xxxx"
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-app-files"
[[kv_namespaces]]
binding = "CACHE"
id = "xxxx"
[[queues.producers]]
binding = "TASK_QUEUE"
queue = "background-tasks"
[[queues.consumers]]
queue = "background-tasks"
max_batch_size = 10
max_batch_timeout = 30
// src/index.ts — sử dụng bindings qua env
interface Env {
DB: D1Database;
STORAGE: R2Bucket;
CACHE: KVNamespace;
TASK_QUEUE: Queue;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// D1: SQL query trực tiếp
const posts = await env.DB
.prepare("SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 10")
.all();
// KV: đọc cache
const cached = await env.CACHE.get("homepage:data", "json");
// R2: upload file
if (request.method === "PUT") {
await env.STORAGE.put("uploads/avatar.png", request.body);
}
// Queue: đẩy task bất đồng bộ
await env.TASK_QUEUE.send({
type: "process-image",
key: "uploads/avatar.png"
});
return Response.json({ posts: posts.results });
}
} satisfies ExportedHandler<Env>;
Binding vs SDK — sự khác biệt kiến trúc
Với AWS, khi Lambda gọi S3, request phải đi qua network (dù cùng region vẫn có latency ~1-5ms). Với Cloudflare, Worker và storage nằm trên cùng máy — binding là một in-process reference, không phải network call. Kết quả: D1 query thường hoàn thành trong <1ms, R2 read trong <5ms.
3. D1 — Serverless SQL Database trên Edge
D1 là cơ sở dữ liệu SQL serverless dựa trên SQLite, chạy trên edge network của Cloudflare. Điểm đặc biệt: D1 hỗ trợ Global Read Replication — dữ liệu được replicate tự động đến các data center gần user nhất, giảm latency đọc xuống dưới 1ms cho hầu hết khu vực.
# Khởi tạo D1 database
npx wrangler d1 create my-app-db
# Tạo schema
npx wrangler d1 execute my-app-db --command "CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
view_count INTEGER DEFAULT 0
)"
# Seed data
npx wrangler d1 execute my-app-db --command "INSERT INTO posts (title, body, slug) VALUES ('Hello World', 'First post content', 'hello-world')"
3.1. D1 — Patterns thực tế
// Parameterized queries (chống SQL injection)
async function getPostBySlug(env: Env, slug: string) {
const result = await env.DB
.prepare("SELECT * FROM posts WHERE slug = ?")
.bind(slug)
.first();
return result;
}
// Batch operations — nhiều query trong 1 round-trip
async function getDashboardData(env: Env) {
const results = await env.DB.batch([
env.DB.prepare("SELECT COUNT(*) as total FROM posts"),
env.DB.prepare("SELECT * FROM posts ORDER BY created_at DESC LIMIT 5"),
env.DB.prepare("SELECT slug, view_count FROM posts ORDER BY view_count DESC LIMIT 5"),
]);
return {
totalPosts: results[0].results[0].total,
recentPosts: results[1].results,
popularPosts: results[2].results,
};
}
// Time Travel — khôi phục dữ liệu về bất kỳ thời điểm nào trong 30 ngày
// npx wrangler d1 time-travel my-app-db --before="2026-04-20T10:00:00Z"
Giới hạn cần lưu ý của D1
D1 dựa trên SQLite nên không hỗ trợ concurrent writes — mỗi database chỉ có một write point. Điều này hoàn toàn phù hợp cho blog, landing page, dashboard (read-heavy). Nhưng nếu ứng dụng cần write-heavy (chat real-time, gaming leaderboard), hãy xem xét Durable Objects hoặc kết hợp Queues để serialize writes.
4. R2 — Object Storage không tính phí Egress
R2 tương thích S3 API, nghĩa là bạn có thể dùng bất kỳ S3 SDK nào để tương tác. Điểm khác biệt duy nhất: $0 egress. Cloudflare không tính phí bandwidth ra internet, dù bạn phục vụ 1TB hay 100TB mỗi tháng.
// Upload file với metadata
async function uploadFile(env: Env, request: Request) {
const formData = await request.formData();
const file = formData.get("file") as File;
const key = `uploads/${Date.now()}-${file.name}`;
await env.STORAGE.put(key, file.stream(), {
httpMetadata: {
contentType: file.type,
cacheControl: "public, max-age=31536000",
},
customMetadata: {
uploadedBy: "user-123",
originalName: file.name,
},
});
return new Response(JSON.stringify({ key, url: `/files/${key}` }), {
headers: { "Content-Type": "application/json" },
});
}
// Serve file với cache headers
async function serveFile(env: Env, key: string) {
const object = await env.STORAGE.get(key);
if (!object) return new Response("Not Found", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("cache-control", "public, max-age=31536000, immutable");
return new Response(object.body, { headers });
}
graph LR
CLIENT["Client"] -->|"Upload"| WORKER["Worker"]
WORKER -->|"PUT object"| R2["R2 Storage"]
WORKER -->|"Save metadata"| D1["D1 Database"]
CLIENT -->|"Download"| CDN["Cloudflare CDN"]
CDN -->|"Cache MISS"| R2
R2 -->|"$0 egress"| CDN
CDN -->|"Cache HIT"| CLIENT
style WORKER fill:#e94560,stroke:#fff,color:#fff
style R2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style CDN fill:#2c3e50,stroke:#e94560,color:#fff
style CLIENT fill:#2c3e50,stroke:#e94560,color:#fff
Hình 2: R2 kết hợp CDN — file được cache tại edge, egress hoàn toàn miễn phí
5. KV — Global Edge Cache với Eventual Consistency
Workers KV là key-value store được replicate trên toàn bộ edge network. Dữ liệu ghi ở một location sẽ lan truyền đến tất cả 330+ PoPs trong vòng ~60 giây. KV phù hợp cho dữ liệu đọc nhiều, ghi ít — configuration, feature flags, cached API responses.
// KV với TTL và metadata
async function cacheApiResponse(env: Env, key: string, data: unknown) {
await env.CACHE.put(key, JSON.stringify(data), {
expirationTtl: 3600, // 1 giờ
metadata: {
cachedAt: new Date().toISOString(),
version: "v2",
},
});
}
// Đọc kèm metadata để kiểm tra freshness
async function getCachedData(env: Env, key: string) {
const { value, metadata } = await env.CACHE.getWithMetadata(key, "json");
if (!value) return null;
const cachedAt = new Date(metadata?.cachedAt as string);
const age = Date.now() - cachedAt.getTime();
return { data: value, ageMs: age, version: metadata?.version };
}
// Stale-while-revalidate pattern
async function getWithSWR(env: Env, key: string, fetcher: () => Promise<unknown>) {
const cached = await env.CACHE.get(key, "json");
if (cached) {
// Trả cached ngay, refresh bất đồng bộ
const ctx = env as unknown as ExecutionContext;
ctx.waitUntil(
fetcher().then(fresh =>
env.CACHE.put(key, JSON.stringify(fresh), { expirationTtl: 3600 })
)
);
return cached;
}
const fresh = await fetcher();
await env.CACHE.put(key, JSON.stringify(fresh), { expirationTtl: 3600 });
return fresh;
}
| Đặc điểm | KV | D1 | Durable Objects |
|---|---|---|---|
| Mô hình dữ liệu | Key-Value | Relational (SQL) | Key-Value hoặc SQLite |
| Consistency | Eventual (~60s) | Strong (single writer) | Strong (single instance) |
| Read latency | <1ms (edge cache) | <1ms (read replica) | ~1-5ms (single location) |
| Write latency | ~60s propagation | <5ms (primary) | <1ms (co-located) |
| Use case | Config, cache, feature flags | CRUD, queries, analytics | Real-time state, WebSocket, coordination |
6. Durable Objects — Stateful Computing trên Edge
Durable Objects (DO) giải quyết bài toán mà serverless truyền thống không thể: stateful coordination. Mỗi DO instance là duy nhất trên toàn hệ thống, có bộ nhớ persistent, và đảm bảo single-threaded execution — không bao giờ có hai request xử lý cùng lúc trên cùng một object.
// Durable Object: Rate Limiter với sliding window
export class RateLimiter implements DurableObject {
private state: DurableObjectState;
constructor(state: DurableObjectState) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
const ip = new URL(request.url).searchParams.get("ip")!;
const now = Date.now();
const windowMs = 60_000; // 1 phút
const maxRequests = 100;
// Lấy danh sách timestamps từ SQLite storage
const timestamps: number[] =
(await this.state.storage.get(`rate:${ip}`)) ?? [];
// Loại bỏ entries ngoài window
const valid = timestamps.filter(t => now - t < windowMs);
if (valid.length >= maxRequests) {
return new Response("Rate limit exceeded", {
status: 429,
headers: {
"Retry-After": String(Math.ceil((valid[0] + windowMs - now) / 1000)),
"X-RateLimit-Remaining": "0",
},
});
}
valid.push(now);
await this.state.storage.put(`rate:${ip}`, valid);
return new Response("OK", {
headers: {
"X-RateLimit-Remaining": String(maxRequests - valid.length),
"X-RateLimit-Reset": String(Math.ceil((valid[0] + windowMs) / 1000)),
},
});
}
}
Durable Objects vs Redis
Nếu quen với Redis, hãy nghĩ Durable Objects như một Redis instance đơn key với strong consistency. Redis cho phép nhiều clients ghi cùng lúc (có race condition nếu không dùng WATCH/transaction). DO đảm bảo serial execution — không cần lock, không cần transaction. Trade-off: DO không có tính năng query phức tạp như Redis (sorted sets, pub/sub).
7. Queues — Xử lý bất đồng bộ
Cloudflare Queues là message queue serverless, tích hợp trực tiếp với Workers. Producer Worker gửi message, Consumer Worker nhận và xử lý theo batch. Queues đảm bảo at-least-once delivery và hỗ trợ retry tự động.
// Producer: gửi task vào queue
async function handleUpload(env: Env, request: Request) {
const formData = await request.formData();
const file = formData.get("image") as File;
// Upload file gốc lên R2
const key = `originals/${crypto.randomUUID()}.${file.name.split(".").pop()}`;
await env.STORAGE.put(key, file.stream());
// Gửi task xử lý ảnh vào queue
await env.TASK_QUEUE.send({
type: "resize-image",
sourceKey: key,
sizes: [
{ width: 320, suffix: "sm" },
{ width: 768, suffix: "md" },
{ width: 1200, suffix: "lg" },
],
});
// Lưu metadata vào D1
await env.DB.prepare(
"INSERT INTO images (key, original_name, status) VALUES (?, ?, 'processing')"
).bind(key, file.name).run();
return Response.json({ key, status: "processing" });
}
// Consumer: xử lý batch messages
export default {
async queue(batch: MessageBatch, env: Env) {
for (const msg of batch.messages) {
try {
const task = msg.body as { type: string; sourceKey: string; sizes: Array<{width: number; suffix: string}> };
if (task.type === "resize-image") {
// Đọc ảnh gốc từ R2
const original = await env.STORAGE.get(task.sourceKey);
if (!original) { msg.ack(); continue; }
// Resize và upload từng kích thước
for (const size of task.sizes) {
const resizedKey = task.sourceKey.replace("originals/", `resized/${size.suffix}/`);
// ... resize logic với @cf/image/resize ...
await env.STORAGE.put(resizedKey, resizedBody);
}
// Cập nhật status
await env.DB.prepare(
"UPDATE images SET status = 'ready' WHERE key = ?"
).bind(task.sourceKey).run();
}
msg.ack();
} catch (e) {
msg.retry(); // Retry tự động với exponential backoff
}
}
},
} satisfies ExportedHandler<Env>;
sequenceDiagram
participant C as Client
participant W as Worker (Producer)
participant R2 as R2 Storage
participant Q as Queue
participant CW as Consumer Worker
participant D1 as D1 Database
C->>W: POST /upload (image)
W->>R2: PUT original image
W->>Q: send({type: "resize", ...})
W->>D1: INSERT status='processing'
W-->>C: 202 Accepted
Note over Q,CW: Async processing
Q->>CW: batch messages
CW->>R2: GET original
CW->>R2: PUT resized (sm, md, lg)
CW->>D1: UPDATE status='ready'
CW-->>Q: ack()
Hình 3: Image processing pipeline — upload đồng bộ, resize bất đồng bộ qua Queues
8. Dynamic Workers — Tương lai của Stateful Serverless (2026)
Tại Developer Week 2026, Cloudflare công bố Dynamic Workers (open beta) — bước tiến lớn kết hợp serverless với stateful computing. Dynamic Workers có thể:
- Tự động scale horizontal từ 0 đến hàng nghìn instances theo traffic
- Duy trì persistent state giữa các invocation, không cần external storage
- Hỗ trợ long-running tasks lên đến 30 phút mỗi invocation
- Cold start vẫn giữ dưới millisecond nhờ V8 isolate
Dynamic Workers vs Durable Objects
Nếu bạn đang dùng Durable Objects chỉ để giữ state (session, counter, cache), Dynamic Workers là sự thay thế đơn giản hơn — không cần thiết kế theo mô hình actor. Durable Objects vẫn phù hợp khi bạn cần single-point-of-execution guarantee — ví dụ distributed lock, leader election, hoặc coordination giữa nhiều clients trên cùng một resource.
9. Kiến trúc Full-Stack thực tế: Blog Platform
Để minh hoạ cách các service phối hợp, dưới đây là kiến trúc một blog platform đầy đủ tính năng chạy hoàn toàn trên Cloudflare:
graph TB
subgraph "Frontend (Pages)"
FE["Vue.js / React SPA"]
end
subgraph "API Layer (Workers)"
API["API Worker"]
AUTH["Auth Middleware"]
end
subgraph "Data Layer"
D1DB["D1: posts, users, comments"]
R2S["R2: images, attachments"]
KVC["KV: page cache, config"]
end
subgraph "Background"
QUE["Queue: email, resize, sitemap"]
CRON["Cron Trigger: daily stats"]
end
FE --> AUTH
AUTH --> API
API --> D1DB
API --> R2S
API --> KVC
API --> QUE
CRON --> API
style FE fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style API fill:#e94560,stroke:#fff,color:#fff
style AUTH fill:#2c3e50,stroke:#e94560,color:#fff
style D1DB fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style R2S fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style KVC fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style QUE fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style CRON fill:#2c3e50,stroke:#e94560,color:#fff
Hình 4: Blog platform full-stack trên Cloudflare — chi phí $0/tháng cho traffic thấp
// Cấu trúc project hoàn chỉnh
// wrangler.toml
// ├── src/
// │ ├── index.ts # Router chính
// │ ├── middleware/auth.ts # JWT verification
// │ ├── routes/
// │ │ ├── posts.ts # CRUD posts (D1)
// │ │ ├── upload.ts # File upload (R2)
// │ │ └── feed.ts # RSS/sitemap (KV cache)
// │ └── workers/
// │ └── consumer.ts # Queue consumer
// Router chính với Hono framework
import { Hono } from "hono";
import { jwt } from "hono/jwt";
const app = new Hono<{ Bindings: Env }>();
// Public routes
app.get("/api/posts", async (c) => {
// Kiểm tra KV cache trước
const cached = await c.env.CACHE.get("posts:list", "json");
if (cached) return c.json(cached);
const posts = await c.env.DB
.prepare(`
SELECT id, title, slug, excerpt, created_at, view_count
FROM posts WHERE published = 1
ORDER BY created_at DESC LIMIT 20
`)
.all();
// Cache 5 phút
await c.env.CACHE.put("posts:list", JSON.stringify(posts.results), {
expirationTtl: 300,
});
return c.json(posts.results);
});
app.get("/api/posts/:slug", async (c) => {
const slug = c.req.param("slug");
const post = await c.env.DB
.prepare("SELECT * FROM posts WHERE slug = ? AND published = 1")
.bind(slug)
.first();
if (!post) return c.json({ error: "Not found" }, 404);
// Tăng view count bất đồng bộ
c.executionCtx.waitUntil(
c.env.DB.prepare("UPDATE posts SET view_count = view_count + 1 WHERE slug = ?")
.bind(slug).run()
);
return c.json(post);
});
// Protected routes
app.use("/api/admin/*", jwt({ secret: "your-secret" }));
app.post("/api/admin/posts", async (c) => {
const body = await c.req.json();
const result = await c.env.DB
.prepare(`
INSERT INTO posts (title, slug, body, excerpt, published)
VALUES (?, ?, ?, ?, ?)
`)
.bind(body.title, body.slug, body.body, body.excerpt, body.published ? 1 : 0)
.run();
// Invalidate cache
await c.env.CACHE.delete("posts:list");
// Gửi notification qua queue
await c.env.TASK_QUEUE.send({
type: "new-post",
postId: result.meta.last_row_id,
title: body.title,
});
return c.json({ id: result.meta.last_row_id }, 201);
});
export default app;
10. Deploy lên Production — từ zero đến live trong 5 phút
# 1. Khởi tạo project
npm create cloudflare@latest my-blog -- --template hono
cd my-blog
# 2. Tạo D1 database
npx wrangler d1 create blog-db
# → Thêm database_id vào wrangler.toml
# 3. Chạy migration
npx wrangler d1 execute blog-db --file=./schema.sql
# 4. Tạo R2 bucket
npx wrangler r2 bucket create blog-files
# 5. Tạo KV namespace
npx wrangler kv namespace create CACHE
# 6. Deploy
npx wrangler deploy
# ✅ Live tại: https://my-blog.your-subdomain.workers.dev
# → Gắn custom domain trong Cloudflare Dashboard
Custom domain miễn phí
Nếu domain của bạn đã được quản lý trên Cloudflare (free plan), bạn có thể gắn custom domain cho Workers không tốn thêm phí. Workers tự động nhận SSL certificate qua Cloudflare Universal SSL. Kết hợp với Pages cho frontend, bạn có một hệ thống hoàn chỉnh với custom domain, SSL, CDN — tất cả miễn phí.
11. Best Practices cho Production
11.1. Caching Strategy
// Multi-layer caching: Cache API → KV → D1
async function getPost(env: Env, slug: string, request: Request) {
// Layer 1: Cloudflare Cache API (edge, ~0ms)
const cacheKey = new Request(`https://cache/${slug}`, request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (response) return response;
// Layer 2: KV (global, <1ms)
const kvData = await env.CACHE.get(`post:${slug}`, "json");
if (kvData) {
response = Response.json(kvData);
response.headers.set("Cache-Control", "public, max-age=60");
await cache.put(cacheKey, response.clone());
return response;
}
// Layer 3: D1 (primary, <5ms)
const post = await env.DB.prepare("SELECT * FROM posts WHERE slug = ?")
.bind(slug).first();
if (!post) return new Response("Not found", { status: 404 });
// Populate cả 2 layer
await env.CACHE.put(`post:${slug}`, JSON.stringify(post), { expirationTtl: 300 });
response = Response.json(post);
response.headers.set("Cache-Control", "public, max-age=60");
await cache.put(cacheKey, response.clone());
return response;
}
11.2. Error Handling & Monitoring
// Structured logging với wrangler tail
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const startTime = Date.now();
const requestId = crypto.randomUUID();
try {
const response = await handleRequest(request, env);
// Log mỗi request
console.log(JSON.stringify({
requestId,
method: request.method,
url: request.url,
status: response.status,
durationMs: Date.now() - startTime,
}));
return response;
} catch (error) {
console.error(JSON.stringify({
requestId,
error: error instanceof Error ? error.message : "Unknown error",
stack: error instanceof Error ? error.stack : undefined,
}));
return new Response("Internal Server Error", { status: 500 });
}
},
} satisfies ExportedHandler<Env>;
// Monitor real-time: npx wrangler tail --format json
12. So sánh tổng thể với AWS / Azure / GCP
| Tiêu chí | Cloudflare | AWS | Azure |
|---|---|---|---|
| Cold start | <1ms (V8 isolate) | 100ms-5s (container) | 200ms-10s (container) |
| Global distribution | Mặc định (330+ PoPs) | Cần chọn region + Lambda@Edge | Cần chọn region |
| Egress cost | $0 | $0.09/GB | $0.087/GB |
| SQL database | D1 (SQLite, edge) | Aurora Serverless (MySQL/PG) | SQL Database serverless |
| Object storage | R2 ($0 egress) | S3 ($0.023/GB + egress) | Blob ($0.018/GB + egress) |
| Free tier | Rất rộng rãi | 12 tháng giới hạn | 12 tháng giới hạn |
| Vendor lock-in | Trung bình (D1=SQLite, R2=S3 API) | Cao (proprietary APIs) | Cao (proprietary APIs) |
| Runtime | JavaScript/TypeScript, Python, Rust/WASM | Đa ngôn ngữ | Đa ngôn ngữ |
Khi nào KHÔNG nên chọn Cloudflare Workers?
Workers chạy trên V8 isolates nên không hỗ trợ binary dependencies (native modules). Nếu ứng dụng cần FFmpeg, ImageMagick, Puppeteer server-side, hoặc machine learning model lớn (trừ Workers AI), bạn vẫn cần Lambda/Cloud Run. D1 cũng chưa phù hợp cho workload write-heavy hoặc cần ACID transactions phức tạp — trường hợp này nên dùng PlanetScale, Neon, hoặc Supabase.
Kết luận
Cloudflare Workers không chỉ là một serverless platform — nó là một hệ sinh thái edge-first hoàn chỉnh. Với D1 cho SQL, R2 cho file storage, KV cho caching, Durable Objects cho stateful logic, và Queues cho async processing, bạn có mọi thứ cần thiết để xây dựng ứng dụng production mà không cần suy nghĩ về server, region, hay scaling. Free tier rộng rãi đủ cho hầu hết dự án cá nhân và startup giai đoạn đầu. Và với Dynamic Workers sắp GA, ranh giới giữa serverless và stateful computing đang dần biến mất — tương lai của edge computing đã ở đây.
Nguồn tham khảo:
Shared Dictionaries — Khi trình duyệt chỉ tải những gì thực sự thay đổi
Database Migration không Downtime: Expand-Contract, EF Core và Batch Backfill cho Production
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.