Cloudflare Workers — Serverless Ở Biên Toàn Cầu
Xây dựng ứng dụng serverless với Cloudflare Workers: kiến trúc, routing, storage, deploy và best practices.
Cloudflare Workers chạy code ở 330+ datacenter trên toàn cầu — gần user nhất, không cold start, giá rẻ.
Workers vs Lambda#
| Cloudflare Workers | AWS Lambda | |
|---|---|---|
| Runtime | V8 Isolates | Container |
| Cold start | ~0ms (luôn warm) | ~100-500ms |
| Regions | 330+ | Tự chọn region |
| Price | $0.30/million req | $0.20/million req + duration |
| Duration | 30s CPU, 5min (fetch) | 15 phút |
| Memory | 128MB (free) | 128MB-10GB |
Hello World#
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return new Response('Hello from the edge!', {
headers: {
'Content-Type': 'text/plain',
'x-edge-location': request.cf?.colo || 'unknown',
},
});
},
};typescriptRouting#
import { Router } from 'itty-router';
const router = Router();
router.get('/api/users', async (request) => {
const users = await getUsers();
return Response.json(users);
});
router.get('/api/users/:id', async (request) => {
const { id } = request.params;
const user = await getUser(id);
if (!user) {
return new Response('Not found', { status: 404 });
}
return Response.json(user);
});
router.post('/api/users', async (request) => {
const body = await request.json();
const user = await createUser(body);
return Response.json(user, { status: 201 });
});
router.all('*', () => new Response('Not found', { status: 404 }));
export default {
fetch: router.handle,
};typescriptStorage — KV, R2, D1#
KV — Key-Value Store#
// wrangler.jsonc
{
"kv_namespaces": [
{ "binding": "CACHE", "id": "abc123" }
]
}typescript// Read
const cached = await env.CACHE.get('user:123', 'json');
if (cached) return Response.json(cached);
// Write
const user = await fetchUser(123);
await env.CACHE.put('user:123', JSON.stringify(user), {
expirationTtl: 3600, // 1 giờ
});
// List
const list = await env.CACHE.list({ prefix: 'user:', limit: 100 });typescriptR2 — Object Storage (S3-compatible)#
const object = await env.BUCKET.get('images/avatar.jpg');
if (!object) return new Response('Not found', { status: 404 });
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'image/jpeg',
'Cache-Control': 'public, max-age=31536000',
},
});
// Upload
await env.BUCKET.put('uploads/file.pdf', request.body, {
httpMetadata: { contentType: 'application/pdf' },
});typescriptD1 — SQLite Database#
// Schema
const db = env.DB;
await db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
`);
// Query
const users = await db.prepare('SELECT * FROM users WHERE id = ?').bind(1).all();
const user = users.results[0];
// Prepared statement
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?1, ?2)');
await stmt.bind('Alice', 'alice@example.com').run();typescriptEnv — Environment Variables#
// wrangler.jsonc
{
"vars": {
"API_URL": "https://api.example.com",
"LOG_LEVEL": "info"
}
}
// TypeScript
interface Env {
API_URL: string;
LOG_LEVEL: string;
CACHE: KVNamespace;
DB: D1Database;
BUCKET: R2Bucket;
AI: Ai;
}typescriptCron Triggers#
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
switch (event.cron) {
case '*/5 * * * *':
await syncData(env);
break;
case '0 0 * * *':
await cleanupOldRecords(env);
break;
}
},
};typescript{
"triggers": {
"crons": ["*/5 * * * *", "0 0 * * *"]
}
}jsonWorkers AI#
Built-in AI inference at the edge:
async function translateText(text: string, env: Env) {
const response = await env.AI.run('@cf/meta/m2m100-1.2b', {
text,
source_lang: 'vietnamese',
target_lang: 'english',
});
return response;
}
async function generateImage(prompt: string, env: Env) {
const response = await env.AI.run('@cf/stabilityai/stable-diffusion-xl-base-1.0', {
prompt,
});
return new Response(response.image, {
headers: { 'Content-Type': 'image/png' },
});
}typescriptWrangler — CLI#
# Init
npm create cloudflare@latest my-worker -- --type=hello-world
# Dev
npx wrangler dev
# Deploy
npx wrangler deploy
# Production
npx wrangler deploy --env production
# Logs
npx wrangler tail
# Secrets
npx wrangler secret put API_KEYbashWebSocket#
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader !== 'websocket') {
return new Response('Expected websocket', { status: 426 });
}
const [client, server] = Object.values(new WebSocketPair());
server.accept();
server.addEventListener('message', (event) => {
server.send(`Echo: ${event.data}`);
});
server.addEventListener('close', () => {
console.log('WebSocket closed');
});
return new Response(null, { status: 101, webSocket: client });
},
};typescriptDeploy từ GitHub#
# .github/workflows/deploy.yml
name: Deploy Worker
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}yamlMiddleware Pattern#
// auth.ts
export async function withAuth(request: Request, env: Env) {
const token = request.headers.get('Authorization')?.split('Bearer ')[1];
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
// Verify token
const user = await verifyToken(token, env.JWT_SECRET);
if (!user) {
return new Response('Invalid token', { status: 401 });
}
request.user = user;
}
// cors.ts
export function withCors(response: Response) {
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
}typescriptBest Practices#
// 1. Cache everything có thể
const cacheKey = `api:${request.url}`;
const cached = await env.CACHE.get(cacheKey);
if (cached) return new Response(cached);
// 2. Response early, process late (ctx.waitUntil)
ctx.waitUntil(logAnalytics(request));
// 3. Streaming response
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(new TextEncoder().encode('Hello '));
await delay(100);
controller.enqueue(new TextEncoder().encode('World'));
controller.close();
},
});
return new Response(stream);
// 4. Error handling
try {
return await handleRequest(request, env);
} catch (err) {
console.error('Handler failed:', err);
return new Response('Internal error', { status: 500 });
}typescriptKết Luận#
Cloudflare Workers là nền tảng serverless edge tốt nhất hiện tại: không cold start, 330+ locations, giá rẻ. Kết hợp KV (cache), R2 (file), D1 (SQLite), AI (inference) — bạn có thể xây dựng toàn bộ ứng dụng mà không cần server truyền thống.