İlk Cloudflare Çalışanınız: İşleyiciyi, Kovucuyu Getirin ve Dağıtın
Cloudflare Worker'ı oluşturmaya, test etmeye ve dağıtmaya yönelik adım adım kılavuz sıfır: getirme işleyicisinden yönetici dağıtımına, ortam değişkenlerinden geçerek, sırlar ve gelişmiş yönlendirme.
Üretimde Sıfırdan İşçiye
Önceki yazıda V8 izolatlarını ve neden soğuğu ortadan kaldırdıklarını anlamıştık. başlar. Bu makalede somut bir şey inşa ediyoruz: Yöneten bir İşçi yönlendirme, ortam değişkenlerini okur, sırları kullanır ve JSON ile yanıt verir. Sonunda Cloudflare hesabınızda küresel olarak erişilebilen bir Çalışanınız olacak Başlangıçta 1 ms'den daha kısa sürede.
Ne Öğreneceksiniz
- Wrangler CLI ve TypeScript ile geliştirme ortamı kurulumu
- Dosya yapısı
wrangler.tomlve tüm ana alanlar - Getirme işleyicisi: request, env, ExecutionContext ayrıntılı olarak
- Karmaşık URL modelleri için manuel ve itty-router yönlendirmesi
- Ortam değişkenleri, sırlar ve bunlara tür uyumlu TypeScript'te nasıl erişileceği
- Yerel test ile
wrangler devve sıcak yeniden yükleme - Üretim ve ortam yönetiminde devreye alma (hazırlama/üretim)
- Özel alanlar ve rota modelleri
Önkoşullar ve İlk Kurulum
Ücretsiz bir Cloudflare hesabına ve Node.js 18+ sürümüne ihtiyacınız olacak. Ücretsiz katman günde 100.000 istek içerir; bu, geliştirme için fazlasıyla yeterlidir ve kişisel projeler.
# Installa Wrangler globalmente (CLI ufficiale Cloudflare)
npm install -g wrangler
# Verifica l'installazione
wrangler --version
# wrangler 3.x.x
# Autenticati con il tuo account Cloudflare
# Apre il browser per il login OAuth
wrangler login
# Verifica che l'autenticazione abbia funzionato
wrangler whoami
# Logged in as: tuo@email.com
# Account ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
İşçi Projesini Oluşturun
Wrangler bir komut sağlar create projeyi kiminle rafa kaldırıyor
TypeScript ve gerekli tüm bağımlılıklar:
# Crea un nuovo Worker con template TypeScript
npm create cloudflare@latest mio-primo-worker -- --type worker
# Oppure con Wrangler direttamente
wrangler init mio-primo-worker --type worker
# Struttura del progetto generata:
mio-primo-worker/
├── src/
│ └── index.ts # Entry point del Worker
├── test/
│ └── index.spec.ts # Test con Vitest (opzionale)
├── wrangler.toml # Configurazione principale
├── tsconfig.json # TypeScript config
├── package.json
└── .gitignore
cd mio-primo-worker
npm install
wrangler.toml: Merkezi Yapılandırma
Dosya wrangler.toml İşçi konfigürasyonunun kalbidir.
Her alanın dağıtım davranışı üzerinde etkileri vardır:
# wrangler.toml
# Nome del Worker (deve essere unico nel tuo account)
name = "mio-primo-worker"
# Versione del formato di configurazione
main = "src/index.ts"
# Compatibilita: usa sempre una data recente per avere le ultime API
compatibility_date = "2025-11-01"
# Flags per abilitare funzionalita sperimentali/stabili
compatibility_flags = ["nodejs_compat"]
# Workers paused: false (default)
# workers_dev = true # Abilita il sottodominio .workers.dev (default true)
# Variabili d'ambiente (visibili nel codice, NON secret)
[vars]
ENVIRONMENT = "production"
API_BASE_URL = "https://api.esempio.it"
MAX_RETRIES = "3"
# Route patterns: il Worker risponde a questi URL
# (richiede un dominio nel tuo account Cloudflare)
# [[routes]]
# pattern = "esempio.it/api/*"
# zone_name = "esempio.it"
# Ambiente di staging (sovrascrive i valori di root)
[env.staging]
name = "mio-primo-worker-staging"
vars = { ENVIRONMENT = "staging" }
# Binding KV Namespace (vedi articolo 3)
# [[kv_namespaces]]
# binding = "CACHE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# preview_id = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
Getirme İşleyicisi: Tam Anatomi
Her Çalışanın bir yöntemle varsayılan bir nesneyi dışa aktarması gerekir fetch.
Her parametreyi ayrıntılı olarak görelim:
// src/index.ts
export interface Env {
// Variabili definite in [vars] di wrangler.toml
ENVIRONMENT: string;
API_BASE_URL: string;
MAX_RETRIES: string;
// Secrets aggiunti con wrangler secret put
DATABASE_URL: string;
API_KEY: string;
// Binding KV (se configurato)
// CACHE: KVNamespace;
}
export default {
/**
* Fetch handler: chiamato per ogni richiesta HTTP
*
* @param request - La richiesta HTTP in arrivo (Request standard WhatWG)
* @param env - I binding: variabili, secrets, KV, R2, D1, Durable Objects, AI
* @param ctx - Execution context: waitUntil() per operazioni post-risposta
*/
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Informazioni Cloudflare sulla richiesta
const cf = request.cf;
console.log({
country: cf?.country, // "IT", "US", "DE", ...
city: cf?.city, // "Milan", "New York", ...
datacenter: cf?.colo, // "MXP" (Milano), "LHR" (Londra), ...
asn: cf?.asn, // Numero ASN dell'ISP
tlsVersion: cf?.tlsVersion, // "TLSv1.3"
});
const url = new URL(request.url);
const { pathname, searchParams } = url;
// Routing semplice basato sul pathname
if (pathname === '/') {
return handleRoot(request, env);
}
if (pathname.startsWith('/api/')) {
return handleApi(request, env, ctx, pathname);
}
if (pathname === '/health') {
return new Response(JSON.stringify({ status: 'ok', env: env.ENVIRONMENT }), {
headers: { 'Content-Type': 'application/json' },
});
}
return new Response('Not Found', {
status: 404,
headers: { 'Content-Type': 'text/plain' },
});
},
};
async function handleRoot(request: Request, env: Env): Promise<Response> {
const html = `
<!DOCTYPE html>
<html>
<head><title>Mio Primo Worker</title></head>
<body>
<h1>Hello from ${env.ENVIRONMENT}!</h1>
<p>Cloudflare Worker is running.</p>
</body>
</html>
`;
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
}
async function handleApi(
request: Request,
env: Env,
ctx: ExecutionContext,
pathname: string,
): Promise<Response> {
// Verifica l'API key (dal header Authorization)
const authHeader = request.headers.get('Authorization');
if (!authHeader || authHeader !== `Bearer ${env.API_KEY}`) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
// Proxy verso il backend con retry logic
const maxRetries = parseInt(env.MAX_RETRIES);
const backendUrl = `${env.API_BASE_URL}${pathname}`;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(backendUrl, {
method: request.method,
headers: request.headers,
body: request.method !== 'GET' ? request.body : undefined,
});
if (response.ok || attempt === maxRetries - 1) {
return response;
}
} catch (error) {
if (attempt === maxRetries - 1) {
return new Response(JSON.stringify({ error: 'Backend unavailable' }), {
status: 502,
headers: { 'Content-Type': 'application/json' },
});
}
// Exponential backoff (attenzione: consuma CPU time)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
}
}
return new Response(JSON.stringify({ error: 'Max retries exceeded' }), { status: 502 });
}
itty-router ile Gelişmiş Yönlendirme
Çok sayıda rotanın olduğu uygulamalar için manuel yönlendirmenin yazılması ayrıntılı hale gelir. Kütüphane küçük yönlendirici İşçiler için özel olarak tasarlanmıştır: küçük harf (1 KB'den küçük) ve aynı standart Web API'lerini kullanır.
# Installa itty-router
npm install itty-router
// src/index.ts con itty-router
import { Router, error, json, withParams } from 'itty-router';
export interface Env {
ENVIRONMENT: string;
API_KEY: string;
}
const router = Router();
// Middleware globale: aggiunge params al request object
router.all('*', withParams);
// Middleware di autenticazione
const authenticate = (request: Request & { params: Record<string, string> }, env: Env) => {
const apiKey = request.headers.get('x-api-key');
if (apiKey !== env.API_KEY) {
return error(401, 'Invalid API key');
}
// Ritornare undefined continua al prossimo handler
};
// Route pubbliche
router.get('/', () => new Response('Hello from Workers!'));
router.get('/health', (req: Request, env: Env) =>
json({ status: 'ok', environment: env.ENVIRONMENT, timestamp: Date.now() })
);
// Route con parametri URL
router.get('/users/:userId', authenticate, async (request, env: Env) => {
const { userId } = request.params;
// Fetch dall'origine con parametri
const user = await fetchUser(userId, env);
if (!user) {
return error(404, `User ${userId} not found`);
}
return json(user);
});
// Route con query parameters
router.get('/search', async (request: Request) => {
const url = new URL(request.url);
const query = url.searchParams.get('q');
const page = parseInt(url.searchParams.get('page') ?? '1');
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '10'), 100);
if (!query) {
return error(400, 'Query parameter "q" is required');
}
return json({
query,
page,
limit,
results: [], // qui andrebbero i risultati reali
});
});
// POST con body JSON
router.post('/users', authenticate, async (request: Request, env: Env) => {
let body: unknown;
try {
body = await request.json();
} catch {
return error(400, 'Invalid JSON body');
}
// Validazione base
if (typeof body !== 'object' || body === null || !('name' in body)) {
return error(422, 'Field "name" is required');
}
// Logica di business...
return json({ created: true, data: body }, { status: 201 });
});
// Catch-all per 404
router.all('*', () => error(404, 'Route not found'));
async function fetchUser(userId: string, env: Env) {
// Esempio: fetch da un'API esterna
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) return null;
return response.json();
}
export default {
fetch: router.fetch,
};
Ortam Değişkenlerini ve Sırlarını Yönetme
Workers iki tür yapılandırma değerini birbirinden ayırır: değişkenler (kodda ve Wrangler'da görünür, şifrelenmemiş) e sırlar (şifrelidir, çalışma zamanında yalnızca Worker'da görünür, kontrol panelinde hiçbir zaman silinmez).
# Aggiungere un secret (interattivo: chiede il valore)
wrangler secret put DATABASE_URL
# Enter a secret value: [digitare il valore, non visibile]
# Successfully created secret DATABASE_URL
# Aggiungere un secret da stdin (utile in CI/CD)
echo "postgres://user:password@host:5432/db" | wrangler secret put DATABASE_URL
# Listare i secrets (mostra solo i nomi, non i valori)
wrangler secret list
# [
# { "name": "DATABASE_URL", "type": "secret_text" },
# { "name": "API_KEY", "type": "secret_text" }
# ]
# Eliminare un secret
wrangler secret delete DATABASE_URL
# Aggiungere secrets per ambiente specifico
wrangler secret put DATABASE_URL --env staging
wrangler secret put DATABASE_URL --env production
Değişkenler ve Sırlar: Ne Zaman Kullanılmalı?
- Değişkenler ([vars]): Genel API URL'leri, özellik işaretleri, yapılandırma çevrenin. Bunları hassas veriler için kullanmayın çünkü bunlar wrangler.toml'de görünür. ve Cloudflare Kontrol Panelinde.
- Sırlar: API anahtarları, veritabanı şifreleri, JWT belirteçleri, tüm veriler hassas. Bunlar şifrelenir ve günlüklerde bile hiçbir zaman açık bir şekilde açığa çıkmazlar.
Wrangler dev ile Yerel Kalkınma
wrangler dev Workers çalışma zamanını simüle eden yerel bir sunucuyu başlatır.
otomatik sıcak yeniden yükleme:
# Avvia il server di sviluppo locale
npm run dev # equivale a: wrangler dev
# Output tipico:
# ⛅️ wrangler 3.x.x
# -------------------
# Using vars defined in .dev.vars
# Your Worker has access to the following bindings:
# - Vars:
# - ENVIRONMENT: "development"
# - API_BASE_URL: "https://api.esempio.it"
# ⎔ Starting local server...
# [wrangler:inf] Ready on http://localhost:8787
# In un altro terminale, testa le route:
curl http://localhost:8787/health
# {"status":"ok","environment":"development","timestamp":1742000000000}
curl -H "x-api-key: test-key" http://localhost:8787/users/1
# {"id":1,"name":"Leanne Graham","username":"Bret",...}
Yerel gizli diziler için bir dosya oluşturun .dev.vars (eklenecek
.gitignore!):
# .dev.vars - NON committare questo file!
# Questi valori sovrascrivono [vars] in wrangler.toml durante lo sviluppo locale
DATABASE_URL=postgres://localhost:5432/dev_db
API_KEY=dev-test-key-12345
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxx
# Per usare .dev.vars anche per variabili non-secret:
ENVIRONMENT=development
API_BASE_URL=http://localhost:3000
Üretimde Dağıt
Deploy, TypeScript'i derleyen ve bağımlılıkları bir araya getiren tek bir komuttur. esbuild ile sonucu 300'den fazla Cloudflare PoP'a yükleyin:
# Deploy all'ambiente di default (production)
wrangler deploy
# Output:
# ⛅️ wrangler 3.x.x
# -------------------
# Total Upload: 45.32 KiB / gzip: 12.18 KiB
# Uploaded mio-primo-worker (1.23 sec)
# Published mio-primo-worker (0.45 sec)
# https://mio-primo-worker.tuo-account.workers.dev
# Current Deployment ID: xxxxxxxxxx
# Current Version ID: yyyyyyyyyy
# Deploy su ambiente specifico
wrangler deploy --env staging
# Preview del deploy senza pubblicare
wrangler deploy --dry-run
# Verificare il Worker deployato
curl https://mio-primo-worker.tuo-account.workers.dev/health
Özel Etki Alanları ve Rota Modelleri
Çalışanı özel bir etki alanıyla (Cloudflare üzerinde olması gereken) ilişkilendirmek için yapılandırın
içindeki yollar wrangler.toml:
# wrangler.toml - configurazione route con dominio custom
name = "api-gateway"
main = "src/index.ts"
compatibility_date = "2025-11-01"
# Opzione 1: route pattern (piu flessibile)
# Corrisponde a: esempio.it/api/ e tutti i sottopercorsi
[[routes]]
pattern = "esempio.it/api/*"
zone_name = "esempio.it"
# Opzione 2: custom domain diretto (Workers For Platforms)
# [env.production]
# routes = [{ pattern = "api.esempio.it/*", zone_name = "esempio.it" }]
# Opzione 3: Worker Routes nel dashboard Cloudflare
# (utile per pattern complessi o gestione manuale)
Hata Yönetimi ve Yanıt Yardımcıları
Sağlam bir hata işleme modeli genel bir sarmalayıcı kullanır:
// src/utils/error-handler.ts
export function handleError(error: unknown): Response {
if (error instanceof WorkerError) {
return new Response(JSON.stringify({
error: error.message,
code: error.code,
}), {
status: error.status,
headers: { 'Content-Type': 'application/json' },
});
}
// Errore non atteso: non esporre dettagli in produzione
console.error('Unexpected error:', error);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
export class WorkerError extends Error {
constructor(
message: string,
public readonly status: number = 500,
public readonly code: string = 'INTERNAL_ERROR'
) {
super(message);
this.name = 'WorkerError';
}
static notFound(resource: string): WorkerError {
return new WorkerError(`${resource} not found`, 404, 'NOT_FOUND');
}
static unauthorized(): WorkerError {
return new WorkerError('Unauthorized', 401, 'UNAUTHORIZED');
}
static badRequest(message: string): WorkerError {
return new WorkerError(message, 400, 'BAD_REQUEST');
}
}
// src/index.ts - integrazione con il fetch handler
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
try {
return await handleRequest(request, env, ctx);
} catch (error) {
return handleError(error);
}
},
};
CORS: Çalışanlarda Çapraz Köken Yönetimi
Farklı etki alanlarındaki tarayıcılardan çağrılması gereken API'ler için CORS'u yapılandırın doğrudan İşçide:
// src/middleware/cors.ts
const ALLOWED_ORIGINS = [
'https://esempio.it',
'https://www.esempio.it',
'http://localhost:3000', // solo in development
];
export function corsHeaders(request: Request, origin?: string): HeadersInit {
const requestOrigin = origin ?? request.headers.get('Origin') ?? '';
const allowed = ALLOWED_ORIGINS.includes(requestOrigin) ? requestOrigin : '';
return {
'Access-Control-Allow-Origin': allowed,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key',
'Access-Control-Max-Age': '86400',
'Vary': 'Origin',
};
}
// Gestisci il preflight OPTIONS
export function handleCors(request: Request): Response | null {
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: corsHeaders(request),
});
}
return null; // Continua con il normale handling
}
// Integrazione nel fetch handler
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const corsResponse = handleCors(request);
if (corsResponse) return corsResponse;
const response = await router.fetch(request, env);
// Aggiungi headers CORS a tutte le risposte
const newResponse = new Response(response.body, response);
Object.entries(corsHeaders(request)).forEach(([key, value]) => {
newResponse.headers.set(key, value);
});
return newResponse;
},
};
Sonuçlar ve Sonraki Adımlar
Artık sağlam TypeScript Çalışanları oluşturmak için tüm araçlara sahipsiniz: ile Yönlendirme
itty-router, değişkenlerin ve sırların yönetimi, yerel kalkınma wrangler dev
ve üretime dağıtın. Bir sonraki adım İşçilerinizi durum bilgisi haline getirmektir
Cloudflare'in sunduğu farklı depolama katmanlarıyla.
Serideki Sonraki Yazılar
- Madde 3: Kenar Kalıcılığı — KV, R2 ve D1 — Küresel önbelleğe alma için Workers KV'nin, nesne depolama için R2'nin ne zaman ve nasıl kullanılacağı Çıkış ücreti olmadan S3 uyumlu ve ilişkisel SQL sorguları için D1.
- Madde 4: Dayanıklı Nesneler — ilkel durum Son derece tutarlı ve uçta WebSocket.
- Madde 9: Miniflare ve Vitest ile test etme — birim testleri ve Dağıtım gerektirmeyen Çalışanlar için entegrasyon testleri.







