Il flusso completo, dal click al pixel
21 - Il flusso completo, dal click al pixel
Confessione: al progetto dei Guardiani manca ancora un pezzo, e forse non te
ne sei accorto. Il form spedisce le adesioni, il database le
custodisce... ma il frontend non ha mai mostrato il registro. Per
vedere chi c'è nella Guardia, finora, dovevi aprire localhost:3000/guardiani
e leggerti il JSON nudo. Non proprio un'esperienza da capitolo 4, quello
sull'occhio, la testa e il cuore.
Oggi completiamo l'opera: una pagina che mostra l'elenco, accoglie le nuove reclute e si aggiorna da sola dopo ogni arruolamento. Il ciclo completo (form → API → database → interfaccia) tutto insieme, per la prima volta.
🏗️ La pagina del registro
Partiamo dall'HTML. Al modulo che conosci aggiungiamo la sezione dell'elenco:
1<!DOCTYPE html>
2<html lang="it">
3<head>
4 <meta charset="UTF-8" />
5 <title>Registro dei Guardiani della Notte</title>
6 <link rel="stylesheet" href="guardiani.css" />
7 <script src="guardiani.js" defer></script>
8</head>
9<body>
10 <h1 id="titolo-pagina">Registro dei Guardiani</h1>
11
12 <!-- l'elenco: lo riempirà JavaScript -->
13 <ul id="registro" class="registro"></ul>
14
15 <!-- il form di arruolamento -->
16 <form class="form-dark">
17 <label for="name">Nome:</label>
18 <input type="text" id="name" name="name" class="campo" required />
19
20 <button class="btn-invio">Entra nella Guardia</button>
21 <p id="errore" class="errore-form"></p>
22 </form>
23</body>
24</html>Due novità: la <ul id="registro"> vuota (la riempirà JavaScript con i
dati veri) e un paragrafo per gli errori, perché il capitolo scorso non è
passato invano. Due tocchi di CSS per le new entry:
1.registro {
2 max-width: 500px;
3 margin: 24px auto;
4 padding: 0;
5 list-style: none;
6}
7
8.registro li {
9 padding: 10px 14px;
10 margin-bottom: 8px;
11 background-color: #2e2e2e;
12 border-radius: 6px;
13 color: #f2f2f2;
14}
15
16.errore-form {
17 color: #ff6b6b;
18}📖 Leggere e mostrare: la funzione chiave
E ora il cuore del capitolo, in guardiani.js: la funzione che chiede
l'elenco al server e lo disegna nella pagina. È il caricaGuardiani del
capitolo scorso più la manipolazione del DOM del capitolo 8, vecchi amici
che finalmente lavorano insieme:
1const registro = document.querySelector('#registro');
2const form = document.querySelector('.form-dark');
3const errore = document.querySelector('#errore');
4
5async function caricaRegistro() {
6 try {
7 const risposta = await fetch('http://localhost:3000/guardiani');
8 if (!risposta.ok) {
9 throw new Error(`Il server ha risposto ${risposta.status}`);
10 }
11 const guardiani = await risposta.json();
12
13 // svuota l'elenco e ridisegnalo da zero
14 registro.textContent = '';
15 for (const guardiano of guardiani) {
16 const voce = document.createElement('li');
17 voce.textContent = `${guardiano.nome} - ${guardiano.ruolo}`;
18 registro.appendChild(voce);
19 }
20 } catch (err) {
21 errore.textContent = 'Impossibile caricare il registro. Il server è acceso?';
22 }
23}
24
25// al caricamento della pagina, popola subito l'elenco
26caricaRegistro();Nota una scelta che sembra un dettaglio e invece è sicurezza: i dati entrano
nella pagina con textContent, mai con innerHTML. Il nome del
guardiano arriva dal database, cioè, in origine, da un utente: se
qualcuno si fosse arruolato come <script>...</script>, con innerHTML
quel codice verrebbe eseguito nel browser di chiunque apra il registro (si
chiama XSS, la cugina frontend della SQL injection). Con textContent è
solo testo, sempre. Mai fidarsi del client, nemmeno quando i suoi dati
tornano indietro.
✍️ Arruolare e aggiornare
Il submit ora usa tutto quello che sai, e alla fine richiama
caricaRegistro(), chiudendo il cerchio:
1form.addEventListener('submit', async (event) => {
2 event.preventDefault();
3 errore.textContent = '';
4
5 const campoNome = document.querySelector('#name');
6
7 try {
8 const risposta = await fetch('http://localhost:3000/guardiani', {
9 method: 'POST',
10 headers: { 'Content-Type': 'application/json' },
11 body: JSON.stringify({ nome: campoNome.value }),
12 });
13
14 if (!risposta.ok) {
15 throw new Error(`Il server ha risposto ${risposta.status}`);
16 }
17
18 campoNome.value = ''; // pulisci il campo
19 await caricaRegistro(); // e mostra il registro aggiornato
20 } catch (err) {
21 errore.textContent = 'Arruolamento fallito. Riprova tra poco.';
22 }
23});Apri la pagina: l'elenco si popola da solo, con Jon e chiunque sia sopravvissuto ai tuoi esperimenti. Arruola una recluta: il campo si svuota e il nome compare nell'elenco, subito. Riavvia pure il server: ci sono ancora tutti. Nessuna magia: solo ogni capitolo di questa guida al suo posto.
🔭 Il viaggio completo, al rallentatore
Nel capitolo 2 ti avevo promesso che avremmo capito "cosa succede quando clicchi un pulsante". Guardalo adesso, fotogramma per fotogramma:
- l'utente scrive "Grenn" e clicca: il DOM genera l'evento
submit - il nostro JavaScript lo intercetta, blocca il ricaricamento e compone una
POSTconfetch - la richiesta HTTP parte: metodo, header, body JSON
- Fastify la riceve, la instrada, valida il nome (mai fidarsi del client)
- il backend esegue l'INSERT parametrizzato su SQLite: Grenn è nel database, per sempre
- torna la risposta
201 Createdcol guardiano completo diid - il frontend richiama il registro (
GET), riceve il JSON aggiornato - il DOM si ridisegna: l'utente vede Grenn nell'elenco
Otto passaggi, una manciata di millisecondi, e ogni singolo passaggio lo hai costruito tu. Quando nel capitolo 2 dicevo che dietro un click "c'è un viaggio", era una promessa: adesso è una mappa che sai leggere (e percorrere) da solo.
✅ Conclusione
Il ciclo è chiuso: frontend, backend e database non sono più tre argomenti di studio ma tre organi dello stesso corpo, e li hai visti battere insieme. Da qui in poi, ogni tecnologia nuova che incontrerai (un framework, un ORM, un servizio cloud) andrà semplicemente a sostituire o potenziare uno di questi organi. Lo scheletro, ormai, lo conosci.
E non è ancora il momento dei saluti: nell'ultima parte della guida sistemiamo le buone abitudini: come si organizza un progetto vero, quel Git che ti avevo promesso ormai diciotto capitoli fa, e la mappa per continuare il viaggio da solo. Tieni il registro a portata di mano: alla Guardia servono sempre nuove reclute.