L'aplicació d'Escena Viva té ara una estructura sòlida però una sola ruta. En aquesta lliçó l'omplim: recuperem els quatre endpoints de l'API del mòdul 4 (/salut, /esdeveniments, /esdeveniments/:id, /esdeveniments/:id/sessions), hi afegim les sessions i les comandes, i els organitzem en routers modulars amb controladors separats. Pel camí compararem cada mecanisme d'Express amb l'enrutador.js que vas escriure a mà, perquè la millor manera d'entendre app.get('/esdeveniments/:id') és recordar què costava fer-ho sense framework.
Contingut
- Mètodes de ruta i anatomia d'un gestor
- Paràmetres de ruta i
req.params - Paràmetres opcionals, comodins i expressions regulars
- Les altres dades de la petició:
query,body,headers,ip express.Router(): routers modulars- Reorganitzar l'API d'Escena Viva
- Controladors separats
router.route()irouter.param()- L'ordre importa
- Els mètodes de
res
- Mètodes de ruta i anatomia d'un gestor
Express exposa un mètode per cada verb HTTP, més all per a tots:
aplicacio.get('/esdeveniments', gestor); // llegir una colleccio
aplicacio.post('/comandes', gestor); // crear un recurs
aplicacio.put('/sessions/:id', gestor); // reemplacar del tot
aplicacio.patch('/sessions/:id', gestor); // modificar parcialment
aplicacio.delete('/comandes/:id', gestor); // eliminar
aplicacio.all('/api/*resta', gestor); // qualsevol metodeCadascun registra una entrada a la pila interna d'Express: un patró, un mètode i una o diverses funcions. És literalment el que feia el teu enrutador.registrar(metode, patro, gestor), amb la diferència que Express admet diverses funcions per ruta: a aplicacio.post('/comandes', validarCos, comprovarAforament, crearComanda) les tres s'executen en ordre i cadascuna decideix si continua. Això ja és middleware, i és el tema de 06-04.
Anatomia d'un gestor
// peticio: IncomingMessage estes; resposta: ServerResponse estes.
function gestor(peticio, resposta, seguent) {
// Tres sortides possibles:
// 1. Respondre: resposta.json(...) / resposta.send(...) — la cadena acaba.
// 2. Continuar: seguent() — passa al seguent gestor que encaixi.
// 3. Fallar: seguent(error) — salta al gestor d'errors (06-07).
resposta.json({ estat: 'ok' });
}Una regla d'or: cada camí d'execució ha d'acabar en exactament una d'aquestes tres coses. Si un if no respon ni crida seguent(), la petició es queda penjada fins que el client es rendeix. Ho diagnosticarem a 06-04.
- Paràmetres de ruta i
req.params
req.paramsaplicacio.get('/esdeveniments/:id', (peticio, resposta) => {
resposta.json({ idDemanat: peticio.params.id });
});
// curl -s localhost:3000/esdeveniments/evt-002 -> {"idDemanat":"evt-002"}Compara-ho amb el que feia el teu mòdul 4. compilarPatro('/esdeveniments/:id') recorria el patró, substituïa cada :nom per un grup de captura, desava els noms en un array i construïa la regex /^\/esdeveniments\/([^/]+)$/; després aparellar l'executava sobre la ruta i reconstruïa a mà l'objecte { id: 'evt-002' }. Express fa el mateix, amb diferències que importen:
| Aspecte | El teu compilarPatro |
Express (path-to-regexp) |
|---|---|---|
| Descodificació de la URL | Cridaves decodeURIComponent tu |
Automàtica: /esdeveniments/Sala%20B%C3%B3veda arriba descodificat |
| Caràcters especials i casos límit | Escapament a mà; risc de regex catastròfica | Escapament segur i patrons acotats i provats |
| Múltiples paràmetres | Funcionava, però el codi creixia | /esdeveniments/:idEsdeveniment/sessions/:idSessio sense cost |
Diversos paràmetres en una ruta:
aplicacio.get('/esdeveniments/:idEsdeveniment/sessions/:idSessio', (peticio, resposta) => {
const { idEsdeveniment, idSessio } = peticio.params;
resposta.json({ idEsdeveniment, idSessio });
});
// GET /esdeveniments/evt-001/sessions/ses-001-2
// -> {"idEsdeveniment":"evt-001","idSessio":"ses-001-2"}Important:
req.paramssempre conté cadenes. Si esperes un número, converteix-lo i valida'l. A 06-06 ho farem amb esquemes en comptes d'a mà.
- Paràmetres opcionals, comodins i expressions regulars
Aquí és on Express 5 va trencar amb Express 4; para atenció si et trobes codi antic.
| Necessitat | Express 4 | Express 5 |
|---|---|---|
| Paràmetre opcional | /esdeveniments/:id? |
/esdeveniments{/:id} |
| Comodí | /fitxers/* → req.params[0] |
Anònim no permès (llança error); amb nom, /fitxers/*ruta → req.params.ruta |
| Segment repetible | /:seccio+ |
/*seccio (captura la resta) |
// Parametre opcional: respon a /esdeveniments i a /esdeveniments/evt-001.
aplicacio.get('/esdeveniments{/:id}', (peticio, resposta) => {
const { id } = peticio.params;
resposta.json(id === undefined ? { tipus: 'colleccio' } : { tipus: 'element', id });
});
// Comodi amb nom: GET /descarregues/informes/2026/ocupacio.csv
// -> peticio.params.ruta === 'informes/2026/ocupacio.csv'
aplicacio.get('/descarregues/*ruta', (peticio, resposta, seguent) => {
try {
// El comodi es la porta classica al recorregut de directoris:
// reutilitzem resoldreDinsDe del modul 3.
resposta.sendFile(resoldreDinsDe(DIRECTORI_INFORMES, peticio.params.ruta));
} catch (error) {
seguent(error); // codi RUTA_NO_PERMESA -> 403
}
});Fixa't en la lliçó que es repeteix: el comodí és còmode i perillós. Que Express extregui la ruta per tu no vol dir que sigui segura; resoldreDinsDe continua sent imprescindible.
Per a casos que la sintaxi de segments no cobreix, s'admet una expressió regular com a patró; amb ella, les captures arriben per índex numèric: aplicacio.get(/^\/esdeveniments\/(evt-\d{3})$/, (p, r) => r.json({ id: p.params[0] })). Es pot fer, però fes-ho amb moderació: una regex a la ruta és difícil de llegir i amaga una validació on ningú no la busca. És millor acceptar :id i validar el format amb un esquema (06-06), perquè així l'error que retornes és explicatiu en comptes d'un 404 mut.
- Les altres dades de la petició
| Propietat | Contingut | Notes |
|---|---|---|
req.params |
Paràmetres de ruta | Sempre cadenes, ja descodificades |
req.query |
Query string interpretada | Només lectura a Express 5 |
req.body |
Cos interpretat | undefined sense express.json() (06-04) |
req.headers / req.get(n) |
Capçaleres en minúscules / una capçalera | req.get('Content-Type') ignora majúscules |
req.ip, req.protocol, req.secure |
IP i esquema del client | Depenen de trust proxy (06-02) |
req.method, req.path, req.originalUrl |
Mètode i ruta | originalUrl conserva la URL completa abans de muntatges |
// GET /api/esdeveniments?sala=Sala%20Boveda&pagina=2&ordenar=data
aplicacio.get('/api/esdeveniments', (peticio, resposta) => {
const { sala, pagina = '1', ordenar = 'data' } = peticio.query;
resposta.json({ sala, pagina: Number.parseInt(pagina, 10), ordenar });
});L'error clàssic en migrar a Express 5
A Express 4, req.query era una propietat normal i molt codi «normalitzava» la query reassignant-la:
// FUNCIONAVA a Express 4 i FALLA a Express 5.
peticio.query = normalitzar(peticio.query); // TypeError: Cannot set property query
// Correcte a Express 5: el resultat normalitzat va a una altra propietat.
peticio.consulta = normalitzar(peticio.query);A Express 5, req.query és un getter que interpreta la query string sota demanda i la memoritza; no té setter. Aquest és exactament el patró que farà servir el middleware validar() de 06-06 amb req.dadesValidades.
express.Router(): routers modulars
express.Router(): routers modularsUn Router és una mini-aplicació: té les seves rutes i el seu middleware, però no escolta en cap port. Es munta a l'aplicació (o en un altre router) sota un prefix.
const rutesEsdeveniments = express.Router();
rutesEsdeveniments.get('/', llistarEsdeveniments); // ruta relativa
rutesEsdeveniments.get('/:id', obtenirEsdeveniment); // ruta relativa
// El prefix es decideix en muntar, no en declarar.
aplicacio.use('/api/esdeveniments', rutesEsdeveniments);
// Resultat: GET /api/esdeveniments i GET /api/esdeveniments/:idEls avantatges enfront de declarar-ho tot a app.js: rutes relatives (el router no sap on està muntat, així que canviar /api/esdeveniments per /api/v2/esdeveniments és una línia), middleware acotat (rutesComandes.use(limitador) afecta només les comandes), fitxers petits (un router per recurs, en comptes d'un app.js interminable) i reutilització (el mateix router es pot muntar dues vegades sota prefixos diferents).
- Reorganitzar l'API d'Escena Viva
// src/rutes/index.js — punt unic de muntatge.
const express = require('express');
const { rutesEsdeveniments } = require('./esdeveniments.js');
const { rutesSessions } = require('./sessions.js');
const { rutesComandes } = require('./comandes.js');
function crearRutesApi() {
const api = express.Router();
api.get('/salut', (p, r) => r.json({ estat: 'ok', instant: new Date().toISOString() }));
api.use('/esdeveniments', rutesEsdeveniments);
api.use('/sessions', rutesSessions);
api.use('/comandes', rutesComandes);
return api;
}
module.exports = { crearRutesApi };// src/rutes/esdeveniments.js
const express = require('express');
const cc = require('../controladors/esdeveniments.js');
const rutesEsdeveniments = express.Router();
// S'executa una sola vegada per peticio que porti :id, abans del gestor.
rutesEsdeveniments.param('id', cc.carregarEsdeveniment);
rutesEsdeveniments.get('/', cc.llistarEsdeveniments);
rutesEsdeveniments.get('/:id', cc.obtenirEsdeveniment);
rutesEsdeveniments.get('/:id/sessions', cc.llistarSessionsDeLEsdeveniment);
module.exports = { rutesEsdeveniments };
// src/rutes/sessions.js i src/rutes/comandes.js segueixen el mateix patro.
rutesSessions.get('/:idSessio', obtenirSessio);
rutesSessions.get('/:idSessio/preus', obtenirPreusDeSessio);
rutesComandes.route('/').post(crearComanda);
rutesComandes.route('/:idComanda').get(obtenirComanda);
// I el muntatge a src/app.js:
aplicacio.use(express.json({ limit: configuracio.limitCos }));
aplicacio.use('/api', crearRutesApi());
aplicacio.use(express.static(configuracio.directoriPublic, { index: 'index.html' }));Tota l'API penja de /api i el front-end estàtic de public/ es serveix a l'arrel. Un sol use decideix el versionat de l'API sencera.
- Controladors separats
El controlador tradueix HTTP a domini. No conté regles de negoci: crida el domini i decideix el codi d'estat.
// src/controladors/esdeveniments.js
const { obtenirCataleg, obtenirEsdevenimentPerId } = require('../cataleg-dades.js');
/** GET /api/esdeveniments — llistat resumit del cataleg. */
async function llistarEsdeveniments(peticio, resposta) {
const esdeveniments = (await obtenirCataleg()).map((e) => ({
id: e.id, titol: e.titol, sala: e.sala, nombreSessions: e.nombreSessions,
aforamentTotal: e.aforamentTotal, entradesVenudes: e.entradesVenudes, exhaurit: e.exhaurit,
}));
resposta.json({ total: esdeveniments.length, esdeveniments });
}
// Middleware de router.param: carrega l'esdeveniment una sola vegada. Si no existeix,
// propaga l'error de domini (ESDEVENIMENT_NO_TROBAT -> 404).
async function carregarEsdeveniment(peticio, resposta, seguent, valorId) {
peticio.esdeveniment = await obtenirEsdevenimentPerId(valorId);
seguent();
}
/** GET /api/esdeveniments/:id — l'esdeveniment ja ve carregat per carregarEsdeveniment. */
const obtenirEsdeveniment = (peticio, resposta) => resposta.json(peticio.esdeveniment.toJSON());
/** GET /api/esdeveniments/:id/sessions */
function llistarSessionsDeLEsdeveniment(peticio, resposta) {
const { id, nombreSessions, sessions } = peticio.esdeveniment;
resposta.json({
idEsdeveniment: id, total: nombreSessions,
sessions: sessions.map((s) => ({
id: s.id, inici: s.inici, aforament: s.aforament, lliures: s.lliures,
exhaurida: s.exhaurida, preuCentims: s.preuCentims,
})),
});
}
module.exports = {
llistarEsdeveniments, carregarEsdeveniment, obtenirEsdeveniment, llistarSessionsDeLEsdeveniment,
};Tres coses que no veuràs en aquest fitxer, i és deliberat: no hi ha try/catch (a Express 5, si obtenirEsdevenimentPerId rebutja amb codi ESDEVENIMENT_NO_TROBAT, aquest rebuig arriba sol al gestor d'errors, que el traduirà a 404 amb la taula ESTAT_PER_CODI del mòdul 4; vegeu 06-07); no hi ha lectura de fitxers, que és de cataleg-dades.js; i no hi ha regles d'aforament, que són de domini/.
// src/controladors/comandes.js — POST /api/comandes
async function crearComanda(peticio, resposta) {
// req.body existeix gracies a express.json() (06-04);
// a 06-06 el substituirem per req.dadesValidades.
const comanda = await registrarComanda(peticio.body);
resposta.status(201).location(`/api/comandes/${comanda.id}`).json(comanda.toJSON());
}
router.route() i router.param()
router.route() i router.param()router.route()
Encadena diversos mètodes sobre la mateixa ruta sense repetir el patró: rutesSessions.route('/:idSessio').get(obtenirSessio).patch(actualitzarSessio).delete(cancellarSessio).
Avantatges: el patró s'escriu una vegada (menys risc que una còpia es desincronitzi) i queda visualment clar quines operacions admet el recurs. A més, Express genera automàticament el 405 Method Not Allowed amb la capçalera Allow correcta per als mètodes no registrats d'aquesta ruta — exactament el que tu vas programar a mà a enrutador.js.
router.param()
rutesEsdeveniments.param('id', carregarEsdeveniment) registra un middleware que s'executa una vegada per petició quan la ruta conté aquest paràmetre, abans de qualsevol gestor. Amb això, les tres rutes /, /:id i /:id/sessions comparteixen la càrrega de l'esdeveniment sense repetir ni una línia, i els controladors reben req.esdeveniment ja resolt.
| A favor | En contra |
|---|---|
| Elimina duplicació en rutes que comparteixen paràmetre i deixa els controladors molt nets | El controlador depèn d'una cosa que no es veu al seu fitxer |
| Punt únic per al 404 del recurs | S'executa també allà on potser no el necessites, i confon si dos routers fan servir el mateix nom amb semàntiques diferents |
Compte important: router.param() és local al router on es registra. Si muntes rutesSessions en un altre lloc, el seu param no s'hereta. I si el mateix nom (:id) significa coses diferents en dos routers, fes servir noms diferents (:idEsdeveniment, :idSessio) com hem fet aquí.
- L'ordre importa
Express recorre la seva pila en l'ordre de registre i es queda amb la primera entrada que encaixi en mètode i ruta. Això explica el 90 % dels «la meva ruta no s'executa».
// MALAMENT: el comodi es declara primer i es menja tota la resta.
rutesEsdeveniments.get('/*resta', servirPaginaGenerica);
rutesEsdeveniments.get('/destacats', llistarDestacats); // MAI no s'executa
rutesEsdeveniments.get('/:id', obtenirEsdeveniment); // MAI no s'executa
// BE: del mes especific al mes generic.
rutesEsdeveniments.get('/destacats', llistarDestacats); // literal
rutesEsdeveniments.get('/:id', obtenirEsdeveniment); // parametre
rutesEsdeveniments.get('/*resta', servirPaginaGenerica); // comodiAmb l'ordre dolent, una petició a /api/esdeveniments/destacats encaixa amb /*resta, que respon, i la cadena acaba aquí: les altres dues rutes són codi mort. La regla pràctica, en ordre de declaració: primer les rutes literals (/destacats, /salut), després les que porten paràmetre (/:id), tot seguit les de comodí (/*resta) i al final el 404. És el mateix problema que ja tenies al mòdul 4: el teu enrutador.js també recorria l'array de rutes en ordre. Express no ho resol per tu; n'hereta la mateixa regla.
La ruta 404 final
// Al final de src/app.js, despres de totes les rutes i estatics.
aplicacio.use((peticio, resposta, seguent) => {
const missatge = `No existeix la ruta ${peticio.method} ${peticio.originalUrl}`;
seguent(Object.assign(new Error(missatge), { codi: 'RUTA_NO_TROBADA' }));
});És un use sense ruta, així que encaixa amb tot el que hi hagi arribat sense ser atès. No respon: propaga un error, perquè el gestor central de 06-07 doni la resposta en el format { error: { codi, missatge, estat } } com qualsevol altre error.
- Els mètodes de
res
res| Mètode | Què fa | Què afegeix sobre node:http |
|---|---|---|
res.json(objecte) |
Serialitza i envia JSON | Fixa Content-Type i Content-Length, aplica json spaces, calcula ETag |
res.send(valor) |
Envia cadena, Buffer o objecte | Dedueix el Content-Type segons el tipus de l'argument |
res.status(codi) |
Fixa l'estat | Retorna res, així que s'encadena |
res.sendStatus(codi) |
Estat + cos amb el text de l'estat | res.sendStatus(204) respon en una línia |
res.location(url) |
Fixa la capçalera Location |
Codifica la URL correctament |
res.redirect([codi], url) |
Location + estat + cos |
Per defecte 302; fes servir 301 o 308 per a permanents |
res.sendFile(rutaAbsoluta) |
Envia un fitxer | ETag, Content-Type, rangs i streaming: és el teu estatics.js |
res.set(n, v) / res.type(t) |
Capçaleres i Content-Type |
set admet un objecte; type accepta dreceres ('json') |
res.end() |
Acaba la resposta | El mateix de node:http, sense extres |
// Exemples concrets a Escena Viva.
resposta.status(201).location(`/api/comandes/${comanda.id}`).json(comanda.toJSON());
resposta.sendStatus(204); // cancellacio acceptada, sense cos
// Redireccio permanent d'una URL antiga.
aplicacio.get('/esdeveniments/:id', (p, r) => r.redirect(301, `/api/esdeveniments/${p.params.id}`));
// Diverses capcaleres alhora.
resposta.set({ 'Cache-Control': 'public, max-age=60', 'X-Sala': 'Teatro Almendra' });Comprova l'equivalència amb el mòdul 4: curl -si localhost:3000/api/esdeveniments/evt-001 retorna 200 OK amb Content-Type: application/json; charset=utf-8, Content-Length i un ETag: W/"19c-...". Aquest ETag el calculava el teu estatics.js a mà amb un hash; res.json() ho fa per defecte per a qualsevol resposta.
Errors Comuns i Consells
- Declarar
/:idabans que/destacats. El paràmetre captura la paraula literal i la ruta específica queda morta. Del més específic al més genèric, sempre. - Fer servir
*sense nom a Express 5. Ja no està permès: l'aplicació falla en arrencar. Migra a*nom. - Reassignar
req.query.TypeErrora Express 5; desa el resultat normalitzat en una altra propietat. - Esperar que
req.bodyexisteixi senseexpress.json(). Seràundefinedi obtindràs unTypeError: la fallada més comuna en escriure el primerPOST. - Ficar lògica de negoci al controlador. Si calcula ocupacions o valida aforaments, aquesta lògica no es pot reutilitzar des dels informes ni provar sense HTTP.
- Oblidar que
router.param()és local al router: no s'hereta en muntar-lo en un altre lloc. - Confiar en un
:idsense validar. Arriba com a cadena i pot ser qualsevol cosa. Un 404 mut no ajuda el client; a 06-06 retornarem un 400 explicatiu. - Consell: en desenvolupament, imprimeix la taula de rutes en arrencar recorrent
aplicacio.router.stack. Veure l'ordre real de registre cura molts misteris.
Exercicis
Exercici 1: filtratge i paginació per query
Amplia GET /api/esdeveniments perquè admeti ?sala=, ?exhaurit=true|false, ?pagina= i ?perPagina= (per defecte 1 i 10, màxim 50). Retorna { pagina, perPagina, total, totalPagines, esdeveniments }. No reassignis req.query. Amb les dades de llavor, ?perPagina=2 ha de retornar 2 pàgines per als 3 esdeveniments.
Exercici 2: router de sessions amb route() i param()
Crea src/rutes/sessions.js amb un router.param('idSessio', carregarSessio) que cerqui la sessió recorrent el catàleg i la deixi a req.sessio (propagant SESSIO_NO_TROBADA si no existeix), i exposa amb router.route('/:idSessio') un GET que retorni el detall. Comprova que PUT /api/sessions/ses-001-1 retorna 405 amb la capçalera Allow.
Exercici 3: l'ordre que trenca
Escriu una aplicació amb tres rutes mal ordenades (/*resta, /destacats, /:id), demostra amb curl que només s'executa la primera, reordena-les i torna a provar. Afegeix-hi també un endpoint /depuracio/rutes que retorni la llista de rutes registrades en el seu ordre real.
Solucions
Solució 1
// src/controladors/esdeveniments.js (fragment)
const MAXIM_PER_PAGINA = 50;
async function llistarEsdeveniments(peticio, resposta) {
// Lectura sense reassignar req.query; tota la conversio es manual.
const { sala, exhaurit } = peticio.query;
const pagina = Math.max(1, Number.parseInt(peticio.query.pagina ?? '1', 10) || 1);
const brut = Number.parseInt(peticio.query.perPagina ?? '10', 10) || 10;
const perPagina = Math.min(MAXIM_PER_PAGINA, Math.max(1, brut));
let esdeveniments = await obtenirCataleg();
if (sala) esdeveniments = esdeveniments.filter((e) => e.sala === sala);
if (exhaurit === 'true' || exhaurit === 'false') {
esdeveniments = esdeveniments.filter((e) => e.exhaurit === (exhaurit === 'true'));
}
const total = esdeveniments.length;
const desDe = (pagina - 1) * perPagina;
resposta.json({
pagina, perPagina, total,
totalPagines: Math.max(1, Math.ceil(total / perPagina)),
esdeveniments: esdeveniments.slice(desDe, desDe + perPagina).map((e) => e.toJSON()),
});
}Tota aquesta conversió manual —parseInt, topalls, ?? '1'— és justament el que substituirem per un esquema a 06-06.
Solució 2
// src/controladors/sessions.js
async function carregarSessio(peticio, resposta, seguent, valorId) {
const cataleg = await obtenirCataleg();
for (const esdeveniment of cataleg) {
const sessio = esdeveniment.cercarSessio(valorId);
if (sessio) {
peticio.sessio = sessio;
peticio.esdevenimentDeLaSessio = esdeveniment;
seguent();
return;
}
}
const missatge = `No existeix la sessio ${valorId}`;
seguent(Object.assign(new Error(missatge), { codi: 'SESSIO_NO_TROBADA' }));
}
const obtenirSessio = (peticio, resposta) =>
resposta.json({ ...peticio.sessio, idEsdeveniment: peticio.esdevenimentDeLaSessio.id });
module.exports = { carregarSessio, obtenirSessio };I curl -si -X PUT localhost:3000/api/sessions/ses-001-1 retorna 405 Method Not Allowed amb Allow: GET, HEAD, generat per Express sense que tu escriguis res.
Solució 3
// ordre.js — versio que falla; reordena per comparar.
const aplicacio = require('express')();
aplicacio.get('/*resta', (p, r) => r.json({ ruta: 'comodi' }));
aplicacio.get('/destacats', (p, r) => r.json({ ruta: 'destacats' }));
aplicacio.get('/:id', (p, r) => r.json({ ruta: 'parametre' }));
// La llista de rutes en el seu ordre real es treu de la pila interna.
aplicacio.get('/depuracio/rutes', (peticio, resposta) => {
resposta.json(
aplicacio.router.stack.filter((c) => c.route).map((c) => c.route.path)
);
});
aplicacio.listen(3000);Amb aquest ordre, curl -s localhost:3000/destacats i curl -s localhost:3000/evt-001 retornen tots dos {"ruta":"comodi"}. Després de reordenar (destacats, :id, *resta) retornen {"ruta":"destacats"} i {"ruta":"parametre"}. Nota: /depuracio/rutes tampoc no s'arriba a assolir mentre el comodí estigui declarat abans; és el mateix exercici demostrant-se a si mateix.
Conclusió
Escena Viva ja té una API organitzada: tres routers muntats sota /api, controladors que només tradueixen HTTP a domini, router.param() carregant l'esdeveniment una sola vegada i deixant-lo a req.esdeveniment, i router.route() agrupant els mètodes de cada recurs amb el 405 i la seva capçalera Allow de franc. Has vist que :id i req.params són el teu compilarPatro més ben resolt, que els comodins d'Express 5 exigeixen nom i continuen necessitant resoldreDinsDe, que req.query és de només lectura i per què això trenca codi antic, i que l'ordre de declaració de les rutes és una regla que Express hereta, no que resol.
Queda un cap solt molt visible: POST /api/comandes llegeix req.body, i req.body no existeix per si sol: apareix únicament si abans de la ruta s'ha executat express.json(). Aquest «abans de la ruta» és la porta al concepte central del framework. A la lliçó següent, Middleware, veurem què és exactament aquesta cadena de funcions (req, res, next) que travessa cada petició: com es recorre, què signifiquen next(), next(error) i —l'error més frustrant d'Express— no cridar next() en absolut. Escriurem els middleware propis d'Escena Viva (registre amb durada, identificador de petició, control de memòria cau) i desmuntarem express.json() i express.static() comparant-los amb el cos.js i l'estatics.js que vas escriure a mà.
Curs de Node.js: De Principiant a Avançat
Mòdul 1: Introducció a Node.js
- Què és Node.js?
- Instal·lació i Configuració de l'Entorn
- El Teu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Modern per a Node.js
- El Projecte del Curs: la Plataforma Escena Viva
Mòdul 2: Conceptes Bàsics
- Arquitectura de Node.js
- El Bucle d'Esdeveniments (Event Loop)
- Callbacks i Programació Asíncrona
- Promeses i async/await
- Esdeveniments i EventEmitter
- Mòduls CommonJS i require()
- Mòduls ES i Interoperabilitat
Mòdul 3: Sistema de Fitxers i E/S
- Lectura i Escriptura de Fitxers
- El Mòdul fs a Fons
- Rutes Multiplataforma amb el Mòdul path
- Treballant amb Streams
- Streams de Transformació i pipeline
- Buffers i Dades Binàries
Mòdul 4: HTTP i Servidors Web
- Creant un Servidor HTTP Simple
- Gestió de Sol·licituds i Respostes
- Enrutament Manual
- Servint Fitxers Estàtics
- Rebent Dades: Cossos de Petició i JSON
- Consumint APIs Externes des de Node.js
Mòdul 5: NPM i Gestió de Paquets
- Introducció a NPM i package.json
- Instal·lació i Ús de Paquets
- Versionat Semàntic i package-lock
- Scripts d'npm i Automatització del Projecte
- Creació i Publicació de Paquets
- Seguretat i Manteniment de Dependències
Mòdul 6: Framework Express.js
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Enrutament a Express
- Middleware
- Middleware de Tercers Essencials
- Validació de Dades d'Entrada
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Usant MongoDB amb Mongoose
- Operacions CRUD
- Relacions, Poblat i Consultes Avançades
- Usant Bases de Dades SQL amb Sequelize
- Migracions, Transaccions i Dades de Prova
Mòdul 8: Autenticació i Autorització
- Introducció a l'Autenticació
- Registre d'Usuaris i Hash de Contrasenyes
- Sessions i Galetes amb Passport.js
- Autenticació amb JWT
- Control d'Accés Basat en Rols
- Bones Pràctiques de Seguretat en APIs
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Dobles de Prova amb Sinon
- Proves d'Integració
- Cobertura i Automatització de les Proves
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
- El Mòdul Cluster
- Fils de Treball (Worker Threads)
- Memòria Cau i Cues de Treball amb Redis
- Optimització del Rendiment
- Construcció d'APIs RESTful
- GraphQL amb Node.js
Mòdul 11: Desplegament i DevOps
- Configuració i Variables d'Entorn
- Registre i Monitoratge en Producció
- Usant PM2 per a la Gestió de Processos
- Empaquetatge amb Docker
- Desplegant a Heroku i Altres PaaS
- Integració i Desplegament Continus
