Il middleware è una funzione della pipeline della richiesta che ispeziona o modifica richieste e risposte prima della logica della route. Due accezioni condividono la parola: quella più antica, aziendale (broker di messaggi e bus di integrazione tra applicazioni), e quella dei framework web di cui si occupa questa voce, cioè funzioni dentro una sola applicazione, attraversate in ordine da ogni richiesta. Express enuncia il modello senza giri di parole: un’app “è essenzialmente una serie di chiamate a funzioni middleware” – e l’ordine di quelle chiamate è, letteralmente, il programma.
Punti chiave
| Domanda | Risposta |
|---|---|
| Il contratto | Ispezionare/modificare → poi rispondere (cortocircuito) oppure chiamare next() |
| La forma | Una cipolla: le richieste scendono nella pila, le risposte risalgono |
| La legge | Ordine di registrazione = ordine di esecuzione – quasi tutti i bug di middleware sono bug di ordine |
| La pila canonica | Header → CORS → parsing → logging → autenticazione → autorizzazione → limiti → route → 404 → errori |
| Rispetto al gateway | Il middleware gira dentro una sola app; un gateway sta davanti a molte |
La pila, in ordine
// JavaScript / Node.js — Express + Parse Server
// Middleware: functions the request flows through, in registration order
const app = express();
app.use(helmet()); // 1 · security headers
app.use(cors(corsOptions)); // 2 · CORS before anything that fails
app.use(express.json({ limit: '1mb' })); // 3 · body parsing, bounded
// Parse Server IS middleware — a whole backend mounted into the stack
app.use('/parse', new ParseServer(config).app);
app.use(notFoundHandler); // 404 — after all routes
app.use(errorHandler); // error handler LAST (4 args) // Flutter / Dart — Back4app Flutter SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
final response =
await QueryBuilder<ParseObject>(ParseObject('Post')).query();
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. // iOS / Swift — Back4app Swift SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
let posts = try await Post.query().find()
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. // Android / Kotlin — Back4app Android SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
val posts = ParseQuery.getQuery<ParseObject>("Post").find()
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. Ogni posizione ha la sua ragione: gli header di sicurezza per primi (devono comparire su ogni risposta, errori compresi); CORS prima di tutto ciò che può fallire (altrimenti i browser mascherano l’errore vero); il parsing del body, limitato, prima delle route (altrimenti req.body vale undefined); l’autenticazione prima dell’autorizzazione (permessi verificati contro nessuno sono permessi concessi a chiunque); il rate limiting prima del lavoro costoso (un limitatore messo dopo la query sul database non protegge niente); il 404 dopo tutte le route; il gestore di errori proprio per ultimo.
La cipolla, disegnata correttamente
La metà che quasi tutte le spiegazioni omettono: la pipeline funziona nei due sensi. La documentazione di Django la disegna come una cipolla – ogni middleware è un livello attorno alla vista, al centro – e il codice che sta dopo la chiamata a next() (o dopo get_response) gira sul percorso di ritorno della risposta, in ordine inverso. È lì che si misurano i tempi di risposta, che gli header vengono apposti e che il logging registra quello che è davvero accaduto. Un livello che fa cortocircuito non salta solo l’handler; salta entrambe le metà di ogni livello interno – che è esattamente la garanzia per cui esiste una barriera di autenticazione.
Bug di ordine che arrivano in produzione
Il consiglio generico è “l’ordine conta”; i bug concreti sono più istruttivi. Autorizzazione prima dell’autenticazione: il controllo dei permessi gira contro un principal anonimo – 401/403 intermittenti, nessuna eccezione da nessuna parte, ore di debug. Body parser dopo le route: ogni handler vede req.body === undefined e dà la colpa al client. Auth prima di CORS: il browser blocca la risposta 401 stessa perché priva di header CORS, così il frontend vede un errore di rete invece di quello vero. File statici prima dell’auth: file privati serviti di buon grado a chi non è autenticato. Gestore di errori non per ultimo: gli errori lanciati dopo la sua posizione nella pila non lo raggiungono mai. Ognuno di questi bug supera uno smoke test sul percorso felice – i bug di ordine sono proprio quelli che arrivano in produzione.
Cortocircuito: quando non chiamare next() è il punto
Il contratto ha due uscite legittime: passare il controllo, oppure chiudere il ciclo. Chiuderlo presto non è un fallimento del middleware, è metà del suo lavoro: il 401 della barriera di auth, il 429 del limitatore, l’hit di cache, il reindirizzamento, il preflight CORS risolto sul posto. La regola che tiene onesta ciascuna delle due uscite: farne sempre esattamente una – rispondere, oppure next(). Non farne nessuna lascia la richiesta sospesa per sempre; farle entrambe genera errori headers-already-sent che confondono tutti a valle.
La stessa idea in ogni framework
| Framework | Il middleware è | Passare il controllo | Al ritorno |
|---|---|---|---|
| Express | (req, res, next) => {} | next() | Il codice dopo next() (con cautela) |
| Django | Un callable che avvolge get_response | get_response(request) | Il codice dopo la chiamata – la cipolla |
| Rack / Rails | Un oggetto con call(env) | @app.call(env) | Dopo il ritorno della chiamata |
| Koa / Hono | async (ctx, next) => {} | await next() | Dopo l’await – in modo nativo |
Un modello, quattro accenti. Il percorso degli errori ha la propria convenzione in ogni framework: in Express la firma a quattro argomenti (err, req, res, next) è il meccanismo di registrazione, ed è per questo che cancellare un parametro “non usato” trasforma in silenzio il gestore di errori in un middleware ordinario che non scatta mai.
Middleware vs. gateway vs. hook
| Middleware | Gateway API | Hook sui dati | |
|---|---|---|---|
| Gira | Dentro il processo di una sola app | Davanti a molte app | Attorno alle operazioni sui dati |
| Granularità | Per richiesta | Per richiesta, tra servizi | Per save/delete/find |
| Governa | La pipeline di questa app | Routing, auth al bordo, limiti globali | Validazione, reazioni sui dati |
| Si configura con | Codice, in ordine | Configurazione dell’infrastruttura | Una registrazione per classe |
Tre livelli di intercettazione, un unico annidamento: il gateway sta davanti alla flotta, il middleware fa passare ogni app dalla propria batteria di controlli, e gli hook scattano dove le richieste diventano dati. Una preoccupazione appartiene al livello più esterno in grado di deciderla – limiti globali al gateway, auth di sessione nel middleware, “questa scrittura è valida?” nell’hook.
Casi d’uso tipici
- Autenticazione e gestione delle sessioni – stabilire l’identità una volta sola, presto, per tutto quello che segue.
- Igiene trasversale – CORS, header di sicurezza, compressione, ID di richiesta.
- Disciplina degli input – parsing del body con limiti di dimensione, controllo del content-type, validazione.
- Osservabilità – logging e misurazione dei tempi attorno a tutta la pipeline, via il percorso di ritorno della cipolla.
- Protezione del traffico – rate limiting e barriere anti-abuso che fanno cortocircuito prima che il costo venga sostenuto.
In quale livello sta questa cosa? Matrice decisionale
| Preoccupazione | Livello |
|---|---|
| Si applica a tutte le app in esercizio | Gateway |
| Si applica a ogni richiesta di questa app | Middleware, posizionato con intenzione |
| Si applica a route precise | Middleware a livello di router |
| Si applica quando i dati vengono scritti o letti | Hook beforeSave / beforeFind |
| Operazioni di business su misura | Funzioni, non trucchetti di pipeline |
| Formattazione degli errori | Middleware di errore – per ultimo, quattro argomenti, senza eccezioni |
Limiti e trade-off
- L’ordine è invisibile finché non lo è più. La pila si legge dall’alto in basso ma fallisce in modi che puntano da ogni altra parte; la registrazione dei middleware va trattata come codice portante, da sottoporre a revisione.
- Ogni livello tassa ogni richiesta. Dieci middleware da 2 ms ciascuno sono 20 ms su ogni risposta; la pila si misura come si misurano le query.
- Lo stato globale è una trappola. Il middleware gira in concorrenza su più richieste; qualsiasi stato mutabile condiviso diventa una race condition – i dati propri della richiesta vanno attaccati all’oggetto richiesta, e in nessun altro posto.
- Le pipeline nascondono il flusso di controllo. Un livello che fa cortocircuito tre posizioni più sotto può spiegare perché una route “non gira mai”; la mossa di debug è sempre la stessa: stampare la pila, in ordine.
- Non tutto è una questione di pipeline. La logica di business infilata di nascosto in un middleware vincola a sé ogni route; la pipeline serve alle preoccupazioni trasversali, non a quelle centrali.
Il middleware su Back4app
Back4app è una piattaforma open-source di Backend as a Service (BaaS) che combina database gestito, API REST e GraphQL generate automaticamente, autenticazione, archiviazione di file e funzioni serverless con Cloud Code. Qui la relazione è di una letteralità rara: il server di Back4app è esso stesso un middleware Express – la scheda JavaScript lo mostra montato con app.use('/parse', …) in una pila standard – e la piattaforma esegue la batteria canonica per ogni richiesta: header di sicurezza, CORS, parsing limitato, verifica delle chiavi, autenticazione di sessione e rate limiting, nell’ordine giusto, mantenuti come infrastruttura. La logica personalizzata per singola richiesta va poi dove indica la matrice decisionale, invece che in codice di pipeline fatto a mano: i trigger beforeSave/beforeFind per le regole vicine ai dati, le Cloud Functions per le operazioni – ciascuno con il contesto utente della richiesta già attaccato, che è gran parte di ciò che un middleware su misura ha sempre voluto sapere.
Domande frequenti
Che cos'è il middleware, in parole semplici?
Una funzione che sta sul percorso tra una richiesta in arrivo e la logica della route, e che elabora ogni richiesta al passaggio – come i controlli di sicurezza di un aeroporto prima del gate. Ciascuna ispeziona o modifica la richiesta, poi la lascia passare oppure la blocca di netto.
Quali sono gli esempi comuni di middleware?
La pila abituale: header di sicurezza, CORS, parsing del body con limiti di dimensione, logging, autenticazione, autorizzazione, rate limiting, servizio dei file statici e – proprio alla fine – gli handler 404 e di errore. Quasi tutto ciò che è trasversale in un'applicazione web è un middleware.
Come funziona la catena di middleware?
Ogni funzione o chiude il ciclo inviando una risposta, oppure chiama next() per passare il controllo alla successiva; il framework percorre la pila nell'ordine di registrazione finché qualcosa non risponde. Il bug classico: non rispondere e non chiamare next() – la richiesta resta sospesa per sempre.
L'ordine dei middleware conta?
È la prima fonte di bug. L'autorizzazione prima dell'autenticazione verifica permessi contro nessuno; un body parser dopo le route lascia req.body a undefined; l'auth prima di CORS spinge i browser a mascherare l'errore vero; un gestore di errori messo in un punto che non sia l'ultimo non intercetta niente. L'ordine è il programma.
Che cos'è il middleware di gestione degli errori?
Un middleware verso cui il framework indirizza gli errori invece della catena normale – in Express si riconosce dalla firma a quattro argomenti (err, req, res, next) e va registrato per ultimo. Gli errori lanciati e le chiamate next(err) saltano tutto il resto e atterrano lì, ed è per questo che la sua posizione non è negoziabile.
Qual è la differenza tra un middleware e un handler di route?
L'intenzione e la posizione. Il middleware gestisce preoccupazioni trasversali per molte route e di solito passa il controllo; l'handler di route è la destinazione che produce la risposta. Nella maggior parte dei framework sono funzioni strutturalmente identiche: la pipeline si limita a terminare su una di esse.
Qual è la differenza tra un middleware e un gateway API?
L'ambito. Il middleware gira dentro il processo di una sola applicazione, a ogni richiesta; un gateway è infrastruttura posta davanti a molte applicazioni, che gestisce routing, auth e rate limiting tra servizi. Un gateway è il middleware di tutta l'architettura – e i due si combinano invece di escludersi.
Quando un middleware NON deve chiamare next()?
Quando ha gestito del tutto la richiesta: un rifiuto di auth che restituisce 401, un rate limiter che restituisce 429, un hit di cache, un reindirizzamento, una risposta a un preflight CORS. Il cortocircuito è la feature – la garanzia che nulla oltre la barriera giri per le richieste che non l'hanno superata.