First steps with Express

All’avvio di un progetto Node.js che utilizza Express, il primo passo consiste nell’importare il pacchetto tramite:

import express from 'express';

Da questa importazione si istanzia l’oggetto applicazione con const app = express();. Quest’oggetto, convenzionalmente chiamato app, fungerà da contenitore di tutte le funzionalità del server: configurazione, middleware, definizione delle rotte e avvio del processo d’ascolto.

Per servire contenuti occorre definire almeno una rotta. Nel caso più semplice, si crea una rotta GET per il percorso radice ('/') utilizzando il metodo app.get. Il gestore riceve due parametri: un oggetto Request (req) che incapsula la richiesta HTTP in arrivo e un oggetto Response (res) attraverso cui si costruisce la risposta. Un esempio minimale restituisce la stringa Hello World!:

app.get('/', (req, res) => 
	res.send('Hello World!')
);

Questo pattern si ripete per ogni combinazione di metodo (GET, POST, PUT, …) e percorso che l’applicazione deve supportare.

Dopo aver configurato le rotte, si avvia il server con app.listen(3000, callback). Il primo argomento indica la porta su cui il processo rimarrà in ascolto (nell’esempio, 3000); il secondo, opzionale, è una funzione che viene eseguita quando il binding sulla porta va a buon fine. Inserirvi un semplice console.log('Server ready') è una buona pratica di debug e conferma che l’applicazione è pronta a ricevere richieste.

Routing

Nel framework Express, il concetto di routing stabilisce come un server deve reagire a ciascuna richiesta HTTP. La forma generale di una rotta è app.method(path, handler);, dove ogni elemento ha un ruolo preciso e non intercambiabile.

app rappresenta l’istanza di Express che governa l’applicazione. Su questa istanza definiamo tutte le rotte, creando così la mappa logica tra URL e logica di business.

method indica il verbo HTTP a cui la rotta deve rispondere. I casi d’uso più comuni sono get, post, put, delete, ma Express espone l’intero set di metodi previsti dallo standard. Se si desidera intercettare tutti i metodi con un’unica definizione, è sufficiente ricorrere a app.all(), che funge da “catch-all” per ogni tipo di richiesta.

path è il percorso dell’URL che il server deve confrontare con il campo path presente nel messaggio HTTP in arrivo. Il confronto è letterale (o, più precisamente, basato su espressioni di path interne a Express) e determina se la rotta debba o meno essere eseguita.

handler è la callback che Express invoca quando la rotta trova corrispondenza. Riceve tipicamente due parametri - req e res - che modellano rispettivamente la richiesta del client e la risposta che il server dovrà inviare. Tutta la logica associata alla rotta (validazione, accesso al database, composizione della risposta) risiede in questa callback.

Un esempio minimalista ma didattico è il seguente:

app.get('/', (req, res) => {
	res.send('Hello World!');
});

Extending express with “Middlewares”

Quando in Express si parla di middleware ci si riferisce a funzioni che vengono eseguite nel ciclo di vita di ogni richiesta HTTP prima che la risposta venga inviata al client. In altre parole, il middleware è un meccanismo di estensione che consente di aggiungere logica trasversale (ad esempio autenticazione, logging, validazione) senza doverla ripetere in ogni handler di rotta.

Un middleware è dichiarato come function (req, res, next). I primi due parametri sono gli oggetti della richiesta e della risposta, gli stessi che si usano in un normale handler. Il terzo parametro, next, è un puntatore alla prossima funzione nella catena: invocarlo è essenziale per non interrompere il flusso. Finché non si chiama next(), la richiesta resta bloccata nel middleware corrente; ciò consente di eseguire operazioni asincrone o di terminare la risposta in anticipo (ad esempio restituendo un errore).

Se si desidera applicare un middleware solo a una rotta specifica, lo si inserisce direttamente tra il percorso e la callback finale, ad esempio: