Come comunicano frontend e backend
20 - Come comunicano frontend e backend
In questa guida fetch l'abbiamo già usato due volte, sempre di corsa: nel
capitolo 14 per spedire l'adesione di Samwell, con una spiegazione al volo di
async e await. Ha funzionato, ma "funziona" e "l'ho capito" sono due cose
diverse. In questo capitolo smontiamo la comunicazione tra frontend e backend
pezzo per pezzo, perché è il ponte su cui cammina tutto quello che hai
costruito, e sui ponti conviene sapere dove mettere i piedi.
📜 Prima di fetch: la storia di AJAX
Un po' di contesto, che aiuta a capire i nomi strani che incontrerai. Nel web delle origini ogni azione ricaricava la pagina intera: cliccavi "invia", schermata bianca, pagina nuova. Poi, attorno al 2005, si diffuse una tecnica per fare richieste HTTP da JavaScript, senza ricaricare: la battezzarono AJAX (Asynchronous JavaScript And XML: l'XML è finito in soffitta, il nome è rimasto).
È il trucco che rese possibili Gmail e Google Maps, e che nel capitolo 2
chiamavamo Single Page Application. Lo strumento dell'epoca,
XMLHttpRequest, era però così scomodo che una generazione di sviluppatori
usò librerie apposta per non toccarlo. Oggi non serve più: il browser ha
fetch, un'API moderna e pulita. Quando senti "chiamata AJAX", sappi che
si parla di questo: una richiesta HTTP fatta da JavaScript.
🧩 Anatomia di una chiamata
La forma più semplice è una GET:
const risposta = await fetch('http://localhost:3000/guardiani');Per tutto il resto si passa un secondo argomento, l'oggetto delle opzioni:
1const risposta = await fetch('http://localhost:3000/guardiani', {
2 method: 'POST',
3 headers: { 'Content-Type': 'application/json' },
4 body: JSON.stringify({ nome: 'Samwell Tarly' }),
5});Riconosci i pezzi? Sono esattamente le parti della richiesta HTTP del
capitolo 11: il metodo, gli header, il body. fetch non inventa niente: ti
dà le manopole per comporre il messaggio che ormai sai leggere riga per riga.
Una riga merita attenzione: JSON.stringify({...}). Il body di una richiesta
è testo, non un oggetto JavaScript: stringify serializza l'oggetto in
una stringa JSON. Tienilo a mente, perché tra poco incontrerai la sua metà
speculare.
✉️ La Response e il mistero del doppio await
Ti sei mai chiesto perché gli await sono due?
const risposta = await fetch('http://localhost:3000/guardiani'); // 1
const guardiani = await risposta.json(); // 2Perché arrivano due cose, in due momenti. Il primo await si risolve
quando arriva la busta: la riga di stato e gli header. Sai già che il
server ha risposto 200, ma il corpo potrebbe essere ancora in viaggio (una
lista da megabyte non arriva tutta insieme). Il secondo await aspetta il
corpo completo e lo parsa: .json() legge il testo e lo trasforma in
oggetti JavaScript veri, su cui puoi fare .map, .filter e compagnia.
Ecco la simmetria promessa: in uscita JSON.stringify (da oggetto a testo),
in entrata .json() (da testo a oggetto). Il JSON è il formato di viaggio;
ai due capi ci sono sempre oggetti JavaScript.
🚨 Il gotcha che devi conoscere: fetch non fallisce sui 404
E ora la cosa più importante del capitolo, quella che nel capitolo 14 ho
volutamente glissato. Intuitivamente ti aspetteresti che un 404 o un 500
facciano "fallire" la chiamata. No: per fetch, se una risposta è
arrivata, la missione è compiuta, anche se la risposta dice "errore". La
promise viene rigettata solo se la richiesta non è proprio partita o
arrivata: rete assente, server spento, DNS che non risolve.
Gli errori HTTP vanno controllati a mano, con la proprietà
response.ok (vera per gli stati 2xx):
1async function caricaGuardiani() {
2 try {
3 const risposta = await fetch('http://localhost:3000/guardiani');
4
5 // errore HTTP: il server ha risposto, ma male
6 if (!risposta.ok) {
7 throw new Error(`Il server ha risposto ${risposta.status}`);
8 }
9
10 return await risposta.json();
11 } catch (errore) {
12 // qui finiscono sia gli errori di rete sia il throw qui sopra
13 console.error('Impossibile caricare il registro:', errore.message);
14 return [];
15 }
16}Questo è lo scheletro di ogni chiamata fatta come si deve: try/catch per
gli imprevisti di rete, response.ok per gli errori del server, e una
decisione consapevole su cosa fare quando le cose vanno male (qui: logga e
restituisci una lista vuota, e l'app resta in piedi).
La regola pratica: due famiglie di errori, due controlli. La rete può tradirti (catch), il server può dirti di no (response.ok). Il codice che ignora una delle due famiglie funziona benissimo, fino al giorno della demo davanti al cliente.
✅ Conclusione
Ora fetch non ha più segreti: sai da dove viene (AJAX), come si compone una
chiamata (le stesse parti di una richiesta HTTP), perché gli await sono due
(busta prima, corpo poi), come viaggia il JSON (stringify in uscita, json in
entrata) e, soprattutto, come si gestiscono gli errori per davvero.
Nel prossimo capitolo mettiamo tutto in fila per l'ultima volta: il flusso completo, dal click dell'utente al pixel aggiornato, passando per Fastify e SQLite. Il registro della Guardia merita una pagina degna di questo nome.