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()
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.
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.
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.