Skip to content

HARRY-31 — GĐ17 — Cloudflare cho Backend: Workers, R2, D1, KV, Queues, Durable Objects, DNS/CDN

GĐ17 — Cloudflare cho Backend: Workers, R2, D1, KV, Queues, Durable Objects, DNS/CDN ​

Giai đoạn này đưa một API nhỏ lên Cloudflare, nối compute với dữ liệu và file, xử lý việc nền, rồi quan sát request ở production. Mục tiêu là hiểu cách chọn đúng sản phẩm và ranh giới của chúng — không phải học thuộc toàn bộ danh mục Cloudflare.


1. Sau giai đoạn này, bạn làm được gì? ​

  • Giải thích request đi từ DNS/proxy đến cache, Worker và binding dữ liệu như thế nào.
  • Viết được một Worker API bằng TypeScript và chạy thử bằng Wrangler.
  • Chọn đúng nơi lưu: R2 cho object/file, D1 cho dữ liệu quan hệ, KV cho dữ liệu đọc nhiều và chấp nhận eventual consistency.
  • Đưa công việc khỏi request bằng Queues, có retry, DLQ và xử lý lặp an toàn.
  • Nhận ra lúc nào cần Durable Objects để điều phối một trạng thái dùng chung.
  • Phân biệt quyền tài khoản Cloudflare, API token, secret của ứng dụng và binding của Worker.
  • Đọc log/trace, giới hạn cache đúng cách, triển khai staging rồi mới production và dọn tài nguyên lab.

Giai đoạn trước đã giúp bạn hiểu Docker, CI/CD và AWS. Giai đoạn này so sánh các ý tưởng tương đương để dễ định hướng, nhưng không coi sản phẩm hai nền tảng là thay thế 1:1.

Nhu cầuCloudflare thường dùngGần với AWS ở khía cạnhKhác biệt cần nhớ
Chạy API / request handlerWorkersLambdaWorker chạy trên runtime web/edge; không phải máy Linux luôn bật như EC2
File và objectR2S3R2 có API S3-compatible và binding riêng; quyền truy cập công khai cần cấu hình có chủ đích
SQLD1RDS ở góc nhìn “database có quản lý”D1 là SQLite serverless, không phải PostgreSQL/RDS thu nhỏ tương đương
Đọc key-value rất nhiềuWorkers KVMột phần use case DynamoDB/cacheKV eventually consistent; không dùng làm nguồn sự thật cần transaction
Việc nềnQueuesSQS ở khái niệm hàng đợiTin nhắn có thể giao lặp; consumer phải idempotent
Trạng thái cần điều phốiDurable ObjectsMột phần use case stateful service/lockMỗi object là một điểm điều phối có tên và storage riêng, mạnh về một nhóm trạng thái cụ thể
DNS, proxy, phân phối nội dungDNS, CDN/Cache, RulesRoute 53 + CloudFront ở một số lớpCache phụ thuộc proxy, header và rule; DNS không tự làm ứng dụng an toàn

Cloudflare không cung cấp một bản sao VPC theo cách AWS tổ chức VPC/subnet/security group. Nếu cần Worker truy cập database riêng tư đang nằm trong AWS/on-prem, xem Hyperdrive + Workers VPC/Tunnel; đó là kết nối tới mạng/database hiện có, không phải thay VPC AWS.


2. Mô hình request: edge, Worker, binding ​

Với domain được proxy qua Cloudflare, request thường đi theo các bước sau:

text
Browser
  → DNS trả về địa chỉ anycast của Cloudflare
  → Cloudflare edge: TLS, rules, WAF và cache phù hợp
  → Worker nếu route/ứng dụng yêu cầu chạy code
  → binding tới D1 / R2 / KV / Queue / Durable Object
  → Response trở lại client; cache chỉ áp dụng nếu chính sách cho phép

Binding là cách Worker truy cập dịch vụ Cloudflare bằng một biến runtime như env.DB hoặc env.FILES. Binding giúp ứng dụng gọi resource qua API đã định nghĩa, không cần tự gọi Cloudflare REST API hay nhúng credential quản trị vào code. Tên biến binding do bạn đặt trong cấu hình Wrangler.

Static asset có thể được trả trực tiếp mà không gọi Worker. Với Workers Static Assets, request khớp file tĩnh thường được phục vụ trước; các route API hoặc request không khớp mới đi vào Worker theo cấu hình. Đây là cách ghép site tĩnh và API trong cùng ứng dụng.

