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

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).

330+ Data centers toàn cầu
<1ms Cold start trung bình
100K Requests/ngày miễn phí
$0 Egress bandwidth (R2)
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ỏ:

ServiceFree TierPaid ($5/tháng)So sánh AWS
Workers100K requests/ngày, 10ms CPU/request10M requests/tháng, 30s CPULambda: 1M requests/tháng free
D15M rows read/ngày, 100K rows write/ngày, 5GB storage25B reads/tháng, 50M writes/thángAurora Serverless: không có free tier
R210GB storage, 1M Class A ops, 10M Class B ops/tháng$0.015/GB, $0 egressS3: 5GB free, $0.09/GB egress
KV100K reads/ngày, 1K writes/ngày, 1GB storage10M reads/tháng, 1M writes/thángDynamoDB: 25 RCU/WCU free
Durable Objects100K requests/ngày, 5GB SQLite storage1M requests/thángKhông có tương đương trực tiếp
Queues10K operations/ngày1M operations/thángSQS: 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ểmKVD1Durable Objects
Mô hình dữ liệuKey-ValueRelational (SQL)Key-Value hoặc SQLite
ConsistencyEventual (~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 caseConfig, cache, feature flagsCRUD, queries, analyticsReal-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íCloudflareAWSAzure
Cold start<1ms (V8 isolate)100ms-5s (container)200ms-10s (container)
Global distributionMặc định (330+ PoPs)Cần chọn region + Lambda@EdgeCần chọn region
Egress cost$0$0.09/GB$0.087/GB
SQL databaseD1 (SQLite, edge)Aurora Serverless (MySQL/PG)SQL Database serverless
Object storageR2 ($0 egress)S3 ($0.023/GB + egress)Blob ($0.018/GB + egress)
Free tierRất rộng rãi12 tháng giới hạn12 tháng giới hạn
Vendor lock-inTrung bình (D1=SQLite, R2=S3 API)Cao (proprietary APIs)Cao (proprietary APIs)
RuntimeJavaScript/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: