Twój pierwszy pracownik Cloudflare: obsługa pobierania, wrangler i wdrażanie
Przewodnik krok po kroku dotyczący tworzenia, testowania i wdrażania pracownika Cloudflare z zero: od modułu obsługi pobierania do wdrożenia wranglera, przechodząc przez zmienne środowiskowe, sekrety i zaawansowane routing.
Od zera do pracownika produkcyjnego
W poprzednim artykule zrozumieliśmy izolaty V8 i dlaczego eliminują przeziębienie zaczyna. W tym artykule budujemy coś konkretnego: Pracownika, który zarządza routing, odczytuje zmienne środowiskowe, używa sekretów i odpowiada za pomocą JSON. Na koniec będziesz mieć pracownika wdrożonego na swoim koncie Cloudflare, dostępnego globalnie w czasie krótszym niż 1 ms podczas uruchamiania.
Czego się nauczysz
- Konfiguracja środowiska programistycznego za pomocą Wrangler CLI i TypeScript
- Struktura pliku
wrangler.tomli wszystkie główne pola - Procedura pobierania: żądanie, env, ExecutionContext w szczegółach
- Ręczny i itty-router routing dla złożonych wzorców adresów URL
- Zmienne środowiskowe, sekrety i sposób uzyskiwania do nich dostępu w TypeScript z bezpiecznym typem
- Test lokalny z
wrangler devi gorące przeładowanie - Wdrożenie w zarządzaniu produkcją i środowiskiem (staging/prod)
- Niestandardowe domeny i wzorce tras
Wymagania wstępne i konfiguracja wstępna
Będziesz potrzebować darmowego konta Cloudflare i Node.js 18+. Poziom bezpłatny obejmuje 100 000 żądań dziennie, co jest więcej niż wystarczające do rozwoju i projekty osobiste.
# 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
Utwórz projekt pracownika
Wrangler wydaje polecenie create z kim odkłada projekt
TypeScript i wszystkie niezbędne zależności:
# 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: Konfiguracja centralna
Plik wrangler.toml jest to serce konfiguracji Worker.
Każde pole ma wpływ na zachowanie podczas wdrażania:
# 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"
Osoba zajmująca się aportowaniem: pełna anatomia
Każdy proces roboczy musi wyeksportować obiekt domyślny za pomocą metody fetch.
Przyjrzyjmy się szczegółowo każdemu parametrowi:
// 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 });
}
Zaawansowany routing z itty-routerem
W przypadku aplikacji z wieloma trasami ręczne pisanie routingu staje się szczegółowe. Biblioteka itty-router został zaprojektowany specjalnie dla pracowników: tak jest małymi literami (mniej niż 1 KB) i korzysta z tych samych standardowych interfejsów API sieci Web.
# 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,
};
Zarządzanie zmiennymi środowiskowymi i sekretami
Workers rozróżnia dwa typy wartości konfiguracyjnych: zmienne (widoczne w kodzie i w Wranglerze, niezaszyfrowane) e tajniki (zaszyfrowane, widoczne tylko w procesie roboczym w czasie wykonywania, nigdy nieczytelne w panelu kontrolnym).
# 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
Zmienne a tajemnice: kiedy czego używać
- Zmienne ([vars]): Publiczne adresy URL API, flagi funkcji, konfiguracja środowiska. Nie używaj ich do wrażliwych danych, ponieważ są one widoczne w pliku wrangler.toml oraz w panelu kontrolnym Cloudflare.
- Tajniki: Klucze API, hasła do baz danych, tokeny JWT, dowolne dane wrażliwy. Są one szyfrowane i nigdy nie są ujawniane jawnie, nawet w dziennikach.
Rozwój lokalny z firmą Wrangler Dev
wrangler dev uruchamia lokalny serwer, który symuluje środowisko wykonawcze Workers
automatyczne ładowanie na gorąco:
# 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",...}
W przypadku lokalnych wpisów tajnych utwórz plik .dev.vars (do dodania
.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
Wdróż w produkcji
Wdróż to pojedyncze polecenie, które kompiluje TypeScript, łącząc zależności za pomocą esbuild i prześlij wynik do ponad 300 punktów PoP Cloudflare:
# 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
Domeny niestandardowe i wzorce tras
Aby powiązać pracownika z domeną niestandardową (która musi znajdować się w Cloudflare), skonfiguruj
trasy w 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)
Zarządzanie błędami i pomocnicy reagowania
Solidny wzorzec obsługi błędów wykorzystuje opakowanie globalne:
// 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: Zarządzanie między źródłami w pracownikach
W przypadku interfejsów API, które muszą być wywoływane z przeglądarek w różnych domenach, skonfiguruj CORS bezpośrednio w Worker:
// 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;
},
};
Wnioski i dalsze kroki
Masz teraz wszystkie narzędzia do tworzenia solidnych procesów roboczych TypeScript: Routing
itty-router, zarządzanie zmiennymi i sekretami, rozwój lokalny za pomocą wrangler dev
i wdrożyć do produkcji. Następnym krokiem jest uczynienie pracowników stanowymi
z różnymi warstwami przechowywania udostępnianymi przez Cloudflare.
Następne artykuły z serii
- Artykuł 3: Trwałość krawędzi — KV, R2 i D1 — kiedy i jak używać Workers KV do globalnego buforowania, R2 do przechowywania obiektów Kompatybilny z S3 bez opłat za wyjście i D1 dla relacyjnych zapytań SQL.
- Artykuł 4: Obiekty Trwałe — stan pierwotny mocno spójny i WebSocket na krawędzi.
- Artykuł 9: Testowanie za pomocą Miniflare i Vitest — testy jednostkowe oraz testy integracyjne dla Workerów bez wdrożenia.







