BACK TO THE SERIESSviluppo web da zero · ゼロからのウェブ開発

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.

JAVASCRIPT · コード
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:

BASH · コード
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/guardiani

Un 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:

JAVASCRIPT · コード
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:

BASH · コード
npm install @fastify/cors

E due righe in cima a server.js:

JAVASCRIPT · コード
import cors from '@fastify/cors';

await app.register(cors); // per ora: tutti possono chiamarci

In 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.

JAVASCRIPT · コード
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.