A la lliçó anterior vam fer servir exactament dues funcions de fs: readFile i writeFile. Amb elles Escena Viva ja llegeix el seu catàleg del disc, i això va resoldre el deute que arrossegàvem des del Mòdul 1. Però un fitxer solt és el cas més senzill del sistema de fitxers.
Les aplicacions reals treballen amb arbres de directoris: cal saber si una cosa existeix i què és, quant ocupa i quan es va modificar; crear carpetes que encara no existeixen, llistar el que hi ha a dins, esborrar el que és vell, moure fitxers de lloc i, de vegades, llegir un tros concret d'un fitxer enorme sense carregar-lo sencer. Aquesta lliçó recorre aquesta segona meitat de fs i acaba construint una cosa que Escena Viva ja necessita: un magatzem d'informes que organitzi les sortides per mes, guardi l'informe d'ocupació del dia, llisti el que hi ha amb la seva mida i la seva data, i purgui automàticament el que passi de certa antiguitat.
Contingut
stati l'objecteStatsstatdavant delstat: enllaços simbòlics- Recórrer directoris:
readdiriwithFileTypes - Crear i esborrar arbres:
mkdir,rm,rename,copyFile fs.constants, permisos iaccess- Descriptors de fitxer i l'API
FileHandle - Vigilar canvis:
watchdavant dewatchFile - El magatzem d'informes d'Escena Viva
stat i l'objecte Stats
stat i l'objecte Statsstat respon a la pregunta «què sap el sistema operatiu sobre aquesta ruta?». Torna un objecte Stats amb les metadades del fitxer: no el seu contingut, sinó la seva fitxa.
const fs = require('node:fs/promises');
const info = await fs.stat('dades/esdeveniments.json');
console.log(info.isFile()); // true
console.log(info.isDirectory()); // false
console.log(info.size); // 1893 (bytes)
console.log(info.mtime); // 2026-08-11T09:14:22.417Z (objecte Date)| Camp o mètode | Tipus | Què és |
|---|---|---|
isFile() |
booleà | És un fitxer regular |
isDirectory() |
booleà | És un directori |
isSymbolicLink() |
booleà | És un enllaç simbòlic (només té sentit amb lstat) |
size |
número | Mida en bytes. En un directori no és la suma del seu contingut |
mtime / mtimeMs |
Date / número |
Última modificació del contingut. El que faràs servir gairebé sempre |
ctime |
Date |
Últim canvi de metadades (permisos, propietari). No és «creació» |
birthtime / atime |
Date |
Creació / últim accés. Cap dels dos no és fiable a tots els sistemes |
mode |
número | Permisos i tipus, en bits |
Dues confusions clàssiques: ctime no significa creation time, sinó change time de les metadades —reanomenar o canviar permisos l'actualitza sense tocar el contingut—, i size en un directori no és el pes del que conté, sinó el del registre de directori mateix. Per saber quant ocupa una carpeta cal recórrer-la i sumar.
Com qualsevol operació de fs, stat falla amb ENOENT si la ruta no existeix. Això el converteix en la manera habitual —i correcta— de respondre a «això existeix i què és?» en una sola crida: embolcalla'l en un try/catch que torni null davant d'ENOENT i propagui qualsevol altre codi, tal com vam fer a la lliçó anterior amb llegirSiExisteix.
stat davant de lstat: enllaços simbòlics
stat davant de lstat: enllaços simbòlicsUn enllaç simbòlic és un fitxer que apunta a una altra ruta. La diferència entre les dues funcions és què fan en trobar-ne un:
| Funció | En topar amb un enllaç simbòlic |
|---|---|
stat |
El segueix i torna les dades de la destinació |
lstat |
No el segueix i torna les dades de l'enllaç mateix |
const desti = await fs.stat('informes/ultim.json'); // segueix l'enllac
console.log(desti.isFile(), desti.isSymbolicLink()); // true false
const enllac = await fs.lstat('informes/ultim.json'); // no el segueix
console.log(enllac.isFile(), enllac.isSymbolicLink()); // false trueLa conseqüència pràctica importa: isSymbolicLink() sempre torna false amb stat, perquè aleshores ja has saltat a la destinació; i si l'enllaç apunta a una cosa esborrada —un enllaç trencat—, stat falla amb ENOENT mentre que lstat funciona i et descriu l'enllaç. Regla: fes servir stat quan t'interessi el contingut (el normal) i lstat quan estiguis recorrent o esborrant un arbre, per no seguir enllaços sense voler i acabar fora del directori que et pensaves que estaves processant.
- Recórrer directoris:
readdir i withFileTypes
readdir i withFileTypesconst noms = await fs.readdir('informes');
console.log(noms);
// [ '2026-07', '2026-08', 'vendes-per-sessio.json' ]Per defecte readdir torna només noms, sense ruta i sense dir-te què és cada cosa. L'impuls natural és cridar stat sobre cadascun —for (const nom of await fs.readdir(dir)) { const info = await fs.stat(${dir}/${nom}); ... }—, i amb 5 entrades tant li fa; amb 50.000 són 50.000 crides al sistema extra.
L'opció withFileTypes ho resol: el sistema operatiu ja sap el tipus de cada entrada en llistar el directori, així que Node te'l torna de franc en forma d'objectes Dirent.
// EFICIENT: una sola crida, el tipus ve inclos.
const entrades = await fs.readdir('informes', { withFileTypes: true });
for (const entrada of entrades) {
// entrada.name es el nom; entrada.parentPath, el directori que el conte.
if (entrada.isDirectory()) console.log(`carpeta: ${entrada.name}`);
else if (entrada.isFile()) console.log(`fitxer: ${entrada.name}`);
}Un Dirent ofereix els mateixos predicats que Stats (isFile, isDirectory, isSymbolicLink) però no porta size ni mtime: si necessites mida o data, aleshores sí que toca stat. La regla és filtrar primer per tipus amb Dirent i fer stat només sobre el que t'interessa de debò. readdir accepta també recursive: true per recórrer l'arbre sencer d'una vegada, i no garanteix cap ordre: si necessites els informes ordenats per data, ordena'ls tu.
- Crear i esborrar arbres:
mkdir, rm, rename, copyFile
mkdir, rm, rename, copyFile// Crea l'arbre complet. Amb recursive, NO falla si ja existeix.
await fs.mkdir('informes/2026-08', { recursive: true });
// Esborra un arbre sencer. force evita l'ENOENT si ja no hi es.
await fs.rm('informes/2026-01', { recursive: true, force: true });
// Moure o reanomenar (atomic dins del mateix sistema de fitxers).
await fs.rename('informes/esborrany.json', 'informes/2026-08/ocupacio.json');
// Copiar sense sobreescriure la destinacio si ja existeix.
await fs.copyFile('dades/esdeveniments.json', 'dades/esdeveniments.copia.json', fs.constants.COPYFILE_EXCL);| Operació | Opcions clau | Comportament a recordar |
|---|---|---|
mkdir |
recursive: true |
Crea els pares que falten i no falla amb EEXIST. Sense això, sí que falla |
rm |
recursive, force |
recursive per a directoris amb contingut; force ignora que no existeixi |
rename |
— | Atòmic al mateix sistema de fitxers; falla amb EXDEV entre discs diferents |
copyFile |
COPYFILE_EXCL |
Per defecte sobreescriu la destinació sense avisar |
cp |
recursive: true |
Copia arbres sencers; l'equivalent de cp -r |
Tres detalls que estalvien depuracions. Primer: mkdir amb recursive és idempotent, així que cridar-lo abans d'escriure és la manera neta de garantir la destinació, sense comprovacions prèvies ni condicions de cursa. Segon: rm sense recursive sobre un directori falla amb ERR_FS_EISDIR; i rm amb force i recursive no pregunta res, de manera que un rm(rutaCalculada, {recursive: true, force: true}) amb la ruta mal calculada esborra el que li posis al davant —valida sempre la ruta abans—. Tercer: rename falla amb EXDEV si origen i destinació són en discs o volums diferents; allà cal copiar i esborrar.
fs.constants, permisos i access
fs.constants, permisos i accessfs.constants reuneix els valors numèrics que espera el sistema operatiu. Els d'ús diari són aquests:
| Constant | Per a què |
|---|---|
F_OK |
Existeix la ruta? |
R_OK / W_OK |
Puc llegir-la? Puc escriure-hi? |
X_OK |
Puc executar-la (o entrar al directori)? |
COPYFILE_EXCL |
copyFile falla si la destinació existeix |
O_RDONLY, O_WRONLY, O_CREAT, O_APPEND |
Modes d'obertura de baix nivell per a open |
const fs = require('node:fs/promises');
const { constants } = require('node:fs');
try {
await fs.access('informes', constants.W_OK);
} catch {
console.error('[informes] el directori no es escrivible per aquest proces');
}access no torna res: compleix o llança. I aquí convé recordar la lliçó anterior: fer-lo servir com a pas previ a una operació reintrodueix la condició de cursa TOCTOU. El seu ús legítim és diagnòstic, a l'arrencada, per donar un missatge clar («no puc escriure a informes/») en comptes d'un EACCES críptic vint minuts després.
Els permisos es canvien amb chmod en notació octal: await fs.chmod(ruta, 0o600) deixa un fitxer llegible i escrivible només pel seu propietari, que és l'apropiat per a dades personals de compradors (hi tornarem al Mòdul 11). A Windows el model de permisos és diferent i chmod només afecta el bit de només lectura.
- Descriptors de fitxer i l'API
FileHandle
FileHandlereadFile obre, llegeix sencer i tanca. Quan necessites més control —llegir només els primers bytes, escriure en una posició concreta, fer moltes operacions sobre el mateix fitxer sense reobrir-lo— treballes amb un descriptor de fitxer: un identificador que el sistema operatiu et lliura en obrir i que representa aquesta obertura concreta.
A node:fs/promises, open() torna un objecte FileHandle que embolcalla aquest descriptor:
// Llegeix nomes els primers bytes d'un fitxer, sense carregar-lo sencer.
async function llegirCapcalera(ruta, nombreDeBytes) {
const gestor = await fs.open(ruta, 'r');
try {
const desti = Buffer.alloc(nombreDeBytes);
// read(buffer, desplacamentEnBuffer, longitud, posicioEnFitxer)
const { bytesRead } = await gestor.read(desti, 0, nombreDeBytes, 0);
return desti.subarray(0, bytesRead);
} finally {
// SEMPRE. Encara que hi hagi error, encara que hi hagi return dins del try.
await gestor.close();
}
}El try/finally no és opcional. Cada descriptor obert consumeix una entrada d'una taula del sistema operatiu amb un límit per procés (normalment uns quants milers); si una funció obre fitxers i no els tanca, cada crida perd un descriptor i, després d'unes hores de producció, el procés comença a fallar amb EMFILE: too many open files en operacions que no hi tenen res a veure. I fixa't on va el close: al finally, no al final del try, perquè allà qualsevol error del read s'enduria el tancament per davant. És el mateix raonament que aplicaràs a les connexions de base de dades al Mòdul 7.
Mètode de FileHandle |
Què fa |
|---|---|
read(buffer, offset, length, position) |
Llegeix bytes en un Buffer des d'una posició |
write(buffer | string, ...) |
Escriu en una posició concreta |
readFile() / writeFile() / stat() |
Com les globals, però sobre el fitxer ja obert |
truncate(n) |
Retalla el fitxer a n bytes |
sync() |
Força l'abocament de la memòria cau del sistema al disc físic |
close() |
Allibera el descriptor |
sync() és la peça que completa l'escriptura atòmica de la lliçó anterior: escriure el temporal, sync() i només aleshores rename garanteix que el contingut és físicament al disc abans del canvi de nom.
- Vigilar canvis:
watch davant de watchFile
watch davant de watchFileReaccionar a canvis en un fitxer sona trivial i no ho és. Node ofereix dos mecanismes de caràcter oposat:
fs.watch |
fs.watchFile |
|
|---|---|---|
| Com funciona | Notificacions del sistema operatiu (inotify, FSEvents, ReadDirectoryChangesW) | Sondeig: stat cada N mil·lisegons |
| Cost | Molt baix | Constant, encara que no canviï res |
| Latència | Immediata | Fins a un interval complet (5 s per defecte) |
| Fiabilitat | Variable entre plataformes | Predictible a tot arreu |
| Directoris recursius | recursive: true (no a tots els sistemes) |
No |
fs.watch té mala fama merescuda: pot emetre dos esdeveniments per un sol desament (molts editors escriuen un temporal i el reanomenen), el camp eventType ('rename' o 'change') no sempre significa el que sembla, el nom del fitxer arriba null en algunes plataformes, i en xarxes o volums muntats de vegades no arriba res.
const { watch } = require('node:fs');
// Recarrega del cataleg quan canvia la llavor, amb antirebot.
let temporitzador = null;
const vigilant = watch('dades/esdeveniments.json', (tipusEsdeveniment) => {
clearTimeout(temporitzador); // agrupa la rafega en una sola recarrega
temporitzador = setTimeout(() => {
console.error(`[cataleg] ${tipusEsdeveniment}: recarregant llavor`);
obtenirCataleg({ recarregar: true }).catch((e) => console.error(e.message));
}, 200);
});
process.on('SIGINT', () => vigilant.close());El patró d'antirebot (debounce) amb setTimeout no és cap adorn: sense ell, un sol desament provoca dues o tres recàrregues. I observa el vigilant.close(): un vigilant manté el procés viu, així que cal tancar-lo perquè el programa pugui acabar. En projectes seriosos de vigilància, la recomanació honesta és fer servir una biblioteca com chokidar, que existeix precisament per normalitzar totes aquestes diferències.
- El magatzem d'informes d'Escena Viva
Ho ajuntem tot en un mòdul real. src/informes/ocupacio.js ja sap calcular resumirPerSala des del Mòdul 2; el que falta és on guardar aquests informes, com llistar-los i com evitar que creixin sense límit.
// src/informes/magatzem-informes.js
// Guarda, llista i purga els informes generats per Escena Viva.
// Estructura: informes/AAAA-MM/ocupacio-AAAA-MM-DD.json
const fs = require('node:fs/promises');
const DIRECTORI_BASE = 'informes';
// 2026-08-11T09:14:22.417Z -> { mes: '2026-08', dia: '2026-08-11' }
function particionarData(data = new Date()) {
const dia = data.toISOString().slice(0, 10);
return { mes: dia.slice(0, 7), dia };
}
// Guarda l'informe del dia. Si ja n'hi ha un, falla llevat que se sobreescrigui.
async function guardarInforme(contingut, { data = new Date(), sobreescriure = false } = {}) {
const { mes, dia } = particionarData(data);
const directori = `${DIRECTORI_BASE}/${mes}`;
const ruta = `${directori}/ocupacio-${dia}.json`;
// Idempotent: crea l'arbre si falta i no protesta si ja hi es.
await fs.mkdir(directori, { recursive: true });
try {
await fs.writeFile(ruta, JSON.stringify(contingut, null, 2), {
encoding: 'utf8',
flag: sobreescriure ? 'w' : 'wx' // wx: falla si ja existeix
});
} catch (error) {
if (error.code === 'EEXIST') {
const fallada = new Error(`Ja existeix l'informe de ${dia}. Fes servir sobreescriure per reemplacar-lo.`);
fallada.codi = 'INFORME_DUPLICAT';
throw fallada;
}
throw error;
}
return ruta;
}
// Noms de les carpetes mensuals existents. [] si no n'hi ha cap.
async function carpetesMensuals() {
try {
const entrades = await fs.readdir(DIRECTORI_BASE, { withFileTypes: true });
// Filtrem per tipus amb Dirent: encara no hem fet ni un sol stat.
return entrades.filter((e) => e.isDirectory()).map((e) => e.name).sort();
} catch (error) {
if (error.code === 'ENOENT') return []; // encara no se n'ha generat cap
throw error;
}
}
// Llista tots els informes amb la seva mida i la seva data de modificacio.
async function llistarInformes() {
const informes = [];
for (const mes of await carpetesMensuals()) {
const directori = `${DIRECTORI_BASE}/${mes}`;
const fitxers = await fs.readdir(directori, { withFileTypes: true });
for (const fitxer of fitxers) {
if (!fitxer.isFile() || !fitxer.name.endsWith('.json')) continue;
const ruta = `${directori}/${fitxer.name}`;
const info = await fs.stat(ruta); // stat nomes sobre el que ens interessa
informes.push({
mes,
fitxer: fitxer.name,
ruta,
midaBytes: info.size,
modificat: info.mtime.toISOString()
});
}
}
// readdir no garanteix ordre: l'imposem nosaltres.
return informes.sort((a, b) => a.ruta.localeCompare(b.ruta));
}
// Esborra les carpetes mensuals anteriors als ultims N mesos.
async function purgarInformes(mesosAConservar = 6, { simular = true } = {}) {
const limit = new Date();
limit.setMonth(limit.getMonth() - mesosAConservar);
const mesLimit = limit.toISOString().slice(0, 7);
const purgades = [];
for (const mes of await carpetesMensuals()) {
// Comparar 'AAAA-MM' com a text funciona: el format es ordenable.
if (mes >= mesLimit) continue;
const ruta = `${DIRECTORI_BASE}/${mes}`;
if (!simular) await fs.rm(ruta, { recursive: true, force: true });
purgades.push(ruta);
}
return purgades;
}
module.exports = { guardarInforme, llistarInformes, purgarInformes, DIRECTORI_BASE };Tres decisions de disseny mereixen comentari. El particionat per mes (informes/2026-08/) evita el problema clàssic de ficar cent mil fitxers en un sol directori, on qualsevol readdir es torna lent, i a més converteix la purga en un rm de carpeta sencera en comptes de mil esborrats solts. El flag: 'wx' per defecte fa que regenerar l'informe d'un dia ja guardat sigui un error explícit i no una sobreescriptura silenciosa: si de debò el vols, el demanes. I purgarInformes simula per defecte, perquè una funció que esborra recursivament ha d'exigir que li confirmis la intenció, no al revés. Fixa't també que les rutes es componen amb plantilles de text: funciona a Linux i macOS, però és exactament l'error que la lliçó següent corregirà amb path.join.
Errors Comuns i Consells
- Cridar
statsobre cada entrada dereaddir. Fes servirwithFileTypesi reservastatper quan necessitissizeomtime. - Interpretar
ctimecom a data de creació. És la data de l'últim canvi de metadades. Per a creació,birthtime—i només si el teu sistema la manté. mkdirsenserecursivefalla ambEEXISTsi la carpeta ja existeix i ambENOENTsi falta un pare. Ambrecursive: truedesapareixen tots dos problemes.- Deixar descriptors oberts. Tot
open()necessita el seuclose()en unfinally. El símptoma tardà ésEMFILEen un lloc sense relació amb la fallada. copyFilesobreescriu per defecte (passa-lifs.constants.COPYFILE_EXCLsi no ho vols), ireaddirno garanteix cap ordre: ordena explícitament.- Consell: qualsevol funció que esborri ha de tenir mode simulació i fer-lo servir per defecte;
rmambrecursiveiforceno demana confirmació a ningú. I sifs.watchet dispara callbacks duplicats, no busquis la fallada al teu codi: és el comportament normal, aplica antirebot.
Exercicis
Exercici 1: informe d'espai ocupat
Escriu src/laboratori/mesurar-carpeta.js que rebi una ruta per process.argv i recorri l'arbre recursivament calculant: nombre total de fitxers, nombre de directoris, mida total en bytes i els cinc fitxers més grans. Ha de fer servir readdir amb withFileTypes, no seguir enllaços simbòlics (lstat) i mostrar el resultat amb console.table per stdout.
Exercici 2: generar i purgar informes
Escriu src/informes/generar.js que faci servir obtenirCataleg() i resumirPerSala() per construir l'informe del dia, el guardi amb guardarInforme(), mostri el llistat complet amb llistarInformes() i executi purgarInformes(6) en mode simulació, indicant què s'esborraria. Accepta --sobreescriure i --purgar per passar de la simulació a l'acció.
Exercici 3: rotació d'un fitxer de registre
Escriu una funció rotarRegistre(ruta, midaMaximaBytes) que, si el fitxer supera la mida indicada, el reanomeni a ruta.AAAA-MM-DDTHH-mm-ss i deixi l'original buit, conservant com a màxim les cinc rotacions més recents i esborrant la resta. Ha de funcionar encara que el fitxer no existeixi encara.
Solucions
Solució 1. El recorregut recursiu amb withFileTypes és el patró que veuràs una vegada i una altra:
async function recorrer(ruta, acumulat) {
const entrades = await fs.readdir(ruta, { withFileTypes: true });
for (const entrada of entrades) {
const fill = `${ruta}/${entrada.name}`;
// isSymbolicLink() sobre un Dirent no segueix l'enllac: es el que volem.
if (entrada.isSymbolicLink()) continue;
if (entrada.isDirectory()) {
acumulat.directoris += 1;
await recorrer(fill, acumulat);
} else if (entrada.isFile()) {
const info = await fs.lstat(fill);
acumulat.fitxers += 1;
acumulat.bytes += info.size;
acumulat.majors.push({ ruta: fill, bytes: info.size });
}
}
return acumulat;
}En acabar, majors.sort((a, b) => b.bytes - a.bytes).slice(0, 5) dona els cinc més grans. Saltar-se els enllaços simbòlics no és cap escrúpol teòric: un enllaç que apunti a un avantpassat converteix el recorregut en un bucle infinit.
Solució 2. El que importa és l'ordre de les operacions i que l'escriptura convisqui amb l'EEXIST:
const esdeveniments = await obtenirCataleg();
const resum = {
generatEl: new Date().toISOString(),
aforament: esdeveniments.reduce((t, e) => t + e.aforamentTotal, 0),
venudes: esdeveniments.reduce((t, e) => t + e.entradesVenudes, 0),
perSala: resumirPerSala(esdeveniments)
};
try {
const ruta = await guardarInforme(resum, { sobreescriure });
console.error(`[informes] guardat a ${ruta}`);
} catch (error) {
if (error.codi !== 'INFORME_DUPLICAT') throw error;
console.error(`[informes] ${error.message}`);
}
console.table(await llistarInformes());
const purgades = await purgarInformes(6, { simular: !purgar });
console.error(purgar
? `${purgades.length} carpetes esborrades.`
: `S'esborrarien ${purgades.length} carpetes: ${purgades.join(', ')}`);Solució 3. La rotació combina stat, rename i una purga per ordre invers:
async function rotarRegistre(ruta, midaMaximaBytes, rotacionsAConservar = 5) {
let info;
try {
info = await fs.stat(ruta);
} catch (error) {
if (error.code === 'ENOENT') return false; // res a rotar encara
throw error;
}
if (info.size < midaMaximaBytes) return false;
const marca = new Date().toISOString().replace(/[:.]/g, '-');
await fs.rename(ruta, `${ruta}.${marca}`);
await fs.writeFile(ruta, '', 'utf8'); // deixa l'original buit
const directori = ruta.slice(0, ruta.lastIndexOf('/')) || '.';
const base = ruta.slice(ruta.lastIndexOf('/') + 1);
const rotacions = (await fs.readdir(directori, { withFileTypes: true }))
.filter((e) => e.isFile() && e.name.startsWith(`${base}.`))
.map((e) => e.name)
.sort()
.reverse(); // la marca ISO ordena per data
for (const sobrant of rotacions.slice(rotacionsAConservar)) {
await fs.rm(`${directori}/${sobrant}`, { force: true });
}
return true;
}Dos detalls: els : i . de la marca ISO se substitueixen perquè no són vàlids en noms de fitxer a Windows, i ordenar alfabèticament les rotacions equival a ordenar-les per data perquè el format ISO està dissenyat per a això. El trossejat de la ruta amb lastIndexOf('/') és justament el que path.dirname i path.basename faran bé —i de manera portable— a la lliçó següent.
Conclusió
Ja coneixes la part de fs que va més enllà de llegir i escriure un fitxer. Saps consultar metadades amb stat i llegir un objecte Stats sense confondre ctime amb la data de creació; distingeixes stat de lstat i entens per què isSymbolicLink() només té sentit amb el segon; recorres directoris amb readdir i withFileTypes, evitant el stat per entrada que multiplica les crides al sistema; crees i destrueixes arbres amb mkdir({recursive}) —idempotent, i per això la manera neta de garantir una destinació— i amb rm({recursive, force}), que no pregunta res a ningú; mous amb rename sabent que falla amb EXDEV entre volums; i copies amb copyFile recordant que sobreescriu llevat de COPYFILE_EXCL.
Has vist a més els descriptors de fitxer i l'API FileHandle, amb la regla que no admet excepcions: tot open() porta el seu close() en un finally, o tard o d'hora arriba l'EMFILE. I coneixes els dos vigilants, watch i watchFile, amb els seus caràcters oposats i la necessitat d'antirebot. Escena Viva, per la seva banda, ha guanyat src/informes/magatzem-informes.js amb guardarInforme, llistarInformes i purgarInformes: els informes s'organitzen per mes, no se sobreescriuen per accident gràcies al flag: 'wx', es llisten amb la seva mida i la seva data, i es purguen en mode simulació llevat que confirmis el contrari.
Queda un cap solt, i és gros: totes les rutes d'aquest mòdul estan construïdes enganxant cadenes amb /. Funciona a la teva màquina i es trenca tan bon punt algú executi el projecte des d'un altre directori o a Windows. A Rutes Multiplataforma amb el Mòdul path veurem per què concatenar rutes és un error, la diferència exacta entre join i resolve, l'abisme entre process.cwd() i __dirname, com un ../../etc/passwd s'esmuny en una ruta construïda amb dades de l'usuari, i centralitzarem tota la configuració de rutes del projecte a src/config/rutes.js per refactoritzar el que hem escrit en aquestes dues lliçons.
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
