← Node.js: JavaScript fuera del navegador

Sesión 8 · Semana 3

Un fichero JSON como almacén

Hoy · Hoja de ruta

  1. 1. Aprende: Cómo se implementa un CRUD sobre un fichero sin corromperlo.
  2. 2. Haz: Termina el almacén con sus cuatro operaciones.
  3. 3. Comprueba: Un fallo a mitad de escritura no destruye tus datos.

Antes de empezar · 5 minutos, sin apuntes

  1. Si el programa se interrumpe mientras escribe el fichero, ¿qué queda?
  2. ¿Cómo asignas un identificador nuevo a un producto?
  3. ¿Qué diferencia hay entre modificar el objeto y guardar el fichero?

Las cuatro operaciones

export async function listar() {
  return leerProductos();
}

export async function obtener(id) {
  const productos = await leerProductos();
  return productos.find((p) => p.id === id) ?? null;
}

export async function crear(datos) {
  const productos = await leerProductos();
  const id = productos.reduce((max, p) => Math.max(max, p.id), 0) + 1;
  const nuevo = { id, ...datos, creadoEn: new Date().toISOString() };
  await guardarProductos([...productos, nuevo]);
  return nuevo;
}

export async function borrar(id) {
  const productos = await leerProductos();
  const restantes = productos.filter((p) => p.id !== id);
  if (restantes.length === productos.length) return false;
  await guardarProductos(restantes);
  return true;
}

Fíjate en dos cosas. El identificador no es la longitud del array, porque tras borrar el tercero de tres volvería a repetirse el 3. Y borrar devuelve si borró algo, para que quien llame pueda responder 404 en la UD6.

Escribir sin romper

Escribe en un temporal y renombra

Si el proceso muere a mitad de un writeFile, el fichero queda cortado: un JSON inválido, es decir, la pérdida completa de los datos. La reescritura íntegra es precisamente lo que realiza este almacén en cada operación.

La solución estándar: escribir en un fichero temporal y renombrarlo encima del bueno. Renombrar dentro del mismo disco es una operación indivisible, así que el fichero de datos siempre está completo, con el contenido viejo o con el nuevo.

import { writeFile, rename } from "node:fs/promises";

export async function guardarProductos(productos) {
  const temporal = `${RUTA_DATOS}.tmp`;
  await writeFile(temporal, JSON.stringify(productos, null, 2), "utf8");
  await rename(temporal, RUTA_DATOS);
}

Los límites de este almacén

Esto no es una base de datos, y hay que saber por qué

Cada operación lee el fichero entero y lo reescribe entero. Con doscientos productos resulta suficiente; con doscientos mil, no. Si además dos peticiones escriben de forma simultánea, la segunda sobrescribe el resultado de la primera, porque ambas leyeron la misma versión.

Para lo que hacemos aquí es suficiente, y evita instalar y configurar un motor de base de datos. Conviene, no obstante, saber nombrar sus dos límites —tamaño y concurrencia— porque son exactamente los problemas que resuelve la base de datos del módulo de servidor.

Tarea 8 · El almacén completo

  1. Implementa listar, obtener, crear, actualizar y borrar.
  2. Genera los identificadores sin repetirlos nunca.
  3. Escribe de forma segura, con temporal y renombrado.
  4. Guarda fechas de creación y modificación en formato ISO.
  5. Conecta el CLI con el almacén, de punta a punta.
  6. Provoca una interrupción a mitad de escritura y comprueba que los datos sobreviven.
Objetivo mínimoLas cinco operaciones y escritura segura.
Si lo tienesGuarda una copia del fichero antes de cada escritura.
RetoDemuestra con dos procesos simultáneos que una escritura pisa a la otra, y explica por qué.

Checkpoint · fin de la sesión 8

  • Las cinco operaciones funcionan de punta a punta.
  • Los identificadores nunca se repiten.
  • Escribes con temporal y renombrado.
  • Sabes nombrar los dos límites de este almacén.

Antes de cerrar · 2 minutos, sin mirar

  1. ¿Por qué el identificador no puede ser la longitud del array?
  2. ¿Qué protege escribir en un temporal y renombrar?
  3. ¿Qué dos límites tiene un fichero JSON como almacén?
Ver respuestas

1 · Porque tras un borrado se repetiría un identificador ya usado.

2 · Que una interrupción deje el fichero de datos cortado e ilegible.

3 · El tamaño, porque se reescribe entero, y la concurrencia, porque dos escrituras se pisan.