Publicado
- 5 min read
Comunicación en tiempo real: Socket
Habrá ocasiones en las que necesitemos enviar o recibir mensajes en tiempo real, como por ejemplo en una aplicación de chat o cuando recibimos un pedido la cocina de un restaurante.
Una forma eficaz de gestionar estos mensajes sería a través de WebSocket.
Al final de la publicación podrás encontrar un link al repositorio con el ejemplo.
¿Qué es WebSocket?
WebSocket es un protocolo de red que establece cómo se comunican distintas redes. No quiero entrar mucho en detalle, pero al final consiste en la comunicación entre distintos sistemas de forma bidireccional.
¿Qué necesito para implementarlo?
Vamos a crear un ejemplo sencillo para demostrar cómo se implementaría. Utilizaremos una API en JavaScript que se comunicará a un frontend hecho con React. La interfaz se actualizará automáticamente cuando reciba mensajes desde la api.
Para gestionar los mensajes en tiempo real usaremos la librería Socket.io.
Antes de empezar, asegúrate de tener NodeJS instalado, yo tengo instalada la versión v20.6.1, aunque cualquiera debería valer.
Creamos la API con NodeJS
Lo primero que necesitamos es crear nuestra API, que tendrá un endpoint abierto y cuando reciba alguna llamada, se emitirá un mensaje para que lo reciba nuestro frontend.
Inicializamos el proyecto
Empezaremos por crear el proyecto e instalar las dependencias que vamos a necesitar, que serán:
- Express
- Socket.io
Yo utilizaré yarn, pero valdría también NPM si lo prefieres.
yarn init
Después de crearlo, instalamos las dependencias que necesitamos:
yarn add express socket.io
Creamos un archivo index.js y añadimos el siguiente contenido:
// index.js
import express from "express";
import { createServer } from "node:http";
import { Server } from "socket.io";
const PORT = 8080
const app = express();
const server = createServer(app);
const io = new Server(server);
io.on("connection", (socket) => {
console.log("a user connected");
});
app.get('/', (req, res) => {
return res.status(200).json({ ...req.query })
})
server.listen(PORT, () => {
console.log(`server running at http://localhost:${PORT}`);
});
Para correr la API, debemos correr el siguiente comando en la terminal:
node index.js
Ahora podemos hacer una llamada a la api. Al poner esta URL en el navegador, deberíamos recibir una respuesta:
http://localhost:8080/?name=gonzalo&age=29&country=Spain
La respuesta que deberíamos ver en pantalla sería algo así:
{
"name": "gonzalo",
"age": "29",
"country": "spain"
}
Bien, ya tenemos una API montada, corriendo y recibiendo llamadas, ahora nos toca montar el frontend.
Creamos nuestro frontend con React
Vamos a crear nuestro frontend con React y Vite. Será un frontend muy sencillito que lo único que hará será recibir los mensajes de Socket y mostrar alertas.
Inicializamos el proyecto:
npm create vite @latest frontend
Elegimos la opción React.
Luego nos preguntará si trabajaremos con JavaScript o TypeScript. Yo he elegido TypeScript. Una vez instalado todo, corremos los siguientes comandos:
cd frontend
npm install
npm run dev
Una vez tenemos el frontend corriendo, vamos a crear un hook para recibir y gestionar los mensajes que recibimos por socket. Pero antes, necesitamos instalar una dependencia:
npm install--save socket.io - client
Ya tenemos instalado todo lo que necesitamos, ahora vamos a crear nuestro hook en una carpeta nueva llamada hooks, en ella creamos el archivo socket.ts.
// src/hooks/socket.ts
import { useEffect, useState, useCallback } from "react";
import { type Socket, io } from "socket.io-client";
const path = `/ws/socket.io`;
const url = "http://localhost:8080";
const useSocket = () => {
const [socket, setSocket] = useState<Socket | null>(null);
const [isInitialized, setInitialized] = useState(false);
const initializeSocket = useCallback(() => {
if (socket && !isInitialized) {
socket.on("connect", () => {
console.log("Socket connected successfully with ID: ", socket.id);
setInitialized(true);
});
socket.on("connect_error", (error) => {
console.error("Socket connection error: ", error);
});
// We will handle socket messages here.
}
}, [socket, isInitialized]);
useEffect(() => {
if (!socket) {
const socketIO = io(url, { path });
setSocket(socketIO);
}
}, []);
useEffect(() => {
if (!isInitialized && socket) initializeSocket();
});
};
export default useSocket;
Este sería nuestro hook que gestionará los mensajes de Socket, aunque aún faltan un par de detalles. Ahora tenemos que llamar a este hook desde el componente en el que lo queramos usar. En nuestro caso, desde App.tsx, para que el socket esté funcionando en toda la aplicación.
// src/App.tsx
import useSocket from './hooks/socket'
function App() {
useSocket()
}
Configuración del Socket
Ahora tenemos que configurar el Socket tanto en la API como en el frontend.
Configuramos la API
En nuestro servidor tenemos que añadir la siguiente configuración cuando iniciamos el socket:
const io = new Server(server, {
path: `/ws`,
addTrailingSlash: false,
cors: {
origin:
process.env.NODE_ENV === "production" ? false : ["http://localhost:5173"],
},
});
De esta manera el CORS dejará de darnos problemas cuando nos intentamos conectar desde nuestro frontend.
Ahora que ya podemos conectarnos sin problema a nuestro frontend, tenemos que emitir los mensajes. En nuestro caso, lo que queremos es enviar al frontend los datos que nos lleguen a través del endpoint que hemos abierto.
Vamos a crear la función que emite los mensajes:
function emitEvent(event, payload) {
if (io) {
io.emit(event, payload);
} else {
logger.error("Socket has not been properly initialized");
}
}
Ya tenemos nuestra función, así que es el momento de llamarla cuando lo necesitemos:
app.get("/", (req, res) => {
emitEvent('test-socket', req.query)
return res.status(200).json({ ...req.query });
});
Nuestro endpoint ahora luce así. El evento emitido se llamará ‘test-socket’, pero puedes llamarlo como quieras. Intenta que sean nombres descriptivos como: new-order o meal-ready, esto será útil para organizarnos mejor a medida que nuestra aplicación crece.
Gestionamos los mensajes recibidos en el frontend
En nuestro hook, dentro de la función initializeSocket escucharemos a los mensajes de cada tipo y ejecutamos las funciones que corresponda dependiendo del tipo de mensaje que recibimos.
Para ello vamos a añadir el siguiente código:
const initializeSocket = useCallback(() => {
// ...
socket.on('test-socket', data => {
alert(JSON.stringify(data, null, 2))
})
// ...
}, [socket, isInitialized]);
Cuando recibamos un mensaje del tipo ‘test-socket’, simplemente se mostrará una alerta con el JSON que hemos enviado en nuestra llamada a la API.
Nos quedaría algo así:
Conclusiones
Socket es una herramienta genial para gestionar mensajes en tiempo real. Este ejemplo es muy sencillo, pero es fácilmente escalable. Espero que te sirva de ayuda.
Aquí te dejo el link al repositorio con el código completo.
¡Qué pases un buen día!
Un abrazo.