Tancàvem el Mòdul 3 amb una frase: el projecte sap llegir i escriure, però no sap parlar amb ningú. Escena Viva té un catàleg al disc, informes per mes, rutes blindades i canonades que processen vendes amb memòria constant. Tot això ho consumeix una única persona: qui executa l'ordre a la seva terminal.

Avui això canvia. En acabar aquesta lliçó, qualsevol navegador de la teva xarxa podrà demanar dades a Escena Viva.

I veuràs de seguida que no som en territori desconegut. Un servidor HTTP de Node és un EventEmitter que emet request —els mateixos on i emit de la lliçó 02-05—, arrencar-lo és una operació asíncrona que es confirma amb un esdeveniment, i mantenir el procés viu és exactament el mecanisme de compte de referències del bucle d'esdeveniments que vam estudiar a 02-01. El mòdul node:http no afegeix conceptes nous: connecta a la xarxa els que ja tens.

Contingut

  1. HTTP en cinc minuts: petició, resposta i un intercanvi real
  2. http.createServer: el servidor és un EventEmitter
  3. server.listen, l'esdeveniment listening i el port
  4. El primer servidor d'Escena Viva
  5. Tres maneres de provar-lo: navegador, curl i node -e
  6. Per què el procés ja no acaba tot sol
  7. Errors d'arrencada: EADDRINUSE i EACCES
  8. Aturada ordenada: server.close() i SIGINT

  1. HTTP en cinc minuts: petició, resposta i un intercanvi real

HTTP és un protocol de petició i resposta sobre text. El client obre una connexió TCP, envia un bloc de text amb un format molt concret, i el servidor contesta amb un altre bloc. Res més. Ni el client ni el servidor conserven memòria del que ha passat entre una petició i l'altra: HTTP és sense estat, i tot el que sembla estat (sessions, cistelles, usuaris connectats) es construeix a sobre, com veurem al Mòdul 8.

Una petició té quatre parts i una resposta tres:

Missatge Part Exemple Què significa
Petició Mètode GET, POST, PUT, DELETE, HEAD La intenció: llegir, crear, reemplaçar, esborrar
Petició Ruta /esdeveniments/evt-001?format=json Quin recurs, amb paràmetres de consulta opcionals
Petició Capçaleres Accept: application/json Metadades: què accepto, qui sóc, què envio
Petició Cos {"sessioId":"ses-001-1"} Dades. Opcional; GET normalment no en porta
Resposta Codi d'estat 200, 404, 500 Com ha anat, en un nombre de tres xifres
Resposta Capçaleres Content-Type: application/json Metadades sobre el que torno
Resposta Cos El JSON, l'HTML, els bytes del cartell El contingut. Opcional (un 204 no en porta)

Així es veu un intercanvi complet, tal com viatja pel cable. La línia en blanc és el que separa les capçaleres del cos, i és obligatòria:

GET /esdeveniments HTTP/1.1
Host: localhost:3000
User-Agent: curl/8.5.0
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 69
Date: Tue, 14 Aug 2026 09:12:30 GMT
Connection: keep-alive

{
  "esdeveniments": 3,
  "sessions": 7,
  "entradesLliures": 1189
}

Fixa't en dos detalls que t'estalviaran hores de depuració més endavant:

  • La ruta que arriba al servidor no inclou el domini. Host: localhost:3000 viatja en una capçalera a part. Per això, com veurem a la lliçó següent, req.url val /esdeveniments i mai http://localhost:3000/esdeveniments.
  • Les capçaleres són text pla Nom: valor, insensibles a majúscules, i es poden repetir. Node te les lliurarà sempre normalitzades a minúscules.

La bona notícia és que no hauràs d'escriure aquest text a mà: el mòdul node:http l'analitza en rebre'l i el genera en respondre. Tu treballes amb dos objectes, req i res.

  1. http.createServer: el servidor és un EventEmitter

