Un panel en directo con change streams
TeselaDB
Equipo de TeselaDB
30 minIntermedio
Muestra en el navegador el número de pedidos y los ingresos, actualizados 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 con el paquete
mongodb. - Un clúster de TeselaDB, un usuario con
readsobre la baseshopy su cadena en la variableTESELADB_URI.
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.
Siguientes pasos
- Webhooks desde un change stream, para avisar a otros servicios.
- Change streams, con los filtros y las opciones de
watch.