Il routing in Express associa un metodo HTTP e un path a una funzione handler: app.get('/users', handler) risponde alle richieste GET su /users, e app.post(), app.put() e app.delete() fanno lo stesso per gli altri metodi. Un segmento che inizia con i due punti, come in /users/:id, è un parametro di route il cui valore finisce in req.params. Per tenere in ordine un'app che cresce, metti le route correlate in un express.Router() e montalo sotto un prefisso con app.use('/api', router).
GET /users/2 risponde {"id":2,"name":"Linus"}. I parametri di route sono sempre stringhe, per questo l'handler confronta con Number(req.params.id). Invia POST /users con il body {"name": "Grace"} per ottenere 201, poi DELETE /users/1 e GET /users per vedere la lista cambiare.
express.Router() con un prefisso
Un router è una mini app con le sue route e i suoi middleware. I suoi path sono relativi: una route scritta come /:id dentro un router montato su /api/books risponde a /api/books/:id. In un progetto vero ogni router sta nel suo file e finisce con module.exports = router.
GET /api/books/1 restituisce {"id":1,"title":"Dune"}. router.route('/:id') scrive il path una volta e concatena un handler per metodo; invia PUT /api/books/1 e ottieni {"updated":"1"}. Cambia il path di montaggio in /v2/books e lo stesso router risponde lì.
Ordine delle route: vince la prima che corrisponde
Express prova le route nell'ordine in cui le registri e si ferma alla prima che invia una risposta. Una route con parametro cattura qualsiasi cosa in quella posizione, quindi un path fisso registrato dopo non viene mai raggiunto:
GET /users/new risponde {"route":"/users/:id","id":"new"}: la route con parametro è stata registrata prima e l'ha presa lei. Sposta la route /users/new sopra /users/:id ed esegui di nuovo per ottenere {"route":"/users/new"}. Poi invia GET /books per arrivare all'handler 404 in fondo.
L'handler 404
Express risponde a una richiesta senza route corrispondente con la sua pagina HTML, Cannot GET /books. Per un'API di solito vuoi JSON, quindi aggiungi un app.use() senza path come ultimissima route, come nel blocco qui sopra. Viene eseguito solo quando tutte le route precedenti hanno lasciato passare la richiesta. Gli errori lanciati dagli handler sono un caso diverso, con il loro handler a quattro argomenti, trattato in gestione degli errori in Express.
I path delle route in Express 5
Express 5 ha cambiato la sintassi dei path, e la maggior parte degli snippet che trovi online è scritta per Express 4:
| Pattern | Express 4 | Express 5 |
|---|---|---|
| Qualsiasi path | app.get('*', fn) | app.get('/*splat', fn) |
| Parametro opzionale | /users/:id? | /users{/:id} |
| Pattern su un parametro | /users/:id(\\d+) | Non supportato; controlla il valore nell'handler |
Un * da solo o un ? dopo un parametro lancia un TypeError quando la route viene registrata, quindi l'app non parte.
Errori comuni
Registrare per primo l'handler 404. app.use() senza path corrisponde a tutto, quindi ogni richiesta riceve il 404. Tienilo in fondo.
Dimenticare che il prefisso viene tolto. Dentro un router montato su /api, req.path non include /api. Usa req.originalUrl quando ti serve il path completo.
Confrontare un parametro con un numero. req.params.id === 1 è sempre falso perché req.params.id è la stringa '1'.
Domande frequenti
Che differenza c'è tra app.use e app.get?
app.get('/users', fn) esegue fn solo per le richieste GET il cui path è esattamente /users. app.use('/users', fn) viene eseguito per ogni metodo e per qualsiasi path che inizia con /users, per questo si usa per montare router e middleware.
Come rendo le route di Express case sensitive?
Di default le route ignorano maiuscole e minuscole, quindi /Users corrisponde a /users. Disattiva questo comportamento con app.set('case sensitive routing', true), oppure per un singolo router con express.Router({ caseSensitive: true }).
Perché req.params è vuoto dentro un router annidato?
Un router vede solo i parametri dei propri path. Se è montato su /users/:userId/posts, crealo con express.Router({ mergeParams: true }) così req.params.userId del path padre è disponibile.
Conviene mettere tutte le route in un solo file?
Non quando sono più di una manciata. Crea un router per risorsa (routes/users.js, routes/orders.js), esportalo con module.exports = router e monta ciascuno nel file principale con app.use().