Nel capitolo 3 abbiamo visto il server Web che serve file, e poi il server con templating che li compone. Con Node.js il server diventa un programma che decide cosa rispondere: e appena si arriva a questo punto, la domanda smette di essere tecnica e diventa di design. Progettare una API REST significa rispondere a un elenco preciso di domande, che l'esercitazione mette in fila senza girarci intorno:
101 200 201 302 400 404 418 500…/users/userId, /leagues/leagueNameGET POST PUT DELETE PATCH…/message/messageId/send/api/v1/leagues), in query (/api/leagues?v=1), negli header HTTP…Sono domande che non hanno una risposta imposta dal protocollo: HTTP permette tranquillamente GET /deleteUser?id=1234. È il design a stabilire che quella richiesta è sbagliata, e le prossime sezioni servono esattamente a giustificare perché.
Quando si modellano le risorse di una API si può partire da alcuni archetipi di risorse di base. Un'API REST è composta da quattro archetipi distinti: document, collection, store e controller.
Una risorsa documento è un concetto simile a un'istanza di oggetto o a un record di database. La rappresentazione del suo stato include in genere sia valori sia collegamenti ad altre risorse collegate. Il tipo documento è l'archetipo di base per gli altri archetipi. Ciascuno di questi URI identifica un documento:
http://api.soccer.restapi.org/leagues/seattle
http://api.soccer.restapi.org/leagues/seattle/teams/trebuchet
Un documento può avere risorse figlio che rappresentano i suoi concetti subordinati; con la sua capacità di riunire molti tipi di risorse diversi sotto un unico genitore, un documento è il candidato logico per la risorsa radice di una API REST — nell'esempio, http://api.soccer.restapi.org identifica il punto di ingresso.
Una collection è un repository di risorse gestita dal server, usata per raggruppare risorse correlate. I client possono proporre nuove risorse da aggiungere a una collezione, ma spetta alla collezione scegliere se creare o meno la risorsa, cosa vuole contenere, e decidere gli URI delle risorse contenute:
http://api.soccer.restapi.org/leagues
http://api.soccer.restapi.org/leagues/seattle/teams
Uno store è un repository di risorse gestito dal client. Consente al client di inserire risorse, recuperarle ed eliminarle; da soli gli store non creano nuove risorse e non generano nuovi URI. Ogni risorsa memorizzata ha un URI che è stato scelto da un client quando è stata inizialmente inserita. L'esempio mostra un utente (con ID 1234) che inserisce un documento chiamato alonso nel proprio archivio di preferiti:
PUT /users/1234/favorites/alonso
Un controller modella un concetto procedurale: funzioni eseguibili, con parametri e valori restituiti, di input e di output. Una API REST si basa sulle risorse controller per eseguire azioni specifiche dell'applicazione che non possono essere mappate logicamente su uno dei metodi standard (CRUD). In genere compaiono come ultimo segmento in un percorso URI, senza risorse figlio che li seguono nella gerarchia:
POST /alerts/245743/resend
La distinzione decisiva è tra collection e store, e sta in chi decide l'URI. In una collection il client fa POST e il server risponde dicendo dove ha messo la risorsa; in uno store il client fa PUT su un URI che ha scelto lui. È la stessa differenza che passa tra "consegnare un documento a un archivio che lo protocolla" e "mettere un file in una cartella che è tua".
L'esempio guida dell'esercitazione è una smart city. Il punto di ingresso della API è http://api.smartcity.example.com/api, l'entità City ha un name (stringa) e una Population (numero), e le risorse possibili sono:
| URI | Archetipo | Significato |
|---|---|---|
/cities | collection | collezione di città |
/cities/cesena | document | documento che rappresenta la città di Cesena |
/cities/cesena/sensors | collection | collezione di sensori a Cesena |
/cities/cesena/sensors/12345 | document | sensore con id 12345 a Cesena |
Le operazioni sulle collezioni sono quattro: GET .../cities elenca tutte le città, POST .../cities crea una nuova città nella collezione, PUT .../cities aggiorna la collezione con il contenuto della richiesta, DELETE .../cities elimina tutte le città. Sui documenti invece: GET .../cities/cesena recupera i dettagli, PUT .../cities/cesena li aggiorna, DELETE .../cities/cesena elimina la città — e POST .../cities/cesena non ha senso.
| Risorsa | GET | POST | PUT | DELETE |
|---|---|---|---|---|
/cities | Ottiene la lista di città | Crea una nuova città | Aggiorna tutta la collezione | Cancella tutta la collezione |
/cities/cesena | Ottiene i dettagli della città di Cesena | non applicabile | Aggiorna i dettagli della città di Cesena | Cancella la città di Cesena |
Lo stesso schema si applica alle relazioni tra risorse: GET .../cities/cesena/sensors elenca tutti i sensori a Cesena, POST .../cities/cesena/sensors ne crea uno nuovo, GET .../cities/cesena/sensors/12345 recupera il contenuto di quel sensore, PUT .../cities/cesena/sensors/12345 ne aggiorna i dettagli. La relazione City has Sensor diventa un percorso.
Le API REST utilizzano gli Uniform Resource Identifier per indirizzare le risorse, e i designer spaziano tra soluzioni molto diverse: dal parlante http://api.restapi.org/paris/louvre/leonardo-da-vinci/mona-lisa all'opaco http://api.restapi.org/68dd0-a9d3-11e0-9f1c-0800200c9a66. Le regole raccolte a lezione sono queste.
| Regola | Esempio |
|---|---|
| La barra indica una relazione gerarchica, e non va inclusa in fondo agli URI | /leagues/seattle/teams |
| I trattini migliorano la leggibilità | http://api.example.org/blogs/mark/this-is-my-first-post |
| Preferire le lettere minuscole nei percorsi | http://api.example.org/my-folder/my-doc |
| Le estensioni di file non devono comparire | http://api.example.org/students/3248234/transcripts/2005/fall |
| Sostantivo singolare per i document | /leagues/seattle/teams/players/claudio |
| Sostantivo plurale per le collection | /leagues/seattle/teams/players |
| Sostantivo plurale per gli store | /artists/mikemassedotcom/playlists |
| Verbo o frase per i controller | /students/morgan/register, /qa/nightly/runTestSuite |
| I segmenti possono essere sostituiti da valori basati sull'identità | /leagues/:leagueId/teams/:teamId |
| I nomi delle funzioni CRUD non vanno negli URI | DELETE /users/1234 |
| La query filtra collection e store | GET /users?role=admin |
| La query impagina i risultati | GET /users?pageSize=25&pageStartIndex=50 |
Gli antipattern citati esplicitamente per la penultima riga sono quattro, e sono istruttivi perché sbagliano in modi diversi: GET /deleteUser?id=1234 (verbo nell'URI e metodo sbagliato, con un GET che modifica), GET /deleteUser/1234 (idem), DELETE /deleteUser/1234 (metodo giusto ma verbo ridondante nell'URI), POST /users/1234/delete (l'operazione mascherata da risorsa controller quando esiste già il metodo standard).
Sul versioning l'esercitazione mette a confronto due forme senza imporne una: http://api.soccer.restapi.org/my-app/api/leagues?version=1 e http://api.soccer.restapi.org/my-app/api/v1/leagues.
Una API REST utilizza comunemente il corpo del messaggio di risposta per trasmettere lo stato della risorsa identificata dalla richiesta, con un formato basato su testo che rappresenta lo stato come un insieme di campi significativi. Oggi i formati più usati sono XML e JSON:
{
"firstName": "Mario",
"lastName": "Rossi",
"address": {
"city": "Cesena",
"postalCode": "47521"
}
}
Ogni risposta HTTP contiene poi un codice di stato che indica l'esito della richiesta del client: sono numeri a tre cifre, raggruppati per classe.
| Classe | Range | Significato |
|---|---|---|
| 1xx | 100-199 | Informazioni |
| 2xx | 200-299 | Successo |
| 3xx | 300-399 | Reindirizzamento |
| 4xx | 400-499 | Errore lato client |
| 5xx | 500-599 | Errore lato server |
200 OK — richiesta elaborata correttamente201 Created — risorsa creata con successo (es. POST)204 No Content — operazione avvenuta senza restituire corpo (es. DELETE)301 Moved Permanently — risorsa spostata in modo definitivo302 Found — reindirizzamento temporaneo304 Not Modified — risorsa non modificata (usata con la cache)400 Bad Request — richiesta non valida401 Unauthorized — autenticazione richiesta403 Forbidden — accesso negato anche se autenticato404 Not Found — risorsa non trovata418 I'm a teapot — easter egg (RFC 2324)500 Internal Server Error — errore generico lato server502 Bad Gateway — risposta non valida da un altro server503 Service Unavailable — servizio temporaneamente non disponibile504 Gateway Timeout — tempo di risposta scadutoSi possono utilizzare codici standard e personalizzati, purché rispettino il significato della loro classe. La differenza tra 401 e 403 è la più fraintesa: il primo dice "non so chi sei", il secondo "so chi sei e non puoi".
Ricordate dal capitolo 3 che i codici 500, 502, 503 e 504 sono anche i sintomi tipici dell'overload di un server web. Restituire un 500 per un dato di input sbagliato non è solo impreciso: sposta la colpa dal client al server e rende impossibile distinguere un bug applicativo da un problema di carico quando si leggono i log.
Una API REST ben progettata deve essere anche ben documentata. La documentazione permette a sviluppatori e utenti di comprendere le risorse disponibili e i loro URI, conoscere i metodi supportati, vedere lo schema di richieste e risposte e conoscere i possibili codici di stato restituiti.
OpenAPI è uno standard per descrivere le API REST in modo strutturato e leggibile da una macchina. È stato creato inizialmente come Swagger Specification ed è ora gestito dalla OpenAPI Initiative (OAI). È un documento JSON o YAML che descrive risorse e path, parametri di input e output, schemi dei dati di richiesta e risposta, codici di stato e autenticazione.
I vantaggi elencati sono sei: documentazione interattiva; generazione automatica di SDK/client in vari linguaggi; creazione di mock server per test rapidi; integrazione in tool come Swagger UI o Postman; comunicazione più chiara tra sviluppatori backend e frontend; possibilità di generare la documentazione a partire dal codice. Swagger UI, in particolare, permette di visualizzare e interagire con le API tramite un'interfaccia web: elenco delle risorse disponibili, possibilità di provare le richieste direttamente dal browser, esempi di input e output generati automaticamente.
La struttura del documento segue cinque blocchi: versione della specifica, informazioni sull'API, definizione dei server, sicurezza, definizione dei path.
openapi: 3.0.0
info:
title: SmartCity API
description: API per gestire smart city
version: 1.0.0
servers:
- url: https://api.smartcity.example.com/v1
security:
- bearerAuth: []
paths:
/cities:
get:
summary: Elenca tutte le città
tags: [Cities]
responses:
'200':
description: Lista delle città
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/City'
Sulla stessa risorsa si descrive poi il post, con il requestBody obbligatorio e le due risposte possibili — 201 per la città creata con successo e 400 per i dati non validi — e infine i components, dove vivono gli schemi di autenticazione e i modelli di dati riutilizzabili:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
City:
type: object
properties:
id:
type: string
example: cesena
name:
type: string
example: Cesena
population:
type: integer
example: 96500
sensors:
type: array
items:
$ref: '#/components/schemas/Sensor'
Sensor:
...
L'esercizio associato chiede di partire dal file exercises/exercise-01/openapi-film.yaml, definire l'API Filmoteca per gestire dei film e validare il file con Swagger Editor. Un film ha i campi id, title, director, year e genres (un array).
La sequenza dell'esercitazione non è casuale: prima si scrive la specifica, poi si implementa. Con OpenAPI il contratto dell'API è un artefatto formale che esiste prima del codice, può essere validato, può generare un mock server per il frontend e può generare i client. È l'equivalente, sul confine di rete, di quello che gli schemi Mongoose faranno sul confine della persistenza nel capitolo 7.
Express si presenta come "Fast, unopinionated, minimalist web framework for Node.js". Permette di creare web server e API REST in modo semplice e veloce, e aggiunge molte funzionalità utili per lo sviluppo web. Le caratteristiche principali sono cinque: semplicità (API intuitive e facili da usare), flessibilità (adatto a esigenze e progetti diversi), middleware (supporto per funzioni che gestiscono richieste e risposte), routing (sistema potente e flessibile), performance (ottimizzato per alte prestazioni).
Le slide dello stack MEAN aggiungono il quadro d'insieme: Express è un web application framework server-side minimale e flessibile che facilita l'uso di Node.js e l'implementazione di API REST, supporta diversi template engine, fornisce routing, middleware e un sistema modulare, e — dettaglio importante — il layer di funzionalità che fornisce non "oscura" le funzionalità di Node.js. È spesso la base per architetture più complesse.
Si installa con npm install express --save (o semplicemente npm install express) dopo aver creato il progetto con npm init. Le modifiche che ne conseguono sono tre: package.json guadagna la dipendenza, compare package-lock.json con l'albero delle dipendenze, e nasce la cartella node_modules con il codice dei moduli.
Esiste anche express-generator, uno strumento che crea velocemente una struttura dell'applicazione: si installa con npm install express-generator e si usa per esempio con express --view=pug myapp.
Visitando una rotta non definita — per esempio /asw — la risposta è Cannot GET /asw, perché Express utilizza una logica di exact-match, in ordine di definizione delle rotte. Per gestire il caso si definisce in fondo una rotta che cattura tutto:
app.get('/*thepath', (req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.status(404);
res.send('Page not found ' + req.params.thepath);
});
Le due parole "in ordine di definizione" sono la cosa più importante di questa sezione: la posizione delle rotte nel file è semantica. Una rotta catch-all messa all'inizio intercetta tutto e rende irraggiungibile ogni rotta successiva.
Dato un file colors.json, per restituirne il contenuto sulla rotta /colors si importano i dati con require e si definisce l'handler. Ci sono due alternative:
const data = require('./colors.json');
// alternativa 1: header esplicito + send
res.header("Content-Type", 'application/json');
res.send(JSON.stringify(data));
// alternativa 2: json fa entrambe le cose
res.json(data);
Con res.json non è necessario specificare il tipo di contenuto: il metodo imposta l'header e serializza da solo. È il motivo per cui nelle API REST si usa praticamente sempre la seconda forma.
Per restituire il file contacts.html alla rotta /contacts si può usare res.sendFile, che però richiede un path assoluto:
const path = require('path');
app.get('/contacts', (req, res) => {
res.sendFile(path.join(__dirname, 'contacts.html'));
});
La domanda che l'esercitazione pone subito dopo è la più utile: e quando HTML e CSS sono separati, e la pagina include più file — script, fogli di stile, immagini? Serve una rotta per ogni file statico? No: si definisce una cartella public con dentro tutti i file statici e si servono in blocco con un middleware:
app.use(express.static('public'));
Ci sono due modi di passare un id, e a ciascuno corrisponde un accesso diverso:
| Forma dell'URL | Rotta da definire | Come si legge |
|---|---|---|
/user/12345 | /user/:id | req.params.id |
/user?id=12345 | /user | req.query.id |
app.get('/sayhello/:name', (req, res) => {
res.send("Hello " + req.params.name + "!");
});
La scelta tra i due non è stilistica ma di design, e risponde alle regole della sezione 4: il path parameter identifica una risorsa (fa parte della gerarchia), la query string filtra o impagina una collezione. /films/12 è un film; /films?director=Nolan è un sottoinsieme della collezione dei film.
Il routing gestisce le richieste verso path specifici e permette di organizzare il codice in modo modulare, separando le rotte. Il costrutto è express.Router(), che si comporta come una mini-applicazione esportabile:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('Lista di risorse');
});
router.post('/', (req, res) => {
res.send('Crea nuova risorsa');
});
router.get('/:id', (req, res) => {
res.send('Dettagli risorsa ' + req.params.id);
});
module.exports = router;
Le funzioni middleware sono blocchi che elaborano richieste e risposte. Possono fare quattro cose: eseguire codice, modificare req e res, terminare la richiesta, oppure passare il controllo al middleware successivo chiamando next().
function logger(req, res, next) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // passa al middleware/rotta successiva
}
app.use(logger);
Express ne fornisce alcuni già pronti: express.json() per il parsing del JSON nel body delle richieste, express.urlencoded() per i dati di form (x-www-form-urlencoded), express.static() per servire file statici — quest'ultimo lo abbiamo già usato senza chiamarlo per nome.
Cross-Origin Resource Sharing è una sicurezza del browser che impedisce richieste a origini diverse. Per abilitare l'accesso da frontend esterni si usa il pacchetto cors:
const cors = require('cors');
app.use(cors());
Questo è il middleware che serve nel momento in cui, dal capitolo 10 in poi, il frontend Vue gira su una porta diversa da quella del backend Express: due porte sono due origini, e senza CORS il browser blocca la chiamata.
express-validator è un insieme di middleware per la validazione e la sanificazione dei dati nelle richieste HTTP:
const { body, validationResult } = require('express-validator');
app.post('/user', body('email').isEmail(), (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.send('Utente valido!');
}
);
Notate la forma: il validatore è passato come middleware nella definizione della rotta, prima dell'handler, e l'handler si limita a leggere il risultato. E notate il codice di stato: 400 Bad Request, perché l'errore è del client.
Due strade. JWT (pacchetto jsonwebtoken) per l'autenticazione stateless: il token viaggia con ogni richiesta e il server non conserva nulla — coerente con la natura stateless di HTTP vista nel capitolo 3. Cookie e sessioni (express-session) per gestire sessioni utente, cioè per aggiungere stato:
const session = require('express-session');
app.use(session({
secret: 'segreto',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // true se HTTPS
}));
Il commento // true se HTTPS non è un dettaglio di configurazione: cookie.secure a true impone al browser di inviare il cookie di sessione solo su connessione cifrata. In sviluppo su http://localhost va lasciato false, altrimenti la sessione non funziona; in produzione lasciarlo false significa spedire l'identificativo di sessione in chiaro. Sapere perché quel valore cambia tra sviluppo e produzione è una buona risposta da orale.
Arrivati a questo punto un file unico non regge più, e l'esercitazione impone la buona prassi: separare il codice in moduli. Tre cartelle, tre responsabilità:
| Cartella | Contenuto |
|---|---|
routes | un file js che gestisce tutte le rotte dell'applicazione |
controllers | un file js che gestisce la logica di ogni chiamata |
models | un file js che definisce la struttura dei dati (per esempio un modello per i film) |
Questo approccio permette di separare la gestione delle rotte dalla logica applicativa. Il flusso delle chiamate è a tre livelli:
// index.js utilizza i router definiti in routes
const myRouter = require('./src/routes/myRouter');
app.use('/example', myRouter);
// i router utilizzano la logica definita nei controllers
const myController = require('../controllers/myController');
router.route('/').get(myController.helloWorld);
// i controllers implementano la logica e usano i models per i dati
const myModel = require('../models/myModel');
exports.helloWorld = (req, res) => { res.send('Hello World!'); }
exports.getAll = (req, res) => { res.json(myModel.getAll()); }
Riconoscete lo schema: è MVC lato server, quello del capitolo 3, con il Controller come entry-point della richiesta HTTP e il Model che incapsula dati e business logic. Manca la View perché in una API REST la rappresentazione è JSON — che è precisamente la situazione descritta nella sezione "MVC nei moderni web framework": bundle SPA da una parte, API Controller che rispondono JSON dall'altra.
Il filo rosso delle esercitazioni è un archivio di film, che ricompare in forme sempre più complete. La prima versione parte dalla specifica OpenAPI già scritta (exercises/exercise-02/spec.yaml) e chiede di implementare un server Express con cinque rotte:
| Rotta | Effetto |
|---|---|
GET /films | restituisce la lista di tutti i film |
GET /films/:id | restituisce i dettagli di un film specifico, dato l'id |
POST /films | aggiunge un nuovo film (con dati in formato JSON) |
PUT /films/:id | aggiorna i dati di un film esistente |
DELETE /films/:id | elimina un film dall'archivio |
Ogni film è un oggetto JSON con id, title, director, year e genres; i film sono memorizzati in un array in memoria come database fittizio — al riavvio del server le modifiche vanno perse, e sarà il capitolo 7 a rimediare con MongoDB. Vanno restituiti i corretti codici di stato HTTP e le API vanno testate con Postman o Swagger UI, generando la documentazione dall'OpenAPI.
/films tramite query string: ?director=Nolan, ?year=2010, ?genre=Sci-Fi, combinabili tra loro (?director=Nolan&genre=Sci-Fi).year compreso tra 1888 — considerato l'anno del primo film — e l'anno corrente; genres con almeno un elemento; divieto di creare un film con un id già esistente.GET /films/stats che restituisce totalFilms, byDirector e byGenre come oggetti di conteggi.?sort=year per anno crescente, ?sort=-year per decrescente, ?sort=title alfabetico.director, year, genre, sort), regole di validazione (minimum, maximum, minItems, uniqueItems) e il nuovo endpoint /films/stats.GET /films/stats e GET /films/:id hanno la stessa forma. Poiché Express fa exact match in ordine di definizione, se /films/:id è dichiarata per prima catturerà anche /films/stats, trattando la stringa "stats" come un id. La rotta specifica va dichiarata prima di quella parametrica. È lo stesso principio della rotta catch-all che va messa per ultima.
La seconda versione dell'esercizio (esercitazione 03) rifà lo stesso servizio applicando le buone prassi: niente database per la persistenza, gestione dei dati con un array, il file movies.json con i film già presenti, ma il codice separato in routes, controllers e models.
Tre cose da avere pronte su questo capitolo. Uno: i quattro archetipi, con la differenza collection/store spiegata in termini di chi sceglie l'URI. Due: la matrice verbo × risorsa, sapendo motivare la casella vuota e sapendo dire quale status code accompagna ciascuna operazione (200, 201 dopo una creazione, 204 dopo una cancellazione, 400 per dati non validi, 404 per risorsa inesistente). Tre: che cos'è un middleware e cosa può fare, con l'ordine di registrazione come parte della semantica.
Document: simile a un'istanza di oggetto o a un record, la cui rappresentazione include valori e collegamenti ad altre risorse; è l'archetipo di base ed è il candidato naturale per la risorsa radice. Collection: repository di risorse gestito dal server, che decide se creare le risorse proposte dai client e ne sceglie gli URI. Store: repository di risorse gestito dal client, che inserisce, recupera ed elimina risorse su URI scelti da lui; non crea risorse né genera URI da solo. Controller: modella un concetto procedurale, cioè funzioni eseguibili con parametri e valori di ritorno, per azioni che non si mappano sui metodi CRUD standard; compare come ultimo segmento del percorso.
POST su un documento non è applicabile?Perché POST significa "aggiungi un elemento dentro questa risorsa", e ha senso solo verso una collezione, che è il contenitore incaricato di creare la nuova risorsa e di assegnarle un URI. Un documento come /cities/cesena è già una risorsa singola e identificata: non ha una collezione interna a cui aggiungere qualcosa. Le operazioni sensate su di esso sono GET (leggi), PUT (aggiorna) e DELETE (cancella). Se serve un'azione ulteriore, si modella come risorsa controller, per esempio POST /alerts/245743/resend.
Il path parameter identifica una risorsa e fa parte della gerarchia: /user/:id letto con req.params.id. La query string serve a filtrare collection o store e a impaginare i risultati: GET /users?role=admin, GET /users?pageSize=25&pageStartIndex=50, letti con req.query. La regola pratica: se togliendo quel pezzo di URL si ottiene ancora una risorsa sensata (la collezione), allora è una query; se si ottiene una cosa diversa, è parte del percorso.
GET /deleteUser?id=1234 e GET /deleteUser/1234: mettono un verbo CRUD nell'URI e usano GET per un'operazione che modifica lo stato, cosa che rompe la semantica del metodo. DELETE /deleteUser/1234: il metodo è corretto ma il verbo nell'URI è ridondante. POST /users/1234/delete: maschera da risorsa controller un'operazione per cui esiste già il metodo standard. La forma corretta è una sola: DELETE /users/1234.
È uno standard per descrivere le API REST in modo strutturato e leggibile da una macchina, nato come Swagger Specification e oggi gestito dalla OpenAPI Initiative. Il documento, in JSON o YAML, descrive risorse e path, parametri di input e output, schemi di richieste e risposte, codici di stato e autenticazione. I vantaggi sono la documentazione interattiva, la generazione automatica di SDK e client, la creazione di mock server per test rapidi, l'integrazione con Swagger UI e Postman, una comunicazione più chiara tra backend e frontend, e la possibilità di generare la documentazione dal codice.
È una funzione che elabora richieste e risposte lungo una catena, con firma (req, res, next). Può eseguire codice, modificare req e res, terminare la richiesta inviando una risposta, oppure passare il controllo al middleware successivo chiamando next(). Un middleware che non chiama next() e non risponde blocca la richiesta. Express fornisce middleware built-in: express.json() per il body JSON, express.urlencoded() per i dati di form, express.static() per i file statici.
Perché Express usa una logica di exact match, in ordine di definizione: la prima rotta che corrisponde vince. Ne derivano due conseguenze pratiche. La rotta catch-all per il 404 (app.get('/*thepath', …)) va messa in fondo, altrimenti intercetta tutto. E una rotta specifica come /films/stats va dichiarata prima della rotta parametrica /films/:id, altrimenti la seconda cattura anche la prima trattando "stats" come un id.
res.send e res.json?Con res.send bisogna impostare esplicitamente l'header e serializzare: res.header("Content-Type", 'application/json') seguito da res.send(JSON.stringify(data)). Con res.json(data) il metodo fa entrambe le cose da solo: imposta il tipo di contenuto e serializza l'oggetto. Nelle API REST si usa la seconda forma, perché elimina due possibili errori (dimenticare l'header o serializzare male).
Cross-Origin Resource Sharing è un meccanismo di sicurezza del browser che impedisce a una pagina di fare richieste verso origini diverse dalla propria. Ci si scontra con esso appena il frontend e il backend girano su porte diverse — la situazione normale quando il progetto Vue è servito dal dev server di Vite e le API stanno su Express: due porte sono due origini. Si abilita l'accesso con il pacchetto cors e la riga app.use(cors()).
In tre cartelle: routes con il file che gestisce tutte le rotte, controllers con la logica di ogni chiamata, models con la definizione della struttura dei dati. Il flusso è a tre livelli: index.js monta i router con app.use('/example', myRouter), i router delegano ai controller con router.route('/').get(myController.helloWorld), i controller usano i model per i dati. Corrisponde al pattern MVC lato server, con il Controller come entry-point della richiesta HTTP e il Model che incapsula dati e business logic; la View manca perché la rappresentazione è JSON, esattamente lo scenario "SPA più API Controller" descritto nel capitolo 3.
Creazione riuscita: 201 Created, tipicamente in risposta a un POST su una collezione. Cancellazione riuscita: 204 No Content, operazione avvenuta senza restituire corpo. Dati non validi: 400 Bad Request, classe 4xx perché la colpa è del client — è il codice che express-validator usa nell'esempio. Risorsa inesistente: 404 Not Found. Da evitare la scorciatoia di rispondere 500 a un input sbagliato: la classe 5xx indica un errore del server, ed è anche il sintomo tipico dell'overload.