Domain DNS-only chỉ dùng DNS để phân giải tên; request không đi qua Cloudflare proxy nên không tự được CDN/cache/WAF của Cloudflare xử lý. Muốn áp dụng tính năng edge cho host đó, record cần được proxy và sản phẩm/rule tương ứng phải được bật.

Tài liệu: Workers overview, bindings, Static Assets.


3. Workers — compute cho request và API ​

Worker là gì? ​

Worker là code xử lý request/event chạy trên runtime của Cloudflare. Với API, entry point thường nhận Request, env chứa binding/biến môi trường, rồi trả Response. Nhiều API web tiêu chuẩn như fetch, Request, Response, URL, crypto hoạt động tự nhiên.

Worker không phải Express server chạy mãi trên một VM. Không thiết kế dựa trên process sống lâu, file cục bộ bền vững, socket nghe cổng hoặc cache trong bộ nhớ dùng chung cho mọi request. Dữ liệu cần tồn tại phải đặt trong binding/store phù hợp. Nếu code Node cần module hoặc API runtime cụ thể, kiểm tra khả năng tương thích và cấu hình nodejs_compat; bật tương thích Node không biến Worker thành máy chủ Node Linux đầy đủ.

Worker API tối thiểu ​

Ví dụ sau có route health check và route tạo ghi chú. Nó minh họa parse URL, kiểm tra method/input và bind tham số SQL; chưa bao gồm đăng nhập hay authorization.

ts
interface Env {
  DB: D1Database
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url)

    if (request.method === 'GET' && url.pathname === '/health') {
      return Response.json({ ok: true })
    }

    if (request.method === 'POST' && url.pathname === '/api/notes') {
      let body: unknown
      try {
        body = await request.json()
      } catch {
        return Response.json({ error: 'Invalid JSON' }, { status: 400 })
      }

      if (
        typeof body !== 'object' ||
        body === null ||
        !('title' in body) ||
        typeof body.title !== 'string' ||
        body.title.trim().length === 0 ||
        body.title.length > 200
      ) {
        return Response.json({ error: 'title is required' }, { status: 400 })
      }

      const id = crypto.randomUUID()
      const title = body.title.trim()
      await env.DB.prepare(
        'INSERT INTO notes (id, title) VALUES (?, ?)',
      ).bind(id, title).run()

      return Response.json({ id, title }, { status: 201 })
    }

    return Response.json({ error: 'Not found' }, { status: 404 })
  },
} satisfies ExportedHandler<Env>

Vì sao dùng prepared statement? ? là chỗ giữ tham số, .bind(...) đưa dữ liệu riêng vào query. Đừng ghép input thành chuỗi SQL. Trong app thật, thêm authentication và authorization trước khi đọc/ghi tài nguyên của user; validation không thay thế phân quyền.

Wrangler và môi trường ​

Wrangler là CLI phát triển/deploy Worker. Cấu hình mới nên theo format wrangler.jsonc và được xem là source of truth cho tên Worker, compatibility date, bindings, observability và môi trường.

bash
npm create cloudflare@latest -- cloudflare-notes-api
cd cloudflare-notes-api
npx wrangler dev

Chọn template Worker TypeScript để bài lab bắt đầu từ API đơn giản. wrangler dev chạy môi trường local; trước khi dùng binding remote, hãy kiểm tra cấu hình — local code vẫn có thể được nối tới resource thật nếu bật remote binding.

Tách staging và production bằng Wrangler environments hoặc project/deployment riêng. Mỗi môi trường phải gắn đúng database, bucket, queue, route và secret. Không để staging dùng nhầm production database. Sau khi thêm binding, chạy npx wrangler types để sinh kiểu TypeScript theo cấu hình thật.


4. Workers Static Assets, Pages, DNS và CDN ​

Site tĩnh kết hợp API ​

