Everything built in this guide converges here. The Watch's HTML form (chapter 6), styled with CSS (7), brought alive by JavaScript (8), finally talks to a real server (13) through CRUD APIs (12) using HTTP (11). Today the loop closes: Samwell's application reaches its destination.
📚 The Watch's register
Take last chapter's server.js and make it the official register. For now, data lives in a simple in-memory array. Remember that detail; it returns at the end.
1import Fastify from 'fastify';
2
3const app = Fastify();
4
5// the Watch register: in memory for now
6let guardiani = [
7 { id: 1, nome: 'Jon Snow', ruolo: 'lord comandante' },
8];
9let prossimoId = 2;
10
11// Read: the full list
12app.get('/guardiani', () => guardiani);
13
14// Read: one watchman
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: 'Watchman not found' });
19 }
20 return guardiano;
21});
22
23// Create: recruitment
24app.post('/guardiani', (request, reply) => {
25 const { nome, ruolo } = request.body;
26
27 // never trust the client: the server always validates
28 if (!nome) {
29 return reply.code(400).send({ errore: 'Name is required' });
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: promotion (or demotion)
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: 'Watchman not found' });
42 }
43 Object.assign(guardiano, request.body);
44 return guardiano;
45});
46
47// Delete: dismissal
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('The Watch register is listening at http://localhost:3000');Read it carefully: chapter 12 has become code. Endpoints match the CRUD table: same addresses, different verbs and operations. Status codes come from chapter 11: 404 for a missing watchman, 400 for a nameless request (never trust the client, chapter 10: even with the form's required, the server checks again), 201 for creation, 204 No Content for successful deletion.
Restart and test through the terminal with curl, the HTTP Swiss Army knife:
1# Read: the list
2curl http://localhost:3000/guardiani
3
4# Create: enlist Sam
5curl -X POST http://localhost:3000/guardiani \
6 -H "Content-Type: application/json" \
7 -d '{"nome": "Samwell Tarly"}'
8
9# read the list again: there are now two
10curl http://localhost:3000/guardianiA complete, tested, working backend. Now connect the frontend.
⏳ A promise to keep: async and await
In the JavaScript chapter, I promised time for operations "that take time". Here it is: an HTTP request is not instantaneous. It leaves, travels and responds later. JavaScript handles this "later" with Promises, objects representing a value that will arrive.
The modern syntax is disarmingly simple:
const risposta = await fetch('http://localhost:3000/guardiani');
const guardiani = await risposta.json();fetch is the browser's HTTP request function, and await means "wait for the result, then continue". One rule: use await inside functions marked async. Everything else is practice.
🚧 The invisible wall: CORS
Before connecting the form, let me save you an afternoon of swearing. If the frontend (opened from a file or another address) calls your server, the browser blocks access without the required permission. This is CORS (Cross-Origin Resource Sharing), protecting access across origins unless the server explicitly permits it.
Grant permission using an official plugin:
npm install @fastify/corsAnd two lines at the top of server.js:
import cors from '@fastify/cors';
await app.register(cors); // for now: all origins are allowedIn production, do not leave the door wide open: declare which domain may access the server. This is fine for local development, but remember the door exists: eventually you will encounter a CORS error, and now you know what it means.
🔗 The grand finale: the form meets the server
Open chapter 8's guardiani.js and change the data's destination: a real POST to the register instead of a locally fabricated message.
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 // send the application to the server, as in chapter 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 = `Welcome to the Watch, ${guardiano.nome}. You are recruit no. ${guardiano.id}.`;
20
21 form.replaceWith(messaggio);
22});Notice the differences: the event function is now async, the request is the same POST used with curl (method, headers, JSON body), and the recruit number comes from the server: the backend-assigned id, not a number invented by the frontend.
Open the page, fill it in, submit. Visit http://localhost:3000/guardiani: Samwell is in the register. Submit another name: the list grows. Frontend and backend doing their own jobs and cooperating through HTTP. This whole thing is a web app.
🧊 The final plot twist
Before celebrating, one last experiment. Stop the server (Ctrl+C), restart with node server.js and read the list again:
Samwell has disappeared.
Again, no bug: our data lives in an in-memory array, and memory dies with the process. Every restart brings total amnesia. Fine for an experiment; unacceptable for a real application: data must survive restarts, updates and crashes.
It needs somewhere to live persistently, independently of the server: a database, the next part of the guide. The Watch deserves a register remembering its names.
But first, one lesson remains: how to defend it. Notice anyone can now dismiss Jon Snow with one curl line? In the next chapter, we secure the server.