Ja sabem llegir una petició i construir una resposta correcta. Falta la decisió que hi ha al mig: quin codi atén cada petició. Això és enrutar.
Un enrutador no és més que una funció que, donada una parella (mètode, ruta), troba el gestor que li correspon. Sona trivial, i el primer intent cap en deu línies d'if/else. El problema és que aquest primer intent s'ensorra tan bon punt apareix la primera ruta amb un identificador a dins, /esdeveniments/evt-001, i a partir d'aquí cada ruta nova empitjora el desordre. En aquesta lliçó construïm un enrutador de veritat: una taula de rutes, patrons tipus /esdeveniments/:id compilats a expressions regulars, extracció de paràmetres, 404 quan la ruta no existeix, 405 amb capçalera Allow quan existeix però amb un altre mètode, i un try/catch central que tradueix els nostres error.codi a estats HTTP fent servir la taula de la lliçó anterior. És la primera vegada que apareix la idea de gestor d'errors centralitzat, que Express formalitzarà a la lliçó 06-07.
Contingut
- Què és enrutar exactament
- L'intent ingenu:
if/elsesobrereq.url - On es trenca: rutes amb paràmetres
- La taula de rutes
compilarPatro: de/esdeveniments/:ida expressió regular- Normalitzar la ruta abans de casar-la
- Casar, extreure paràmetres i respondre
404o405 - Despatx asíncron i gestió d'errors centralitzada
- Les rutes de l'API d'Escena Viva
- Per què a partir del Mòdul 6 farem servir Express
- Què és enrutar exactament
Enrutar és casar una parella (mètode, ruta) amb un gestor. Res més. Tota la complexitat dels enrutadors reals —Express, Fastify, Koa— neix de tres exigències afegides, i un enrutador que les resolgui ja és útil de veritat:
- Els patrons tenen parts variables.
/esdeveniments/evt-001i/esdeveniments/evt-002han d'anar al mateix gestor, que necessita saber quin dels dos li ha tocat. - El mètode forma part de la identitat de la ruta.
GET /comandes(llistar) iPOST /comandes(crear) són coses diferents amb el mateix camí. - Les fallades s'han de distingir. No és el mateix "aquesta ruta no existeix" (
404) que "aquesta ruta existeix, però no amb aquest mètode" (405).
- L'intent ingenu:
if/else sobre req.url
if/else sobre req.urlAixí comença tothom, i per a dues rutes està perfectament bé:
async function gestionarPeticio(req, res) {
if (req.method === 'GET' && req.url === '/salut') return respondreJson(res, 200, { estat: 'ok' });
if (req.method === 'GET' && req.url === '/esdeveniments') return respondreJson(res, 200, await obtenirCataleg());
return respondreError(res, 404, 'Ruta no trobada', 'RECURS_NO_TROBAT');
}Fixa't en els return: són la disciplina de la lliçó anterior, la que evita ERR_HTTP_HEADERS_SENT. I aquest codi ja té una fallada silenciosa: compara amb req.url en comptes de comparar amb el pathname. GET /esdeveniments?sala=Sala%20B%C3%B3veda no casa amb '/esdeveniments', perquè la cadena inclou la consulta. El resultat és un 404 desconcertant que apareix només quan algú afegeix un filtre. La correcció és la de 04-02: analitzar amb new URL i comparar contra url.pathname.
- On es trenca: rutes amb paràmetres
Ara afegeix GET /esdeveniments/evt-001. Com que l'identificador és variable, la comparació per igualtat ja no serveix, i apareix el trossejat manual:
// El cami cap al desastre
const parts = url.pathname.split('/'); // ['', 'esdeveniments', 'evt-001']
if (req.method === 'GET' && parts[1] === 'esdeveniments' && parts.length === 3) {
return gestionarEsdeveniment(req, res, parts[2]);
}
if (req.method === 'GET' && parts[1] === 'esdeveniments' && parts[3] === 'sessions') {
return gestionarSessions(req, res, parts[2]);
}Amb quatre rutes és suportable. Amb vint és il·legible, i pel camí et trobaràs amb problemes molt concrets:
| Problema | Símptoma |
|---|---|
Índexs màgics (parts[2]) |
Ningú recorda què és el 2; afegir un prefix /api ho trenca tot |
| Condicions de longitud | parts.length === 3 s'oblida i /esdeveniments/x/y/z casa per accident |
| El mètode es repeteix a cada branca | Un DELETE /esdeveniments cau al 404 genèric en comptes de donar 405 |
| Sense descodificar, i ordre fràgil | Un %20 arriba amb el percentatge posat; moure un if deixa una ruta inabastable |
La solució no és escriure millors if: és separar la declaració de les rutes de la lògica que les casa. Declares una taula; un motor genèric la recorre.
- La taula de rutes
Una ruta és un objecte amb tres camps —{ metode: 'GET', patro: '/esdeveniments/:id', gestor: obtenirEsdeveniment }— i l'enrutador és una llista d'aquests objectes més dues operacions: registrar i despatxar.
Amb aquesta forma, afegir una ruta és afegir una fila, i el motor no canvia mai. Aquest és el camí complet d'una petició, i tot el que ve a continuació és omplir aquestes caixes:
flowchart LR
A[request] --> B[normalitzar ruta]
B --> C{cercar a la taula}
C -->|casa metode i patro| D[gestor amb parametres]
C -->|casa patro, no metode| E[405 + Allow]
C -->|no casa res| F[404]
D -->|llanca error| G[try/catch central]
G --> H[estatPerError]
compilarPatro: de /esdeveniments/:id a expressió regular
compilarPatro: de /esdeveniments/:id a expressió regularNecessitem convertir la cadena '/esdeveniments/:id' en alguna cosa que sàpiga dir si '/esdeveniments/evt-001' casa i, a més, extreure'n evt-001. L'eina natural és una expressió regular amb grups de captura. Es construeix en tres passos:
// Compila un patro tipus '/esdeveniments/:id/sessions' a una expressio regular
// i la llista ordenada de noms dels seus parametres.
function compilarPatro(patro) {
const noms = [];
// Pas 1: escapar els caracters amb significat especial en una regex.
// Un patro com '/informes/2026.json' te un punt que ha de ser literal.
const escapat = patro.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Pas 2: substituir cada :nom per un grup de captura d'UN segment.
const font = escapat.replace(/:([a-zA-Z][a-zA-Z0-9]*)/g, (_, nom) => {
noms.push(nom);
return '([^/]+)';
});
// Pas 3: ancorar. Sense ^ i $ la ruta casaria per trossos.
return { expressio: new RegExp(`^${font}$`), noms };
}Val la pena llegir a poc a poc l'expressió resultant. Per a '/esdeveniments/:id/sessions' obtenim:
compilarPatro('/esdeveniments/:id/sessions');
// { expressio: /^\/esdeveniments\/([^\/]+)\/sessions$/, noms: ['id'] }Peça a peça:
| Fragment | Què significa |
|---|---|
^ … $ |
La ruta comença i acaba aquí: no val casar pel mig |
\/esdeveniments\/ |
Text literal (la barra no necessita escapament amb new RegExp, però no molesta) |
( … ) |
Grup de captura: el que casi aquí es podrà recuperar després |
[^/]+ |
Un o més caràcters que no siguin barra: mai no creua a un altre segment |
Els dos detalls crítics són [^/]+ i les àncores. [^/]+ en comptes de .+: amb .+, el patró /esdeveniments/:id casaria amb /esdeveniments/evt-001/sessions i id valdria 'evt-001/sessions', atropellant una altra ruta. I sense ^ i $, /esdeveniments casaria dins de /admin/esdeveniments/esborrar, cosa que a més d'un error funcional és un forat de seguretat.
També importa + en comptes de *: amb *, /esdeveniments/ (barra final, sense identificador) casaria amb id valent cadena buida, i acabaries buscant l'esdeveniment ''.
Amb el patró compilat, casar una ruta i extreure'n els paràmetres és directe:
function aparellar(compilat, ruta) {
const coincidencia = compilat.expressio.exec(ruta);
if (coincidencia === null) return null;
const parametres = {};
compilat.noms.forEach((nom, index) => {
// El grup 0 es la coincidencia completa; els parametres comencen a l'1.
// Es descodifica AQUI, un segment cada cop (mira la llico 04-02).
parametres[nom] = decodeURIComponent(coincidencia[index + 1]);
});
return parametres;
}Tornar null davant de {} és deliberat: un patró sense paràmetres que casa torna un objecte buit, que és diferent de "no casa". Confondre les dues coses amb un if (!parametres) seria un error, perquè {} és un valor verdader i null no.
- Normalitzar la ruta abans de casar-la
La mateixa pàgina es pot demanar de diverses maneres, i totes han de dur al mateix lloc:
| Petició | Problema | Decisió |
|---|---|---|
/esdeveniments/ |
Barra final sobrera | Treure-la (excepte a l'arrel /) |
/ESDEVENIMENTS |
Majúscules | No canviar-les: les rutes distingeixen majúscules per norma |
/esdeveniments//evt-001 |
Barra duplicada | Col·lapsar-la en una |
/esdeveniments%2Fevt-001 |
Barra codificada | No descodificar la ruta sencera: es fa per segments |
/esdeveniments?sala=X |
Consulta inclosa | Casar contra pathname, no contra req.url |
// Deixa la ruta en forma canonica per poder casar-la de manera estable.
function normalitzarRuta(pathname) {
const collapsada = pathname.replace(/\/{2,}/g, '/');
return collapsada.length > 1 ? collapsada.replace(/\/+$/, '') : collapsada;
}Sobre les majúscules, la norma és clara i contraintuïtiva: el domini no distingeix majúscules, la ruta sí. /Esdeveniments i /esdeveniments són recursos diferents. Passar la ruta a minúscules "per comoditat" sembla amable fins que un identificador legítim porta majúscules (EV-2026-000123) i el destrosses. Si vols ser tolerant, fes-ho amb una redirecció 301 a la forma canònica, no casant en silenci: per la mateixa raó la barra final es normalitza en comptes de registrar dues rutes, perquè un mateix contingut vivint a dues adreces confon les memòries cau i penalitza als cercadors.
- Casar, extreure paràmetres i respondre
404 o 405
404 o 405Ja tenim totes les peces. Aquest és src/servidor/enrutador.js complet:
// src/servidor/enrutador.js
// Enrutador minim: una taula de { metode, patro, gestor } i un
// despatxador que casa (metode, ruta), extreu parametres i centralitza errors.
const { respondreError } = require('./respostes.js');
// compilarPatro, aparellar i normalitzarRuta: vistos als apartats 5 i 6.
function crearEnrutador() {
const rutes = [];
function registrar(metode, patro, gestor) {
rutes.push({ metode, patro, compilat: compilarPatro(patro), gestor });
return { registrar, get, post, despatxar }; // encadenable
}
const get = (patro, gestor) => registrar('GET', patro, gestor);
const post = (patro, gestor) => registrar('POST', patro, gestor);
async function despatxar(req, res) {
const url = new URL(req.url, `http://${req.headers.host ?? 'localhost'}`);
const ruta = normalitzarRuta(url.pathname);
// HEAD es serveix amb el gestor de GET: Node ja descarta el cos.
const metode = req.method === 'HEAD' ? 'GET' : req.method;
const metodesPermesos = new Set(); // per poder donar un 405 util
for (const registre of rutes) {
const parametres = aparellar(registre.compilat, ruta);
if (parametres === null) continue; // ni tan sols el cami coincideix
metodesPermesos.add(registre.metode);
if (registre.metode === metode) {
return registre.gestor(req, res, { parametres, consulta: url.searchParams, url });
}
}
if (metodesPermesos.size > 0) {
// La ruta existeix: el 405 HA d'incloure Allow amb els metodes valids.
if (metodesPermesos.has('GET')) metodesPermesos.add('HEAD');
const permesos = [...metodesPermesos].sort().join(', ');
return respondreError(res, 405, `Metode ${req.method} no permes a ${ruta}`,
'METODE_NO_PERMES', { permesos }, { Allow: permesos });
}
return respondreError(res, 404, `No existeix la ruta ${ruta}`, 'RECURS_NO_TROBAT');
}
return { registrar, get, post, despatxar, rutes };
}
module.exports = { crearEnrutador, compilarPatro, aparellar, normalitzarRuta };El 405 és la part que gairebé tothom es salta, i la norma és explícita: una resposta 405 ha d'incloure la capçalera Allow amb els mètodes acceptats. Veure-ho funcionant:
Un client que rep un 404 pensa que s'ha equivocat d'URL i es rendeix. Un que rep 405 amb Allow: GET, HEAD sap exactament què fer. És la diferència entre una API que es pot explorar i una que s'ha d'endevinar.
Sobre l'ordre i l'especificitat: el nostre enrutador recorre la taula i guanya la primera que casa, així que les rutes més específiques van primer. Amb /esdeveniments/:id i /esdeveniments/destacats registrades en aquest ordre, /esdeveniments/destacats cauria a la primera amb id = 'destacats'. Els enrutadors seriosos ordenen per especificitat; el nostre delega aquesta responsabilitat en tu, i per això convé registrar sempre els literals abans que els patrons amb paràmetres.
- Despatx asíncron i gestió d'errors centralitzada
Els gestors són async, perquè gairebé tots llegeixen el catàleg. Això vol dir que qualsevol pot llançar, i no volem repetir un try/catch a cadascun. El pugem una sola vegada al despatxador:
// Embolcalla el despatx: un unic punt on es tradueixen els errors.
function crearGestorHttp(enrutador) {
return function gestionarPeticio(req, res) {
// Promise.resolve() captura tambe el que llanci despatxar de forma sincrona.
Promise.resolve().then(() => enrutador.despatxar(req, res)).catch((error) => {
if (res.headersSent) {
// Les capcaleres ja han sortit: no es pot desmentir l'estat.
console.error("[error] despres d'enviar capcaleres:", error);
return res.destroy();
}
const { estat, codi, missatge } = cosPerError(error);
respondreError(res, estat, missatge, codi);
});
};
}Això és un abans i un després en l'arquitectura del servidor:
- Els gestors deixen de gestionar errors HTTP. Si l'esdeveniment no existeix, llancen
ESDEVENIMENT_NO_TROBATi se n'obliden. La traducció a404la facosPerError, en un sol lloc. - El domini continua sense conèixer HTTP.
Esdeveniment.reservarllançaAFORAMENT_INSUFICIENTigual que quan el cridava la CLI del Mòdul 1. - No es perd res. Un error sense traducció coneguda es converteix en
500i es registra astderr, que és exactament el que volem per assabentar-nos-en. res.headersSentdecideix l'últim recurs. Si la resposta ja estava en marxa (per exemple, enmig de servir un fitxer), el que és honrat és destruir la connexió: el client detectarà la resposta truncada.
Aquest patró —un embolcall que captura i tradueix— és exactament el que Express anomena gestor d'errors, amb la seva signatura de quatre arguments (err, req, res, next). El veurem a la lliçó 06-07 i en reconeixeràs la idea a l'instant, perquè l'acabes d'escriure.
- Les rutes de l'API d'Escena Viva
Amb el motor a punt, declarar l'API és gairebé llegir una llista:
// src/servidor/rutes-api.js
// Declaracio de les rutes HTTP d'Escena Viva. Els gestors NO
// gestionen errors: llancen amb error.codi i ja decideix l'enrutador.
const { obtenirCataleg, obtenirEsdevenimentPerId } = require('../cataleg-dades.js');
const { respondreJson } = require('./respostes.js');
const arrencada = Date.now();
async function cercarEsdevenimentObligatori(id) {
const esdeveniment = await obtenirEsdevenimentPerId(id);
if (!esdeveniment) {
const error = new Error(`No existeix l'esdeveniment ${id}`);
error.codi = 'ESDEVENIMENT_NO_TROBAT';
throw error;
}
return esdeveniment;
}
function registrarRutes(enrutador) {
enrutador.get('/salut', (req, res) => {
const segonsEnMarxa = Math.round((Date.now() - arrencada) / 1000);
respondreJson(res, 200, { estat: 'ok', versio: process.version, segonsEnMarxa });
});
enrutador.get('/esdeveniments', async (req, res, { consulta }) => {
const sala = consulta.get('sala');
const esdeveniments = (await obtenirCataleg())
.filter((esdeveniment) => sala === null || esdeveniment.sala === sala);
respondreJson(res, 200, { total: esdeveniments.length, esdeveniments });
});
enrutador.get('/esdeveniments/:id', async (req, res, { parametres }) => {
respondreJson(res, 200, await cercarEsdevenimentObligatori(parametres.id));
});
enrutador.get('/esdeveniments/:id/sessions', async (req, res, { parametres }) => {
const esdeveniment = await cercarEsdevenimentObligatori(parametres.id);
respondreJson(res, 200, { esdevenimentId: esdeveniment.id, total: esdeveniment.nombreSessions, sessions: esdeveniment.sessions });
});
return enrutador;
}
module.exports = { registrarRutes };I el servidor de la lliçó 04-01 se simplifica fins a una línia: http.createServer(crearGestorHttp(registrarRutes(crearEnrutador()))). Comprovació completa de l'API:
curl -s http://localhost:3000/salut # 200 {"estat":"ok",...}
curl -s http://localhost:3000/esdeveniments | head -3 # 200, total 3
curl -s http://localhost:3000/esdeveniments/evt-002 # 200, Noche de Monologos
curl -s http://localhost:3000/esdeveniments/evt-999 # 404 ESDEVENIMENT_NO_TROBAT
curl -s http://localhost:3000/esdeveniments/evt-003/sessions # 200, total 2
curl -i -X POST http://localhost:3000/esdeveniments # 405 amb Allow: GET, HEAD
curl -i http://localhost:3000/no-existeix # 404 RECURS_NO_TROBAT
- Per què a partir del Mòdul 6 farem servir Express
El nostre enrutador funciona, és curt i l'entens sencer. I tot i així, en un projecte real, gairebé ningú escriuria això. Val la pena ser honestos sobre el que no té:
| Li falta | Què implica |
|---|---|
| Middleware | No hi ha manera de dir "això s'executa abans de totes les rutes" (registre, CORS, autenticació) |
| Subenrutadors | Totes les rutes en una llista plana; res de muntar /api/v1 amb les seves rutes a dins |
| Ordre per especificitat | Registrar en mal ordre fa inabastable una ruta, sense avís |
| Patrons rics | Res d'opcionals, comodins ni restriccions per paràmetre |
| Anàlisi del cos, galetes, contingut negociat | Cada cosa s'ha de fer a mà (comencem a 04-05) |
Aleshores, per què hem fet això? Perquè Express no és màgia, és aquesta mateixa taula amb més anys a sobre. Quan a la lliçó 06-03 escriguis app.get('/esdeveniments/:id', gestor), sabràs que per dins hi ha un patró compilat a expressió regular, un objecte req.params que surt dels grups de captura i un despatxador que recorre una llista. I quan alguna cosa no funcioni —una ruta que no s'assoleix, un 404 inesperat, un gestor d'errors que no es dispara—, no estaràs depurant una caixa negra.
Errors Comuns i Consells
- Casar contra
req.urlen comptes deurl.pathname. Qualsevol paràmetre de consulta trenca la coincidència i produeix un404inexplicable. - Fer servir
.+en lloc de[^/]+per als paràmetres. El patró es menja segments sencers i atropella altres rutes. - Oblidar les àncores
^i$./esdevenimentscasaria dins de/admin/esdeveniments/esborrar. - Registrar
/esdeveniments/:idabans que/esdeveniments/destacats. Guanya la primera que casa, i la literal queda inabastable. - Tornar
404quan correspon405. I si tornes405, la capçaleraAllowés obligatòria. - Passar la ruta a minúscules. Destrossa identificadors legítims com
EV-2026-000123. Si vols tolerància, redirigeix amb301. - Posar un
try/catcha cada gestor. Repetició, incoherència i errors que s'empassen. Un de central i prou. - Consell: exposa
enrutador.rutesi afegeix una rutaGET /rutesen desenvolupament que les llisti. És la documentació més barata que existeix.
Exercicis
Exercici 1: provar compilarPatro sense servidor
Escriu src/laboratori/provar-patrons.js que, sense aixecar cap servidor, comprovi aquests casos i imprimeixi una taula patro | ruta | casa | parametres: /esdeveniments contra /esdeveniments i /esdeveniments/evt-001; /esdeveniments/:id contra /esdeveniments/evt-001, /esdeveniments/evt-001/sessions i /esdeveniments/; /esdeveniments/:id/sessions/:sessioId contra /esdeveniments/evt-002/sessions/ses-002-1; i /informes/2026.json contra /informes/2026Xjson. Explica l'últim cas.
Exercici 2: DELETE i el 405
Registra DELETE /esdeveniments/:id a l'enrutador amb un gestor que llanci ESTAT_INVALID si l'esdeveniment té entrades venudes. Comprova amb curl -i que: DELETE /esdeveniments/evt-001 dóna 409, DELETE /esdeveniments/evt-999 dóna 404, PUT /esdeveniments/evt-001 dóna 405 amb Allow: DELETE, GET, HEAD, i DELETE /esdeveniments dóna 405 amb Allow: GET, HEAD.
Exercici 3: comptar rutes i temps
Afegeix a l'enrutador un comptador per ruta (fent servir registre.patro com a clau) amb el nombre de peticions i el temps total en mil·lisegons, i una ruta GET /metriques que el torni ordenat per temps mitjà descendent. Mesura'n l'efecte: quina ruta és la més lenta i per què?
Solucions
Solució 1. El cas interessant és l'últim: /informes/2026.json contra /informes/2026Xjson no casa, perquè el pas 1 de compilarPatro va escapar el punt i el va convertir en literal. Sense aquest escapament, . casaria amb qualsevol caràcter i la X s'hi colaria.
const { compilarPatro, aparellar } = require('../servidor/enrutador.js');
const casos = [
['/esdeveniments', '/esdeveniments'],
['/esdeveniments/:id', '/esdeveniments/evt-001'],
['/esdeveniments/:id', '/esdeveniments/evt-001/sessions'], // null: [^/]+ no creua segments
['/esdeveniments/:id', '/esdeveniments/'], // null: + exigeix un caracter com a minim
['/informes/2026.json', '/informes/2026Xjson'] // null: el punt esta escapat
];
for (const [patro, ruta] of casos) {
const parametres = aparellar(compilarPatro(patro), ruta);
console.log(`${patro.padEnd(34)} | ${ruta.padEnd(34)} | ${parametres !== null} | ${JSON.stringify(parametres)}`);
}Solució 2. El gestor només s'ocupa del domini; els estats els posa la taula de 04-02:
enrutador.registrar('DELETE', '/esdeveniments/:id', async (req, res, { parametres }) => {
const esdeveniment = await cercarEsdevenimentObligatori(parametres.id); // llanca 404
if (esdeveniment.entradesVenudes > 0) {
const error = new Error(`L'esdeveniment ${esdeveniment.id} te ${esdeveniment.entradesVenudes} entrades venudes`);
error.codi = 'ESTAT_INVALID'; // -> 409
throw error;
}
respondreSenseContingut(res, 204);
});evt-001 té 276 entrades venudes, així que torna 409. Fixa't que el PUT dóna 405 amb DELETE i GET a Allow: l'enrutador ha recorregut la taula sencera acumulant els mètodes de tots els registres el patró dels quals casava, i per això l'Allow surt complet.
Solució 3. El comptador s'embolcalla al voltant de la crida al gestor, dins de despatxar:
const metriques = new Map();
async function invocarAmbMetrica(registre, req, res, context) {
const inici = process.hrtime.bigint();
try {
return await registre.gestor(req, res, context);
} finally {
const ms = Number(process.hrtime.bigint() - inici) / 1e6;
const previ = metriques.get(registre.patro) ?? { peticions: 0, msTotal: 0 };
metriques.set(registre.patro, { peticions: previ.peticions + 1, msTotal: previ.msTotal + ms });
}
}El finally és imprescindible: sense ell, una petició que llança no es comptabilitzaria i les mitjanes mentirien justament en els casos que més interessen. La ruta més lenta serà /esdeveniments/:id/sessions, perquè obtenirEsdevenimentPerId recorre el catàleg després de llegir-lo; amb la memorització de la lliçó 03-01, la primera petició costa una lectura de disc i les següents gairebé res. Aquest salt entre el primer mesurament i els altres es veu perfectament a /metriques.
Conclusió
Escena Viva ja té una API navegable. Has vist que enrutar és casar (mètode, ruta) amb un gestor, que l'if/else sobre req.url està malament des del primer moment —compara la consulta juntament amb la ruta— i que es torna insostenible tan bon punt apareixen els paràmetres: índexs màgics, condicions de longitud i un ordre fràgil que ningú no gosa tocar.
L'alternativa és src/servidor/enrutador.js: una taula de { metode, patro, gestor } i un motor genèric. compilarPatro tradueix /esdeveniments/:id a /^\/esdeveniments\/([^\/]+)$/, i ara saps exactament per què cada peça és com és: les àncores ^ i $ perquè la ruta casi sencera, [^/]+ perquè un paràmetre no es mengi un segment veí, + en comptes de * per rebutjar el valor buit, i l'escapament previ perquè un punt del patró sigui un punt de veritat. aparellar extreu els grups de captura i els descodifica per segments, normalitzarRuta col·lapsa barres i treu la final, i les majúscules es respecten perquè les rutes les distingeixen. Les fallades també: 404 si no casa res, i 405 amb la capçalera Allow obligatòria si la ruta existeix amb un altre mètode.
I sobretot, tens el teu primer gestor d'errors centralitzat: un únic catch al despatxador que crida cosPerError, tradueix ESDEVENIMENT_NO_TROBAT a 404 i ESTAT_INVALID a 409, registra els 500 sense filtrar-los i comprova res.headersSent abans d'intentar respondre. Els gestors de src/servidor/rutes-api.js han quedat nets: fan la seva feina i llancen amb error.codi. Aquesta idea és la que Express formalitza a 06-07, i la reconeixeràs a l'instant perquè l'acabes d'escriure.
Amb GET /esdeveniments, GET /esdeveniments/:id, GET /esdeveniments/:id/sessions i GET /salut en marxa, l'API torna JSON. Però una plataforma de venda d'entrades també ha de mostrar una pàgina. A la lliçó següent, Servint Fitxers Estàtics, muntarem el mini front-end d'Escena Viva des d'aquest mateix servidor: mapejarem la URL a una ruta de disc protegida amb resoldreDinsDe —i veuràs en directe com GET /../../dades/esdeveniments.json s'emporta les teves dades sense aquesta defensa—, enviarem els fitxers amb createReadStream i pipeline en comptes de readFile, i aprendrem a respondre 304 Not Modified amb ETag perquè la segona visita no descarregui res.
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