Workers Static Assets cho phép deploy static files cùng Worker trong một lần. Ví dụ site có /index.html, /assets/app.js và /api/*: file khớp asset có thể được phục vụ từ CDN; route API chạy Worker.

Cloudflare Pages cũng có thể host site và Pages Functions. Backend Guide hiện đang chạy trên Pages, vì vậy dùng repo này làm ví dụ đọc cấu hình/build/deploy là hợp lý; giai đoạn học không yêu cầu chuyển dự án hiện tại sang Workers. Với project mới, hãy xem hướng dẫn Workers Static Assets và framework adapter đang dùng trước khi chọn cách deploy.

Cache đúng nội dung ​

Cache giảm request về origin và thời gian phản hồi, nhưng response chứa dữ liệu theo user không được cache như asset công khai. Với dữ liệu cá nhân, đặt chính sách rõ như Cache-Control: private, no-store; kiểm tra Set-Cookie, Authorization, cache key và rule trước khi bật “cache everything”. Cloudflare không mặc định cache HTML/JSON trong nhiều cấu hình CDN, nhưng Cache Rules có thể thay đổi hành vi đó.

Ví dụ phân loại:

URLCách xử lý
/assets/app.abc123.jsAsset có hash, cache dài hạn; khi nội dung đổi thì tên file đổi
/api/catalogChỉ cache nếu response thực sự dùng chung, có TTL và cách purge/đổi version
/api/meTheo user; thường trả private, no-store, không dùng cache chung
/api/documents/:idKiểm tra quyền user trước khi đọc object; không suy ra quyền từ URL/key

Tài liệu: Cloudflare Cache, default cache behavior, Cache Rules, Static Assets routing.


5. R2 — lưu object và file ​

Khi dùng R2? ​

Dùng R2 cho file do người dùng tải lên, ảnh, export, bản sao lưu hoặc artifact lớn. Đây là object storage: mỗi object có key, metadata và bytes; nó không phải filesystem để ứng dụng tùy ý rename/thực hiện transaction thư mục.

Worker dùng R2 binding để thao tác trực tiếp. Bucket private là mặc định tốt cho file user; chỉ mở đường dẫn tải sau khi API xác thực quyền, hoặc phát hành URL có scope/thời hạn phù hợp. Không trả objectKey công khai rồi cho rằng nó bí mật là đủ bảo vệ file.

Ví dụ upload qua Worker ​

ts
interface Env {
  FILES: R2Bucket
}

async function uploadFile(request: Request, env: Env, userId: string) {
  if (!request.body) {
    return Response.json({ error: 'Empty body' }, { status: 400 })
  }

  const key = `users/${userId}/${crypto.randomUUID()}`
  const object = await env.FILES.put(key, request.body, {
    httpMetadata: { contentType: 'application/octet-stream' },
    customMetadata: { ownerId: userId },
  })

  return Response.json({ key, etag: object?.etag }, { status: 201 })
}

userId trong ví dụ phải lấy từ identity đã xác thực, không lấy trực tiếp từ body/path do client gửi. Trước khi ghi, giới hạn kích thước request, kiểm tra loại file theo allowlist/magic bytes phù hợp, áp quota, chống upload lặp và không ghi file nhạy cảm vào log. Nếu cần file lớn, hỗ trợ multipart/resumable flow thay vì buffer toàn bộ file vào RAM.

Metadata và vòng đời file ​

Thông tin cần query như owner, trạng thái quét, thời gian tạo và liên kết domain nên nằm trong D1/SQL; bytes nằm trong R2. Ghi file và ghi DB không phải một transaction chung. Thiết kế compensation/cleanup: nếu upload thành công nhưng insert DB lỗi thì xóa object hoặc để job đối soát tìm orphan; nếu DB record được tạo nhưng object bị xóa, đánh dấu lỗi và phục hồi/xóa metadata theo quy trình.

Worker download phải kiểm tra quyền trên metadata trước khi get(key). Trả Content-Type đã kiểm tra, X-Content-Type-Options: nosniff, và cân nhắc Content-Disposition: attachment cho file user. Nếu cần giao file trực tiếp qua S3-compatible API, tìm hiểu riêng credentials/scope và presigned URL; API binding trong Worker và S3-compatible API không hoàn toàn giống nhau.

Tài liệu: Use R2 from Workers, R2 Workers API reference.


6. D1 và Hyperdrive — chọn database theo workload ​

D1: SQL qua binding ​

D1 là database SQL serverless của Cloudflare, dựa trên SQLite và được truy cập từ Worker qua binding. Đây là cách dễ bắt đầu cho ứng dụng/lab chạy gần Worker. D1 có migration, prepared query và transaction semantics riêng; đọc giới hạn/consistency/concurrency trước khi chọn cho workload lớn hoặc nhiều ghi đồng thời.

Ví dụ migration migrations/0001_create_notes.sql:

sql
CREATE TABLE notes (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 200),
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX notes_created_at_idx ON notes (created_at DESC);

Cấu hình Worker gắn D1 bằng tên binding, tên database và ID database do Cloudflare tạo. Trong code, dùng env.DB.prepare(...).bind(...).run() như ví dụ ở phần Workers. Tạo migration để thay đổi schema có thể review và chạy lặp theo môi trường; không sửa production bằng câu lệnh ad-hoc rồi quên đưa thay đổi vào source control.

bash
npx wrangler d1 create cloudflare-notes
npx wrangler d1 migrations apply cloudflare-notes --local
npx wrangler dev

Chỉ chạy migration --remote sau khi xác nhận binding, tài khoản và database đích. Tên database trong lệnh phải đúng với cấu hình Wrangler.

Hyperdrive: giữ Postgres/MySQL hiện có ​

Nếu dự án đã dùng PostgreSQL/MySQL — ví dụ RDS ở GĐ16 — không cần chuyển database sang D1 để chạy Worker. Hyperdrive cho Worker kết nối tới database hiện có, quản lý pool gần database và tối ưu kết nối từ mạng edge. Nó giữ vai trò cầu nối/tối ưu truy cập, không biến RDS thành D1 và cũng không xóa nhu cầu cấu hình firewall, credential, backup, schema migration hay connection budget.

Chọn theo câu hỏi:

  1. Schema và query có phù hợp SQLite, workload gọn và muốn dùng database native của Workers? Thử D1.
  2. Hệ thống đã có Postgres/MySQL, cần ORM/driver hoặc migration đang dùng? Giữ DB và cân nhắc Hyperdrive.
  3. Có yêu cầu transaction, extension, query pattern, backup/restore hoặc workload vượt giới hạn dịch vụ? Đo workload và so sánh database managed trước khi quyết định.

Tài liệu: D1 getting started, D1 Wrangler commands, Hyperdrive overview, kết nối database trong Workers.


7. KV và Durable Objects — key-value phân tán hay điều phối trạng thái? ​

Workers KV: đọc nhiều, ghi tương đối ít ​

KV lưu cặp key-value và cache dữ liệu tại edge. Hợp với cấu hình, feature flags, nội dung đọc nhiều, lookup gần như tĩnh hoặc cache có TTL. Khi key được ghi ở một vùng, thay đổi có thể mất thời gian mới nhìn thấy ở vùng khác: KV eventually consistent. Vì vậy không dùng nó làm nguồn sự thật cho số dư tiền, khóa duy nhất, revocation phải tức thời hay cập nhật cần read-your-writes toàn cầu.

Ví dụ cache cấu hình công khai:

ts
const value = await env.CACHE.get('catalog:v3', 'json')
if (value) return Response.json(value)

const fresh = await loadPublicCatalog()
await env.CACHE.put('catalog:v3', JSON.stringify(fresh), {
  expirationTtl: 300,
})
return Response.json(fresh)

Cache này chấp nhận stale tối đa một khoảng ngắn. Tăng version key khi đổi format (catalog:v4), đặt TTL hợp với độ tươi, và có chiến lược fallback nếu cache miss hoặc binding lỗi.

Durable Objects: một điểm xử lý cho một nhóm trạng thái ​

Durable Object có tên định danh duy nhất, storage gắn với object và xử lý tuần tự các yêu cầu tới cùng instance. Dùng khi nhiều request/client phải phối hợp trên cùng một trạng thái: phòng chat, phiên cộng tác, bộ đếm giới hạn theo khóa, presence hoặc lock có vòng đời rõ.

Ví dụ, tạo một object theo roomId; mọi message của phòng đó được đưa về cùng object để cấp thứ tự và broadcast. Các phòng khác nhau vẫn phân tán độc lập. Lưu dữ liệu cần tồn tại trong storage bền, đừng chỉ dựa vào biến RAM vì object có thể idle và khởi tạo lại.

Không dùng Durable Object mặc định cho mọi cache hoặc DB. Mỗi object thường là một vùng điều phối riêng; thiết kế khóa kém (ví dụ một object toàn hệ thống cho mọi tenant) có thể tạo hotspot. Chọn key để chia tải và giới hạn tác động nếu một object chậm/hỏng.

Tài liệu: KV consistency và use cases, Durable Objects.


8. Queues — xử lý việc nền và giao nhận lặp an toàn ​

Đưa việc chậm hoặc có thể retry khỏi request chính. Ví dụ: sau khi file vào R2, Worker ghi metadata và gửi message scan-upload; consumer quét virus/trích metadata rồi cập nhật D1. Request upload trả về sớm với trạng thái processing.

Queues cung cấp batching, retry/delay và DLQ. Delivery mặc định là at-least-once: một message có thể tới consumer nhiều hơn một lần. Vì thế:

  • Payload nhỏ, có jobId, schemaVersion và chỉ chứa dữ liệu cần thiết; không nhét secret/PII dư thừa.
  • Consumer ghi kết quả bằng idempotency key hoặc unique constraint trong DB.
  • Phân loại lỗi tạm thời (retry) và lỗi vĩnh viễn (đưa DLQ/đánh dấu fail).
  • Theo dõi độ sâu queue, tuổi message cũ nhất, retry và DLQ; có quy trình replay sau khi sửa lỗi.
  • Đặt timeout và batch size theo thời gian xử lý/giới hạn dependency, không chỉ chọn số lớn.

Ví dụ consumer ở mức minh họa:

ts
interface UploadJob {
  jobId: string
  objectKey: string
  schemaVersion: 1
}

interface QueueEnv {
  DB: D1Database
}

export default {
  async queue(batch: MessageBatch<UploadJob>, env: QueueEnv) {
    for (const message of batch.messages) {
      try {
        const job = message.body
        await processUploadOnce(env, job) // DB unique key jobId chống xử lý lặp
        message.ack()
      } catch (error) {
        console.error({ event: 'upload_job_failed', error })
        message.retry()
      }
    }
  },
} satisfies ExportedHandler<QueueEnv>

Trong code production, không log raw body hoặc token. processUploadOnce phải thiết kế sao cho retry sau crash không gửi email, tính phí hoặc tạo record trùng. Việc gọi API ngoài cũng nên truyền idempotency key nếu provider hỗ trợ.

Tài liệu: Queues overview, delivery guarantees, how Queues works.


9. Quyền, secrets, log và chi phí ​

Ba loại quyền không được nhầm ​

  1. Account/API token: quyền của người hoặc CI khi quản trị Cloudflare. Tạo token theo đúng account, resource và action tối thiểu; tách token CI khỏi token cá nhân, đặt hạn dùng và có quy trình thu hồi.
  2. Worker binding: quyền runtime tới đúng dịch vụ mà Worker được gắn, như bucket R2, database D1 hoặc namespace KV. Không đưa account API token vào request handler để truy cập dịch vụ nội bộ.
  3. Secret ứng dụng: credential tới dịch vụ ngoài, ví dụ webhook signing key. Lưu bằng secret mechanism của Workers/Wrangler, không commit .dev.vars, không in secret vào log.

Observability ​

Ghi log có cấu trúc để lọc theo route, event, request id và kết quả. Ví dụ:

ts
console.log({
  event: 'document_upload_completed',
  requestId,
  documentId,
  bytes: object.size,
})

Không ghi access token, URL có chữ ký, nội dung file hoặc dữ liệu cá nhân không cần thiết. Bật Workers Logs/observability theo cấu hình, xem invocation/error logs, kiểm tra sampling và retention. Với nhiều request, đo tỉ lệ lỗi và latency theo route; “deploy thành công” chưa chứng minh API hoạt động.

Chi phí và cleanup ​

Tính giá hiện tại trong Cloudflare pricing/calculator trước khi tạo tài nguyên thật. Chi phí có thể đến từ request compute, storage, thao tác đọc/ghi, log ingestion, database, queue và dịch vụ add-on. Đặt budget/alert nếu tài khoản hỗ trợ, dùng resource lab riêng, giới hạn upload, log sampling phù hợp và dọn resource không dùng. Free plan/limit thay đổi theo thời gian — không dùng con số đọc từ bài blog cũ làm cam kết.

Tài liệu: Wrangler secrets, environments, Workers Logs, Workers best practices, pricing.


10. Bài lab — private document API trên Cloudflare ​

Mục tiêu ​

Build một API nhỏ để user upload tài liệu riêng tư: Worker xác thực request, bytes vào R2, metadata vào D1, job quét file gửi qua Queue, rồi endpoint chỉ cho owner tải lại. Thêm KV chỉ cho dữ liệu danh mục công khai; thêm Durable Object như bài mở rộng để điều phối quota theo tenant hoặc phiên xử lý. Không cần ép cả bảy sản phẩm vào nếu use case không đòi hỏi.

Bước 1: khởi tạo và mô hình dữ liệu ​

  1. Tạo Worker TypeScript bằng npm create cloudflare@latest và chạy npx wrangler dev.
  2. Tạo D1 database và R2 bucket riêng cho lab; bind vào Worker bằng tên rõ như DB và FILES.
  3. Tạo migration có bảng documents(id, owner_id, object_key, content_type, byte_size, status, created_at); thêm unique constraint cho object_key và index theo (owner_id, created_at).
  4. Thêm middleware/auth layer; user ID lấy từ token/session đã xác minh, không tin header tự gửi từ browser.

Bước 2: upload an toàn ​

  1. Kiểm tra user có quyền upload, quota, request size, file type và tên file.
  2. Sinh object key ngẫu nhiên phía server, chẳng hạn users/<authenticated-user-id>/<uuid>.
  3. Stream body vào private R2 bucket; không đọc toàn bộ file vào memory nếu file lớn.
  4. Ghi metadata D1 với trạng thái pending_scan, rồi enqueue message có jobId.
  5. Nếu bước giữa thất bại, ghi log có request ID và dùng cleanup/compensation để không để orphan vĩnh viễn.

Bước 3: xử lý queue ​

  1. Consumer kiểm tra jobId đã được xử lý chưa bằng unique constraint/record idempotency.
  2. Xác minh object tồn tại, chạy bước quét/trích metadata, cập nhật trạng thái ready hoặc rejected.
  3. Lỗi tạm thời retry có giới hạn; lỗi dữ liệu không hợp lệ vào trạng thái thất bại/DLQ.
  4. Tạo một test gọi consumer hai lần với cùng job và chứng minh không có side effect trùng.

Bước 4: download và cache ​

  1. Endpoint GET /api/documents/:id tìm metadata bằng id + owner_id trong cùng query.
  2. Chỉ tải R2 object nếu user là owner hoặc qua được policy chia sẻ.
  3. Response file đặt header an toàn; nội dung riêng tư trả Cache-Control: private, no-store.
  4. Kiểm tra request của user B không thể tải file user A kể cả khi biết document ID/object key.

Bước 5: staging, quan sát và dọn ​

  1. Tạo staging environment có D1/bucket/queue riêng; cấu hình lại các binding không tự kế thừa như mong đợi.
  2. Thêm secret thử nghiệm qua Wrangler/dashboard; ví dụ npx wrangler secret put API_KEY --env staging. Lệnh thêm secret triển khai Worker ngay, nên xác nhận environment/resource đích trước khi chạy; tuyệt đối không dùng secret production ở local hoặc staging.
  3. Chạy migration local, request success/error, queue retry/DLQ, upload quá giới hạn và authorization matrix.
  4. Deploy lên staging; gọi endpoint, xem Workers Logs, xác minh object/row/queue state và tắt cache cho dữ liệu user.
  5. Sau khi kiểm tra chi phí và đích deploy, mới deploy production theo CI/CD của dự án; dọn database, bucket, queue và secret lab khi xong.

Lệnh Wrangler và cách khai báo binding có thể thay đổi. Tra Wrangler configuration, local development và tài liệu từng binding trước khi chạy thao tác remote. Đặc biệt, lệnh migration/bucket/deploy tác động resource thật nếu chọn remote/account production.

Definition of done ​

  • Có sơ đồ request flow và giải thích được lúc nào code chạy trên Worker, lúc nào asset được CDN trả trực tiếp.
  • API xác thực user, validate input, phân quyền theo owner và không lộ bucket public.
  • File stream vào R2; metadata/migration ở D1 hoặc DB được chọn có lý do.
  • Queue consumer idempotent, có retry/DLQ và theo dõi message cũ.
  • KV chỉ được dùng ở nơi chấp nhận stale; biết tại sao KV không làm database transaction.
  • Có log để truy một request mà không lộ secret/PII.
  • Staging dùng resource riêng; biết cách ước tính chi phí và dọn lab.
  • Viết được một đoạn giải thích vì sao Cloudflare hoặc AWS phù hợp hơn với project đã chọn.

Liên hệ với repo Backend Guide ​

Repo này đang minh họa một lựa chọn khác với Worker API thuần: VitePress build thành static site, deploy trên Cloudflare Pages và dùng R2 private cho ebook. Đọc wrangler.jsonc, .github/workflows/deploy.yml và phần Ebook trong README.md; lần theo build → Pages deployment → R2 binding → kiểm tra session Supabase → byte-range response. Dùng flow này để so sánh static hosting + Worker endpoint với app full-stack chạy trực tiếp trên Workers.


Tài liệu chính thức nên đọc ​

Học bằng cách build. Chứng minh, đừng tin.

Đồng bộ tiến độ

Đăng nhập trực tiếp để tiếp tục học trên desktop và PWA.