Un panel en directo con change streams
30 minIntermedio

Un panel en el navegador con el número de pedidos y los ingresos, que se actualiza en cuanto entra cada pedido.
Un panel que pregunta a la base cada pocos segundos hace consultas aunque nada cambie. Con un change stream, el servidor sabe cuándo entra un pedido y solo entonces recalcula los totales y se los manda a los navegadores conectados.
Lo que vas a construir
Un servidor de Node.js en el puerto 3000 que manda el número de pedidos y los ingresos con Server-Sent Events a una página HTML, que los muestra y se actualiza con cada pedido nuevo.
Requisitos
- Node.js 18 o posterior.
- Docker y
mongosh, para arrancar TeselaDB en tu máquina y mirar los datos.
Prepara el entorno
docker run -d --name teseladb -p 27017:27017 viuzi/teseladb:latest \
start --dbpath /data --insecure --insecure-allow-remote --bind_ip 0.0.0.0
export TESELADB_URI="mongodb://127.0.0.1:27017/shop"
mongosh "$TESELADB_URI" --quiet --eval 'db.runCommand({ ping: 1 }).ok'
mkdir panel && cd panel && npm init -y && npm install mongodbEl contenedor arranca un nodo de TeselaDB sin TLS ni usuarios, que basta para seguir la guía en tu máquina. Cuando mongosh responde 1, el nodo ya acepta conexiones. TESELADB_URI apunta a ese nodo y a la base shop; en un clúster de producción la cadena lleva TLS y usuario, como cuenta Conectar tu aplicación. La última línea crea la carpeta del programa con el paquete mongodb instalado.
1. El servidor
Guárdalo como dashboard.mjs. Sirve la página en / y, en /totals, cada navegador recibe los totales al conectarse y otra vez
con cada inserción en orders:
import { readFileSync } from "node:fs";
import { createServer } from "node:http";
import { MongoClient } from "mongodb";
const client = new MongoClient(process.env.TESELADB_URI);
const orders = client.db("shop").collection("orders");
const page = readFileSync(new URL("./dashboard.html", import.meta.url));
const listeners = new Set();
async function totals() {
const [row] = await orders
.aggregate([{ $group: { _id: null, orders: { $sum: 1 }, revenue: { $sum: "$total" } } }])
.toArray();
return { orders: row?.orders ?? 0, revenue: row?.revenue ?? 0 };
}
// The page at /, and at /totals one open connection per browser that gets the totals as Server-Sent Events.
createServer(async (req, res) => {
if (req.url !== "/totals") {
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
return res.end(page);
}
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache" });
res.write(`data: ${JSON.stringify(await totals())}\n\n`);
listeners.add(res);
req.on("close", () => listeners.delete(res));
}).listen(3000);
for await (const change of orders.watch([{ $match: { operationType: "insert" } }])) {
const data = `data: ${JSON.stringify(await totals())}\n\n`;
for (const res of listeners) res.write(data);
}2. La página
Guárdala como dashboard.html, junto a dashboard.mjs. EventSource mantiene la conexión abierta y se reconecta solo si se corta:
<p>Pedidos: <strong id="orders">0</strong> · Ingresos: <strong id="revenue">0</strong></p>
<script>
const events = new EventSource("/totals");
events.onmessage = (event) => {
const { orders, revenue } = JSON.parse(event.data);
document.getElementById("orders").textContent = orders;
document.getElementById("revenue").textContent = revenue;
};
</script>Probarlo
Arranca el servidor con node dashboard.mjs y abre http://localhost:3000 en el navegador, que muestra los totales
actuales en cuanto la página se conecta a /totals, y después, con la página todavía abierta, inserta un pedido desde mongosh:
db.getSiblingDB("shop").orders.insertOne({ total: 30 });Los dos números cambian en cuanto se confirma la inserción, sin recargar la página.
Limpiar
Para el servidor con Ctrl+C y cierra la página. No hay nada que borrar, porque el servidor no guarda nada en la base. Al final borra también el contenedor con la última orden: el nodo guarda los datos dentro, sin volumen, así que con él desaparece todo lo que escribiste durante la guía y el puerto 27017 queda libre.
docker rm -f teseladbSiguientes pasos
- Webhooks desde un change stream, para avisar a otros servicios.
- Change streams, con los filtros y las opciones de
watch.