El servidor mínim cap en quatre línies:

const http = require('node:http');

const servidor = http.createServer((req, res) => {
  res.end("Hola des d'Escena Viva\n");
});

servidor.listen(3000);

Desa'l com a hola.js, executa'l amb node hola.js i obre http://localhost:3000. Ja tens un servidor web.

Ara el que importa: aquesta funció que passes a createServer no té res d'especial. http.Server hereta de net.Server, que hereta d'EventEmitter. Passar el gestor al constructor és pur sucre sintàctic: Node fa un servidor.on('request', gestor) per tu. Aquestes dues versions són exactament equivalents:

// Versio A: el gestor com a argument (l'habitual)
const servidor = http.createServer((req, res) => {
  res.end('hola');
});

// Versio B: registrant l'oient a ma (identica a l'anterior)
const servidor = http.createServer();
servidor.on('request', (req, res) => {
  res.end('hola');
});

Saber-ho no és anecdòtic: vol dir que pots registrar diversos oients de request, i que tot el que vas aprendre sobre EventEmitter s'aplica aquí. Un cas real és separar el registre de peticions de la lògica:

// S'executa abans que l'altre: els oients s'invoquen en ordre de registre.
servidor.on('request', (req) => console.error(`[peticio] ${req.method} ${req.url}`));
servidor.on('request', gestionarPeticio);   // La logica de veritat

El diagnòstic va a stderr i les dades a stdout, com fem des del Mòdul 1.

Aquests són els esdeveniments del servidor que importen en aquest mòdul:

Esdeveniment Quan s'emet Arguments
request Arriba una petició HTTP completa (capçaleres; el cos encara pot estar viatjant) (req, res)
listening El sòcol ja accepta connexions —
connection S'obre una connexió TCP, abans de cap petició (socket)
close El servidor ha deixat d'acceptar i ja no queden connexions —
error Fallada del servidor, típicament en arrencar (error)
clientError Un client ha enviat alguna cosa que no és HTTP vàlid (error, socket)

La diferència entre connection i request mereix un moment. No són el mateix: HTTP/1.1 manté la connexió oberta per defecte (Connection: keep-alive), de manera que un navegador que carrega una pàgina amb tres imatges pot obrir una connexió i enviar-hi quatre peticions. Veure-ho en directe aclareix el concepte:

let connexions = 0;
let peticions = 0;

servidor.on('connection', () => console.error(`[tcp] connexions: ${++connexions}`));
servidor.on('request', () => console.error(`[http] peticions: ${++peticions}`));

Recarrega la pàgina un parell de vegades i veuràs com el comptador de peticions puja molt més de pressa que el de connexions. Aquest detall torna a l'apartat 8, quan intentem aturar el servidor.

  1. server.listen, l'esdeveniment listening i el port

listen és el que posa el servidor a escoltar. La seva signatura habitual és listen(port, host, callback), i és asíncrona: quan la crida retorna, el sòcol encara no està a punt.

servidor.listen(3000, '127.0.0.1', () => {
  console.error('Escoltant a http://127.0.0.1:3000');
});

Aquest callback no és error-first: és simplement un oient de listening registrat amb once. Per això aquesta versió fa el mateix:

servidor.on('listening', () => {
  const { address, port } = servidor.address();
  console.error(`Escoltant a http://${address}:${port}`);
});

servidor.listen(3000, '127.0.0.1');

servidor.address() torna { address, family, port } i només té valor després de listening; abans torna null. És la manera correcta d'esbrinar el port real quan demanes el port 0, que vol dir "el sistema operatiu en tria un de lliure" —un truc imprescindible a les proves automàtiques del Mòdul 9, on no pots fixar un port sense arriscar-te a col·lisions.

El segon argument, el host, decideix qui s'hi pot connectar: '127.0.0.1' accepta només connexions des d'aquesta mateixa màquina, i '0.0.0.0' (el valor per defecte) les accepta per totes les interfícies de xarxa, que és el que cal en contenidors o per provar des del mòbil.

