Il primo backend vero: le API dei Guardiani
14 - Il primo backend vero: le API dei Guardiani
Tutto quello che abbiamo costruito in questa guida converge qui. Il modulo HTML dei Guardiani (capitolo 6), vestito di CSS (7), reso vivo da JavaScript (8), che finalmente parla con un server vero (13) attraverso le API CRUD (12) usando il protocollo HTTP (11). Oggi il cerchio si chiude: l'adesione di Samwell arriva a destinazione.
📚 Il registro della Guardia
Riprendiamo il server.js del capitolo scorso e trasformiamolo nel registro
ufficiale. I dati, per ora, vivranno in un semplice array in memoria. Segna
questo dettaglio, a fine capitolo tornerà a bussare.
1import Fastify from 'fastify';
2
3const app = Fastify();
4
5// il registro della Guardia: per ora vive in memoria
6let guardiani = [
7 { id: 1, nome: 'Jon Snow', ruolo: 'lord comandante' },
8];
9let prossimoId = 2;
10
11// Read: l'elenco completo
12app.get('/guardiani', () => guardiani);
13
14// Read: un singolo guardiano
15app.get('/guardiani/:id', (request, reply) => {
16 const guardiano = guardiani.find((g) => g.id === Number(request.params.id));
17 if (!guardiano) {
18 return reply.code(404).send({ errore: 'Guardiano non trovato' });
19 }
20 return guardiano;
21});
22
23// Create: arruolamento
24app.post('/guardiani', (request, reply) => {
25 const { nome, ruolo } = request.body;
26
27 // mai fidarsi del client: il server valida sempre
28 if (!nome) {
29 return reply.code(400).send({ errore: 'Il nome è obbligatorio' });
30 }
31
32 const guardiano = { id: prossimoId++, nome, ruolo: ruolo ?? 'recluta' };
33 guardiani.push(guardiano);
34 return reply.code(201).send(guardiano);
35});
36
37// Update: promozione (o retrocessione)
38app.patch('/guardiani/:id', (request, reply) => {
39 const guardiano = guardiani.find((g) => g.id === Number(request.params.id));
40 if (!guardiano) {
41 return reply.code(404).send({ errore: 'Guardiano non trovato' });
42 }
43 Object.assign(guardiano, request.body);
44 return guardiano;
45});
46
47// Delete: congedo
48app.delete('/guardiani/:id', (request, reply) => {
49 guardiani = guardiani.filter((g) => g.id !== Number(request.params.id));
50 return reply.code(204).send();
51});
52
53await app.listen({ port: 3000 });
54console.log('Il registro della Guardia è in ascolto su http://localhost:3000');Rileggilo con calma: è il capitolo 12 diventato codice. Gli endpoint sono
esattamente quelli della tabella CRUD: stesso indirizzo, verbi diversi,
operazioni diverse. I codici di stato sono quelli del capitolo 11: 404 per
il guardiano che non c'è, 400 per la richiesta senza nome (mai fidarsi
del client, capitolo 10: anche se il form ha il required, il server
ricontrolla), 201 per la creazione, 204 No Content per la cancellazione
andata a buon fine.
Riavvia il server e collaudiamo dal terminale con curl, il coltellino
svizzero delle richieste HTTP:
1# Read: l'elenco
2curl http://localhost:3000/guardiani
3
4# Create: arruoliamo Sam
5curl -X POST http://localhost:3000/guardiani \
6 -H "Content-Type: application/json" \
7 -d '{"nome": "Samwell Tarly"}'
8
9# e rileggiamo l'elenco: ora sono in due
10curl http://localhost:3000/guardianiUn backend completo, collaudato, funzionante. Ora colleghiamo il frontend.
⏳ Il debito da saldare: async e await
Nel capitolo su JavaScript ti avevo promesso che alle operazioni "che richiedono tempo" avremmo dedicato lo spazio giusto. Eccolo, perché ci serve adesso: una richiesta HTTP non è istantanea: parte, viaggia, e la risposta arriva dopo. JavaScript gestisce questo "dopo" con le Promise: un oggetto che rappresenta un valore che arriverà.
La sintassi moderna per lavorarci è di una semplicità disarmante:
const risposta = await fetch('http://localhost:3000/guardiani');
const guardiani = await risposta.json();fetch è la funzione del browser per fare richieste HTTP, e await
significa letteralmente "aspetta che il risultato arrivi, poi continua".
Unica regola: await si usa dentro funzioni marcate async. Tutto qui,
il resto è pratica.
🚧 Il muro invisibile: CORS
Prima di collegare il form, ti salvo da un pomeriggio di imprecazioni. Se il frontend (aperto da file o da un altro indirizzo) chiama il tuo server, il browser bloccherà la richiesta. Nessun bug: è la CORS (Cross-Origin Resource Sharing), una protezione per cui un sito non può chiamare i server di un altro dominio a meno che quel server non lo permetta esplicitamente.
Il permesso lo diamo noi, con un plugin ufficiale:
npm install @fastify/corsE due righe in cima a server.js:
import cors from '@fastify/cors';
await app.register(cors); // per ora: tutti possono chiamarciIn produzione non si spalanca la porta così: si dichiara quale dominio può chiamare il server. Per lo sviluppo locale va benissimo, ma ricordati che questa porta esiste: prima o poi un errore CORS lo incontrerai comunque, e adesso saprai cosa significa.
🔗 Il gran finale: il form incontra il server
Apriamo il guardiani.js del capitolo 8 e cambiamo il destino di quei dati:
non più un messaggio finto costruito al volo, ma una vera POST al registro.
1const form = document.querySelector('.form-dark');
2
3form.addEventListener('submit', async (event) => {
4 event.preventDefault();
5
6 const nome = document.querySelector('#name').value;
7
8 // spediamo l'adesione al server, come da capitolo 11
9 const risposta = await fetch('http://localhost:3000/guardiani', {
10 method: 'POST',
11 headers: { 'Content-Type': 'application/json' },
12 body: JSON.stringify({ nome }),
13 });
14
15 const guardiano = await risposta.json();
16
17 const messaggio = document.createElement('p');
18 messaggio.classList.add('benvenuto');
19 messaggio.textContent = `Benvenuto nella Guardia, ${guardiano.nome}. Sei la recluta n. ${guardiano.id}.`;
20
21 form.replaceWith(messaggio);
22});Nota le differenze dal capitolo 8: la funzione dell'evento ora è async, la
richiesta è la stessa POST che facevi con curl (metodo, header, body JSON),
e il numero di recluta arriva dal server: è l'id che il backend ha
assegnato, non un numero inventato dal frontend.
Apri la pagina, compila, invia. Poi vai su http://localhost:3000/guardiani:
Samwell è lì, nel registro. Compila di nuovo con un altro nome: l'elenco
cresce. Frontend e backend, ognuno col suo mestiere, che collaborano via
HTTP. Questa, tutta intera, è una web app.
🧊 Il colpo di scena finale
Prima di festeggiare, un ultimo esperimento. Ferma il server (Ctrl+C),
riavvialo con node server.js e rileggi l'elenco:
Samwell è sparito.
Nessun bug, anche stavolta: i nostri dati vivono in un array in memoria, e la memoria muore col processo. Ogni riavvio, un'amnesia totale. Per un esperimento va bene; per un'applicazione vera è inaccettabile: i dati devono sopravvivere a riavvii, aggiornamenti e crash.
Serve un posto dove i dati vivano per sempre, indipendentemente dal server: si chiama database, ed è l'argomento della prossima parte della guida. La Guardia merita un registro che non dimentichi i suoi nomi.
Ma prima di consegnarle quel registro, c'è un'ultima lezione da imparare: come difenderlo. Hai notato che chiunque, adesso, può congedare Jon Snow con una riga di curl? Nel prossimo capitolo mettiamo il server in sicurezza.