ksergio.com

I love coding

← Volver

Reflejar cambios de la base de datos en tiempo real

14/1/2024

gif-demo

Configurando el servidor

Estos es la estructura básica. Se omiten algunos archivos y directorios que configuran el servidor de Express.

src/
    index.js
    db.js
    events/
        mensajes.events.js		

Abajo se muestra como inicar mi servidor de ExpressJS junto con un servidor de SocketIO.

mensajesEvents(io) es una función de configuracion del servidor de SocketIO separada en un archivo que contiene los eventos que se van a escuchar y emitir. Se le debe pasar por parametros el servidor para agregar registrar estos eventos.

conectToMongo(io) es la función que conecta el cliente de Mongo. Requeire pasarle el servidor de SocketIO para agregar un watcher que nos dirá si hay algún cambio en la base de datos en tiempo real.

const express = require('express')
const {createServer} = require('http')
const {Server} = require('socket.io')

const  mensajesEvents  =  require('./events/mensajes.events')

const app = express()
const httpServer = createServer(app)
const io = new Server(httpServer, {})

mensajesEvents(io)

const startServer = async () => {
    await connectToMongo(io)
    httpServer.listen(3000, () =>  console.log('Server running at http://locahost:3000'))
}

startServer()

Esuchar y enviar eventos en el servidor

El archivo mensajes.events.js contiene la logica para registrar los eventos.

Esucha las conexiones, y cuando estas se produccen les agrega los eventos que va a tener: - socket.on(nombre_evento) espera este evento. Cuando lo recibe, ejecuta un callback. - socket.emit(nombre_evento) emite un evento desde el servidor al cliente con un payload.

const {db} = require('./../db')

module.exports = (io) => {
    io.sockets.on('connection', socket  => {
        socket.on('mensajes.request', async (req) => {
            const  data  =  await 
            db.collection('mensajes').find({}).toArray()
            socket.emit('mensajes.response',{data})
            })
    })
}

Aqui cuando el servidor recibe un evento llamado mensajes.request, lo que hace es buscar en la base de datos los mensajes y los envia al cliente con un evento llamado mensajes.response.

Esuchar y enviar eventos en el cliente

En el frontend tendríamos algo similar a esto

//- Plantilla escrita en pug

extends  ./_base
    block  content

    h1 Mensajes
    ul#mensajes

    //- Los archivos estáticos se configuran en express.
    //- /socket.io vienen por defecto 
    //- al servirse desde el mismo servidor.
    
    script(src="/socket.io/socket.io.js")
    script(src="/static/js/mensajes.socket.js")

El script con vanilla JS del frontend

const  socket  =  io()
const  ulMensajes  =  document.querySelector('#mensajes')

// Manipulación del DOM
const  poblarMensajes  = (datos) => {
    ulMensajes.innerHTML  =  null
    datos.forEach(dato=>{
        const  li  =  document.createElement('li')
        li.innerText  =  dato.texto
        ulMensajes.appendChild(li)
    })
}

socket.emit('mensajes.request',{})

socket.on('mensajes.response', (res) => {
    poblarMensajes(res.data)
})

socket.on('mensajes.change', () => {
    socket.emit('mensajes.request',{})
})

Enviar el evento mensajes.request al servidor le solicita que nos envie datos nuevos.

El servidor recibe esta petición, procesa esta información y al final nos envía otro evento mensajes.response que contiene los datos. En el cliente hemos configurado que al recibir este evento se llame el callback poblarMensajes que actualizará el DOM con los datos recibidos.

Cuando el cliente recibe un evento mensajes.change sabe que los datos están desfasados con el servidor, así que le vuelve a pedir los nuevos datos actualizados.

Ahora solo nos quedaría crear un metodo fácil para hacer saber que la base de datos se ha actualizado.

Escuchar cambios en la base de datos

En el caso del cliente de mongo nos proporciona a través de su API un watcher que monitoriza los cambios producidos y emite eventos. Así simplemente esperamos recibir este evento change y si este ocurre emitimos un nuevo evento a todos los sockets suscritos.

Este nuevo evento lo he llamado mensajes.change, a modo de señal para que el frontend sepa que sus datos no están sincronizados y delegando ya al cliente el como deba manejar esta situación.

const  connectToMongo  =  async (io) => {

    try {
        await  client.connect()
        // En este caso solo escuchamos cambios en la colección mensajes.
        const  mensajesChangeStream = db.collection('mensajes').watch()
    
        mensajesChangeStream.on('change', () => {
            io.emit('mensajes.change')
        })
        
    } catch (error) {
        throw  Error('No se ha podido conectar con la base de datos.')
    }
}

Con esto ya tendríamos el cliente sincronizado con la base de datos. Cualquier cambio en la base de datos, incluso si no pasa por el servidor será reflejado en el cliente automáticamente.