En desenvolupament, 127.0.0.1 és l'opció prudent: evita que el teu servidor a mig fer quedi exposat a la xarxa de la cafeteria. A Docker, en canvi, és un error clàssic, perquè el contenidor no rebria el trànsit redirigit des de fora (ho veurem al Mòdul 11).

  1. El primer servidor d'Escena Viva

Anem amb el codi real. Crea src/servidor/servidor.js. La regla que ens autoimposem des del minut u és la que defensem tot el curs: res d'E/S síncrona dins del gestor. El catàleg es llegeix amb obtenirCataleg(), que és asíncron des de la lliçó 03-01.

// src/servidor/servidor.js
// Primer servidor HTTP d'Escena Viva: respon un resum del cataleg.

const http = require('node:http');
const { obtenirCataleg } = require('../cataleg-dades.js');

const PORT = Number(process.env.PORT) || 3000;
const HOST = process.env.HOST || '127.0.0.1';

// El gestor es ASINCRON: a dins hi ha una lectura de disc.
async function gestionarPeticio(req, res) {
  const esdeveniments = await obtenirCataleg();

  const resum = {
    esdeveniments: esdeveniments.length,
    sessions: esdeveniments.reduce((total, esdeveniment) => total + esdeveniment.nombreSessions, 0),
    aforamentTotal: esdeveniments.reduce((total, esdeveniment) => total + esdeveniment.aforamentTotal, 0),
    entradesVenudes: esdeveniments.reduce((total, esdeveniment) => total + esdeveniment.entradesVenudes, 0)
  };
  resum.entradesLliures = resum.aforamentTotal - resum.entradesVenudes;

  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  res.end(JSON.stringify(resum, null, 2));
}

function crearServidor() {
  // El gestor es async: torna una promesa que NINGU no recull. Si falla,
  // seria un unhandledRejection (llico 02-04), aixi que es captura aqui.
  const servidor = http.createServer((req, res) => {
    gestionarPeticio(req, res).catch((error) => {
      console.error('[error] fallada no controlada:', error);
      if (!res.headersSent) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'application/json; charset=utf-8');
      }
      res.end(JSON.stringify({ error: 'Error intern del servidor' }, null, 2));
    });
  });

  return servidor;
}

function principal() {
  const servidor = crearServidor();
  servidor.listen(PORT, HOST, () => {
    const { address, port } = servidor.address();
    console.error(`[servidor] escoltant a http://${address}:${port}`);
  });
}

if (require.main === module) principal();

module.exports = { crearServidor, gestionarPeticio, PORT, HOST };

Quatre decisions que ens acompanyaran durant tot el mòdul:

  • crearServidor() s'exporta sense arrencar-lo. Separar la construcció de l'arrencada és el que permetrà aixecar el servidor en un port aleatori dins d'una prova (Mòdul 9) sense que el fitxer obri un port pel simple fet de ser importat. El require.main === module és el que fa que continuï sent executable amb node src/servidor/servidor.js.
  • El .catch no és opcional. http.createServer ignora completament el valor que torni el gestor. Si el gestor és async i llança, la promesa queda rebutjada sense amo: el client es queda penjat fins que expiri el seu temps límit i, des de Node 15, el procés cau per unhandledRejection. Un sol esdeveniment corrupte al JSON tombaria el servidor sencer.
  • res.headersSent evita l'error ERR_HTTP_HEADERS_SENT: si la fallada s'ha produït després de començar a escriure la resposta, ja no es poden canviar les capçaleres. És l'error més comú del mòdul i el desgranem a 04-02.
  • El port surt de l'entorn. process.env.PORT permet canviar-lo sense tocar el codi; en producció l'imposa la plataforma. La configuració per entorn és el tema de la lliçó 11-01.

  1. Tres maneres de provar-lo: navegador, curl i node -e

