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

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:

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

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

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

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


  1. l'utente scrive "Grenn" e clicca: il DOM genera l'evento submit
  2. il nostro JavaScript lo intercetta, blocca il ricaricamento e compone una POST con fetch
  3. la richiesta HTTP parte: metodo, header, body JSON
  4. Fastify la riceve, la instrada, valida il nome (mai fidarsi del client)
  5. il backend esegue l'INSERT parametrizzato su SQLite: Grenn è nel database, per sempre
  6. torna la risposta 201 Created col guardiano completo di id
  7. il frontend richiama il registro (GET), riceve il JSON aggiornato
  8. 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.