La prima volta che ho messo in produzione una JSON API su
Cloudflare Workers,
l'ho deployata dal mio portatile alle 11 di sera e girava su oltre 300 data center prima che finissi il tè.
Niente Dockerfile, niente cluster Kubernetes, niente dramma da cold-start. Un singolo wrangler deploy
e un bundle da 1,2 KB. Quell'esperienza è il motivo per cui Workers è diventato il mio default per i servizi JSON-in, JSON-out —
webhook, proxy, aggregatori di API, auth perimetrale. Se l'80% del tuo backend è "parsifica JSON, fa' qualcosa, restituisci JSON",
questo è l'articolo che avrei voluto avere quando ho iniziato.
Un Cloudflare Worker è sostanzialmente una singola funzione JavaScript (o TypeScript) che gira su isolati V8 sulla rete edge di Cloudflare. Riceve una Request, restituisce una Response, e ha accesso alla Fetch API standard. Se hai usato fetch() in un browser, conosci già il 90% del runtime. Quello che non conosci è il piccolo insieme di pattern che separano un Worker giocattolo da uno che puoi davvero mandare in produzione. Di questo parla l'articolo.
Il tuo primo endpoint JSON
Ecco l'endpoint JSON utile più piccolo. Restituisce un singolo oggetto con un timestamp e un
messaggio. Salvalo come src/index.ts in un progetto Wrangler:
export default {
async fetch(request, env, ctx) {
const payload = {
message: 'Hello from the edge',
servedAt: new Date().toISOString(),
colo: request.cf?.colo ?? 'unknown',
};
return Response.json(payload);
},
};Due cose da notare. Primo, Response.json() è un helper statico che serializza
l'oggetto e imposta Content-Type: application/json per te. Non farti un tuo
new Response(JSON.stringify(x)) a meno che non ti serva un content type personalizzato — prima o poi
ti dimenticherai l'header. Secondo, request.cf.colo ti dice quale data center
Cloudflare sta servendo la richiesta. Una richiesta da Berlino mostrerà FRA,
da Tokyo mostrerà NRT. È l'intera proposta "edge" in un singolo campo.
Parsificare il body di una richiesta JSON
Gli endpoint POST devono leggere un body. La Fetch API ti offre request.json(), che legge lo stream del body e lo parsifica in una sola chiamata:
export default {
async fetch(request) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const body = await request.json();
// body is now a regular JavaScript object
const { email, plan } = body;
return Response.json({
received: { email, plan },
ok: true,
});
},
};Sembra pulito, ma questo codice ha un bug che incontrerai entro 24 ore dal deploy:
se il client invia un body vuoto o JSON malformato, request.json() lancia
un SyntaxError, il tuo Worker crasha e Cloudflare restituisce un generico 500.
Non è la risposta che vuoi mostrare ai clienti.
Gestire JSON malformato — non lasciare che diventi un 500
Avvolgi sempre il parsing del body in un try/catch e restituisci un 400 come si deve. Ecco il pattern che uso in ogni Worker:
async function readJson(request) {
try {
return { ok: true, data: await request.json() };
} catch (err) {
return {
ok: false,
error: 'Invalid JSON body',
detail: err.message,
};
}
}
export default {
async fetch(request) {
const result = await readJson(request);
if (!result.ok) {
return Response.json(result, { status: 400 });
}
const { email, plan } = result.data;
if (!email || !plan) {
return Response.json(
{ ok: false, error: 'email and plan are required' },
{ status: 422 },
);
}
return Response.json({ ok: true, email, plan });
},
};CORS per le JSON API
Se il tuo Worker sarà chiamato da un browser su un'origine diversa — che è
il caso normale — ti servono gli header
CORS.
I browser inviano un preflight OPTIONS prima della richiesta vera per tutto ciò
che è più di una semplice GET. Gestisci entrambi in un unico posto:
const CORS_HEADERS = {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
export default {
async fetch(request) {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
const data = { ping: 'pong', at: Date.now() };
return Response.json(data, { headers: CORS_HEADERS });
},
};Evita Access-Control-Allow-Origin: * su qualsiasi cosa che legga credenziali
o restituisca dati utente. È una di quelle scorciatoie che in dev sembra innocua e in prod si trasforma
in un incidente di sicurezza. Codifica le origin che effettivamente servi, o leggile da una
allow-list in env.
Inoltrare JSON a una API upstream
Uno degli usi più comuni di un Worker è come proxy leggero: nascondere una API key, rimodellare una risposta, togliere campi che al client non servono, o cucire insieme due chiamate upstream. Ecco un Worker che chiama un servizio upstream, seleziona solo i campi che ci interessano e restituisce un payload JSON più pulito:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = url.searchParams.get('id');
if (!userId) {
return Response.json({ error: 'id required' }, { status: 400 });
}
const upstream = await fetch(
`https://api.internal.example.com/users/${userId}`,
{
headers: { 'Authorization': `Bearer ${env.UPSTREAM_TOKEN}` },
},
);
if (!upstream.ok) {
return Response.json(
{ error: 'upstream failed', status: upstream.status },
{ status: 502 },
);
}
const full = await upstream.json();
// Strip internal fields before returning to the client
const safe = {
id: full.id,
displayName: full.display_name,
avatarUrl: full.avatar_url,
joinedAt: full.created_at,
};
return Response.json(safe);
},
};Due cose a cui prestare attenzione. Primo, controlla sempre upstream.ok
prima di chiamare .json() — un 500 dall'upstream avrà HTML o una pagina di errore,
e chiamarci sopra .json() solleva lo stesso tipo di eccezione di qualsiasi altro JSON malformato.
Secondo, tieni i segreti come UPSTREAM_TOKEN nei Wrangler secrets
(wrangler secret put UPSTREAM_TOKEN) — mai in wrangler.toml
e mai committati su git.
Mettere in cache le risposte JSON sull'edge
Quando un upstream è lento o costoso, la Cache API ti permette di memoizzare il JSON sull'edge. Ogni data center tiene la sua cache, quindi il primo utente a Francoforte paga il costo upstream e i successivi 10.000 lo ottengono in meno di 5ms dalla RAM vicina:
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const cacheKey = new Request(request.url, request);
let response = await cache.match(cacheKey);
if (response) {
return response;
}
const upstream = await fetch('https://api.example.com/popular-items');
const data = await upstream.json();
response = Response.json(data, {
headers: {
'Cache-Control': 'public, max-age=60',
},
});
// Don't block the response on the cache write
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
},
};Il ctx.waitUntil() è la parte meno ovvia. Senza, il
cache.put() viene atteso e la tua risposta aspetta una scrittura su disco di cui non
le importa nulla. waitUntil ti permette di restituire la risposta immediatamente
mentre il runtime tiene viva la scrittura in cache in background. È lo stesso pattern
che useresti per i beacon di analytics, l'inoltro di log, qualsiasi cosa fire-and-forget.
Sviluppo locale con Wrangler
Non ti serve un account Cloudflare per iterare. Installa Wrangler, fai lo scaffold di un progetto e ottieni un runtime Workers locale che corrisponde fedelmente alla produzione:
npm create cloudflare@latest my-json-api
cd my-json-api
npm run dev
# Worker is now live at http://localhost:8787
# Hit it from another terminal:
curl -X POST http://localhost:8787 \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","plan":"pro"}'Il runtime locale usa workerd, lo stesso motore che Cloudflare usa in produzione.
I comportamenti che differiscono (latenza di KV, semantica della cache, campi di request.cf)
sono ben documentati e raramente ti mordono per semplici JSON API. Fai il deploy con
wrangler deploy e lo stesso codice è live globalmente in pochi secondi.
Strumenti utili per costruire JSON API con i Worker
Alcuni strumenti a cui ricorro di continuo quando costruisco Worker che lavorano con JSON: JSON Formatter per formattare una brutta risposta upstream che sto cercando di capire, JSON Validator quando un payload POST fallisce e mi serve sapere esattamente dove, JSON Path per pianificare la logica di selezione dei campi prima di scriverla, e JSON Minifier quando voglio controllare se la dimensione sulla rete conta davvero per un dato endpoint.
Il formato JSON di per sé è specificato nell'
RFC 8259
— vale una lettura veloce se mai incontri un edge case tipo "il mio parser accetta NaN?"
(risposta: non dovrebbe). La
galleria di esempi Workers di Cloudflare
ha ricette per verifica JWT, A/B testing, riscrittura HTML e una dozzina di altri pattern
una volta superate le basi di questo articolo.
Tiriamo le somme
Cloudflare Workers è una soluzione davvero adatta alle JSON API — piccole, veloci, distribuite
globalmente e abbastanza economiche da poter lasciare i side project in esecuzione. Il percorso felice è
solo request.json() e Response.json(), ma il percorso produzione
coinvolge quattro abitudini extra: avvolgere il parsing del body in try/catch, aggiungere gli header CORS intenzionalmente,
controllare upstream.ok prima di parsificare risposte proxate, e usare
ctx.waitUntil per le scritture in cache e altro lavoro in background. Indovina quelle quattro
e spedirai Worker che restano su.