Arrenca el servidor:

node src/servidor/servidor.js
# [servidor] escoltant a http://127.0.0.1:3000

El navegador és la prova més ràpida (http://localhost:3000), però també la que menys t'ensenya: no veus capçaleres ni codi d'estat, i afegeix peticions fantasma com /favicon.ico que t'embruten els registres. És útil, però no n'hi ha prou.

curl -i http://localhost:3000/ mostra la resposta completa, capçaleres incloses. És l'eina que farem servir a tot el mòdul:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Transfer-Encoding: chunked

{
  "esdeveniments": 3,
  "sessions": 7,
  "aforamentTotal": 3000,
  "entradesVenudes": 1811,
  "entradesLliures": 1189
}

Aquí hi ha els números de la llavor: 3 esdeveniments, 7 sessions, aforament 3000, 1811 venudes i 1189 lliures. Fixa't que no hi ha Content-Length sinó Transfer-Encoding: chunked; a 04-02 explicarem exactament per què i quan convé cadascun.

Indicadors de curl que farem servir: -i inclou les capçaleres, -s silencia la barra de progrés, -X força el mètode, -H afegeix una capçalera i -d envia un cos.

I node -e, per provar des del mateix Node amb fetch (l'API client que estudiarem a fons a 04-06):

node -e "fetch('http://localhost:3000/').then(r => r.json()).then(d => console.log(d.entradesLliures))"
# 1189

  1. Per què el procés ja no acaba tot sol

Executa el servidor i fixa't en una cosa que no havia passat mai en aquest curs: el procés no acaba. Tots els scripts anteriors feien la seva feina i tornaven el control a la terminal. Aquest es queda allà, aparentment sense fer res.

No és màgia, és el compte de referències del bucle d'esdeveniments de la lliçó 02-01. Node surt quan no queden operacions pendents que puguin produir feina futura. Un sòcol a l'escolta és exactament això: un gestor actiu que pot despertar el bucle en qualsevol moment. Mentre existeixi, el compte no arriba a zero i el procés continua viu.

Ho pots comprovar desactivant aquesta referència:

const servidor = http.createServer((req, res) => res.end('hola'));
servidor.listen(3000, () => console.error('escoltant'));
servidor.unref();   // "Si NOMES queda aixo, no em mantinguis viu"

Aquest programa imprimeix escoltant i acaba immediatament, sense haver servit res. El servidor estava correctament arrencat; simplement havia deixat de comptar. unref() té usos legítims (un servidor de mètriques secundari que no ha d'impedir el tancament), però per al servidor principal és una errada garantida. ref() desfà l'efecte.

La conseqüència pràctica és que, a partir d'ara, el teu procés té cicle de vida: arrenca, serveix durant dies i ha de poder aturar-se bé. D'això tracta l'apartat 8.

  1. Errors d'arrencada: EADDRINUSE i EACCES

Arrenca el servidor en una terminal i, sense parar-lo, arrenca'l en una altra:

node src/servidor/servidor.js
node:events:496
      throw er; // Unhandled 'error' event
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000

Reconeix aquest missatge: Unhandled 'error' event és el de la lliçó 02-05. Un EventEmitter que emet error sense oients llança l'excepció i tomba el procés. http.Server no és una excepció a la regla.

Les dues fallades d'arrencada que veuràs a la pràctica:

Codi Causa Solució
EADDRINUSE El port ja està ocupat per un altre procés (sovint, el teu servidor anterior) Tancar l'altre procés, o fer servir un altre port
EACCES Els ports per sota de 1024 requereixen privilegis d'administrador a Linux i macOS Fer servir un port alt (3000, 8080) i posar un proxy invers al davant en producció

EACCES sorprèn qui intenta escoltar al port 80 "perquè és el d'HTTP". En sistemes tipus Unix, els ports inferiors a 1024 són privilegis reservats: només root els pot obrir. La resposta correcta mai no és executar Node com a root, sinó escoltar en un port alt i deixar que Nginx o el balancejador de la plataforma escoltin al 80/443 (Mòdul 11).

L'arranjament consisteix a registrar un oient d'error abans de cridar listen, i donar un missatge que s'entengui:

servidor.on('error', (error) => {
  if (error.code === 'EADDRINUSE') {
    console.error(`[servidor] el port ${PORT} ja esta en us. Prova: PORT=3001 node ${__filename}`);
  } else if (error.code === 'EACCES') {
    console.error(`[servidor] sense permisos per al port ${PORT}. Fes servir un port >= 1024.`);
  } else {
    console.error('[servidor] error inesperat:', error);
  }
  process.exitCode = 1;
});

error.code (en anglès, propietat del sistema) és el que arriba de libuv; no el confonguis amb el nostre error.codi de domini, que traduirem a estats HTTP a la lliçó següent. I process.exitCode = 1 en comptes de process.exit(1): deixa que el procés acabi de manera natural buidant les seves memòries intermèdies de sortida, com ja vam veure al Mòdul 1.

Per localitzar el culpable d'un EADDRINUSE a Linux o macOS: lsof -i :3000 et diu quin procés té el port, i kill <PID> l'allibera.

  1. Aturada ordenada: server.close() i SIGINT

Quan prems Ctrl+C, la teva terminal envia el senyal SIGINT al procés, i el comportament per defecte de Node és morir a l'acte. Això és acceptable en un script; en un servidor, significa tallar a mitja frase les peticions en curs: un client es queda sense la seva resposta, i una escriptura de fitxer pot quedar a mitges.

L'aturada ordenada consisteix a deixar d'acceptar connexions noves i esperar que acabin les vives:

function installarAturadaOrdenada(servidor) {
  let aturant = false;

  function aturar(senyal) {
    if (aturant) return;   // Un segon Ctrl+C no ha de reentrar aqui
    aturant = true;
    console.error(`\n[servidor] rebut ${senyal}, tancant...`);

    // 1. Deixa d'acceptar connexions NOVES. El callback es crida
    //    quan l'ultima connexio viva s'ha tancat.
    servidor.close((error) => {
      if (error) process.exitCode = 1;
      console.error(error ? `[servidor] error en tancar: ${error.message}` : '[servidor] tancat netament');
    });

    // 2. Tanca les connexions ocioses keep-alive, que si no
    //    mantindrien el proces viu fins al seu temps limit.
    servidor.closeIdleConnections();

    // 3. Xarxa de seguretat: si en 10 s no ha tancat, forcar.
    const termini = setTimeout(() => {
      console.error('[servidor] termini exhaurit, tancament forcat');
      servidor.closeAllConnections();
      process.exit(1);
    }, 10_000);

    termini.unref();   // Aquest temporitzador no ha de mantenir viu el proces
  }

  process.on('SIGINT', () => aturar('SIGINT'));
  process.on('SIGTERM', () => aturar('SIGTERM'));
}

El punt delicat és el 2, i ve del que vam veure a l'apartat 2: close() no tanca les connexions existents, només el sòcol a l'escolta. Amb keep-alive, un navegador que t'ha demanat una pàgina deixa la seva connexió TCP oberta uns segons per si demana res més. Aquestes connexions ocioses no serveixen res, però compten com a vives, així que close() es queda esperant i sembla que el teu servidor "no tanca". closeIdleConnections() (Node 18.2+) despatxa exactament aquest cas: tanca les que no tenen una petició en curs i respecta les que sí que en tenen.

Sobre els senyals: SIGINT és el teu Ctrl+C; SIGTERM és el que envien Docker, systemd i les plataformes de desplegament per demanar un tancament ordenat, amb un termini després del qual envien SIGKILL, que no es pot capturar. Per això el temporitzador de seguretat: més val tancar tu en 10 segons que deixar que et matin als 30 deixant fitxers a mitges. Al Mòdul 11 hi tornarem amb PM2 i Docker.

Errors Comuns i Consells

  • Oblidar res.end(). El client es queda esperant indefinidament. Al navegador es veu com una pestanya que gira sense fi, i a curl com una resposta que no arriba mai. Tot camí d'execució del gestor ha d'acabar en un end().
  • Fer E/S síncrona dins del gestor. Un readFileSync en una ruta bloqueja el bucle d'esdeveniments i, per tant, tots els clients alhora, no només el que la va demanar. És acceptable a l'arrencada; mai dins de request.
  • Gestor async sense .catch. Promesa rebutjada sense amo, client penjat i procés caigut. Embolcalla'l sempre, com fa crearServidor().
  • No escoltar l'esdeveniment error del servidor. Un EADDRINUSE et tomba el procés amb una traça incomprensible en comptes d'un missatge útil.
  • Confondre connection amb request. Amb keep-alive, una connexió serveix moltes peticions. Comptar connexions no és comptar visites.
  • Consell: exporta crearServidor() sense arrencar-lo i arrenca només sota require.main === module. El teu jo del Mòdul 9, escrivint proves d'integració, t'ho agrairà.
  • Consell: durant el desenvolupament, node --watch src/servidor/servidor.js reinicia el procés en desar. Sense dependències i sense nodemon.

Exercicis

Exercici 1: comptador de connexions i peticions

Amplia src/servidor/servidor.js perquè porti el compte de connexions TCP i de peticions HTTP servides, i les exposi al resum JSON sota les claus connexions i peticions. Després, amb el servidor arrencat, executa curl tres vegades seguides i tot seguit recarrega la pàgina del navegador tres vegades. Explica per què els dos números no creixen igual.

Exercici 2: arrencada robusta

Escriu src/servidor/arrencar.js que rebi el port per process.argv (amb process.env.PORT com a segona opció i 3000 com a tercera), arrenqui el servidor i tracti els tres casos: arrencada correcta (imprimeix la URL real obtinguda de servidor.address()), EADDRINUSE (missatge clar i exitCode 1) i EACCES. Prova'l amb els ports 3000, 80 i amb dues instàncies alhora.

Exercici 3: mesurar l'aturada ordenada

Afegeix una ruta lenta al gestor: si req.url és /lent, espera 5 segons amb dormir (de src/utils/dormir.js) abans de respondre. Llança curl http://localhost:3000/lent i, mentre espera, prem Ctrl+C al servidor. Comprova que la petició es completa i que només després apareix tancat netament. Repeteix-ho traient closeIdleConnections() i explica què canvia.

Solucions

Solució 1. Els comptadors són estat del servidor, així que viuen a crearServidor:

function crearServidor() {
  const estadistiques = { connexions: 0, peticions: 0 };

  const servidor = http.createServer((req, res) => {
    estadistiques.peticions++;
    gestionarPeticio(req, res, estadistiques).catch(/* ... */);
  });

  servidor.on('connection', () => { estadistiques.connexions++; });

  return servidor;
}

Tres curl produeixen 3 connexions i 3 peticions: curl tanca la seva connexió en acabar cada invocació. Tres recàrregues del navegador produeixen normalment 1 connexió i 6 peticions o més: el navegador reutilitza la connexió gràcies a keep-alive i, a més, demana /favicon.ico pel seu compte. Aquesta asimetria és justament la raó de ser de closeIdleConnections().

Solució 2. La clau és que l'oient d'error es registri abans de listen, perquè la fallada s'emet de manera asíncrona però immediata:

const port = Number(process.argv[2]) || Number(process.env.PORT) || 3000;
const servidor = crearServidor();

servidor.on('error', (error) => {
  const missatges = {
    EADDRINUSE: `El port ${port} esta ocupat. Prova: node src/servidor/arrencar.js 3001`,
    EACCES: `Sense permisos per al port ${port}. Fes-ne servir un >= 1024.`
  };
  console.error(`[servidor] ${missatges[error.code] ?? error.message}`);
  process.exitCode = 1;
});

servidor.listen(port, '127.0.0.1', () => {
  const { address, port } = servidor.address();
  console.error(`[servidor] escoltant a http://${address}:${port}`);
});

Amb el port 80 sense privilegis obtens EACCES; amb dues instàncies al 3000, la segona dóna EADDRINUSE. Sense l'oient, tots dos casos serien un abocament de pila amb Unhandled 'error' event.

Solució 3. Amb closeIdleConnections() veuràs que la petició lenta es completa i que el procés acaba un instant després. Sense ella, si abans has fet servir el navegador, el procés es queda penjat fins que expira el keep-alive (5 segons per defecte, server.keepAliveTimeout), o fins i tot fins al termini forçat de 10 segons si el navegador renova la connexió. És el símptoma exacte de "el meu servidor no tanca amb Ctrl+C", i ara ja saps que la culpa no és del teu codi, sinó d'una connexió ociosa que continua comptant.

Conclusió

Escena Viva ja és a la xarxa. I ho ha aconseguit sense conceptes nous: http.createServer torna un EventEmitter que emet request —tant se val si passes el gestor al constructor com si el registres amb on—, a més de connection, listening, close i aquest error que, si no l'escoltes, et tomba el procés igual que a la lliçó 02-05. listen(port, host, callback) és asíncron i el seu callback no és res més que un once('listening'); server.address() et dóna el port real, imprescindible quan demanes el port 0.

Tens el primer servidor real a src/servidor/servidor.js, amb la disciplina que mantindrem tot el mòdul: gestor asíncron des del minut u, .catch obligatori perquè una promesa rebutjada no et costi el procés, port configurable amb process.env.PORT i crearServidor() exportat sense arrencar. Saps provar-lo amb el navegador, amb curl -i —que ensenya les capçaleres— i amb node -e. Entens per què el procés ja no acaba tot sol: el sòcol a l'escolta manté el compte de referències del bucle d'esdeveniments, i unref() ho demostra apagant-lo. I saps arrencar-lo i aturar-lo bé: EADDRINUSE i EACCES capturats amb missatges útils, i un tancament ordenat amb SIGINT/SIGTERM, server.close() i closeIdleConnections(), perquè les connexions persistents no es tanquen soles.

El que fa el nostre servidor, però, és vergonyós: respon el mateix a /, a /esdeveniments i a /qualsevol-cosa, sempre amb un 200, sense mirar el mètode ni els paràmetres. És hora de llegir de veritat el que ens arriba i de construir amb cura el que tornem. A la lliçó següent, Gestió de Sol·licituds i Respostes, desmuntem req —que és un stream de lectura— i res —que és un stream d'escriptura—, aprenem a analitzar la URL sense tallar cadenes a mà, repassem els codis d'estat que farà servir el curs sencer i construïm src/servidor/respostes.js amb la taula que tradueix els nostres error.codi de domini a estats HTTP.

Curs de Node.js: De Principiant a Avançat

Mòdul 1: Introducció a Node.js

Mòdul 2: Conceptes Bàsics

Mòdul 3: Sistema de Fitxers i E/S

Mòdul 4: HTTP i Servidors Web

Mòdul 5: NPM i Gestió de Paquets

Mòdul 6: Framework Express.js

Mòdul 7: Bases de Dades i ORMs

Mòdul 8: Autenticació i Autorització

Mòdul 9: Proves i Depuració

Mòdul 10: Temes Avançats

Mòdul 11: Desplegament i DevOps

Mòdul 12: Projectes del Món Real

© Copyright 2026. Tots els drets reservats