Mensajería instantánea con JavaScript y Socket.io

En éste artículo aprenderás a crear un sistema de mensajería instantánea con JavaScript y Socket.io para implementar un chat web.

https://socket.io/ Bidirectional and low-latency communication for every platform

Para entender bien cómo se implementa ésta librería, he preparado un ejercicio práctico. En la siguiente imagen se muestra el resultado al que llegaremos.

Mensajería instantánea con JavaScript y Socket.io
Resultado final de la práctica que realizaremos.

¿Si no tienes tiempo para leer la introducción? Salta directamente al tutorial para crear la aplicación

Las tecnologías detrás de la comunicación en tiempo real

Empezaré el artículo de hoy planteando una pregunta, ¿Cómo crees que están desarrolladas las aplicaciones como WhatsApp, Telegram o Discord?

En concreto, me refiero a cómo implementan la solución para que dos o más clientes intercambien información sin apenas latencia de tiempo.

Dicho de otro modo, ¿Cómo establecen una comunicación bidireccional en tiempo real en un entorno web?

Hoy te revelaré qué tecnologías se esconden detrás de aplicaciones con estas características. De hecho, al final de este artículo, tú mismo habrás creado un chat de mensajería instantánea con JavaScript y la librería Socket.io.

La idea de plantear este ejercicio me vino a la cabeza, tras finalizar el artículo de hace unas semanas.

Crea un botón para copiar directamente al portapapeles

En su momento, generamos la interfaz gráfica de una aplicación de mensajería instantánea. Pero para el propósito de entonces, no fué necesario hacerla funcional. 

Sin embargo, no quería dejar pasar la oportunidad de hacerlo, así que me decidí a crear este tutorial. 

En los siguientes párrafos veremos cómo la librería Socket.io, conjuntamente con otros recursos, nos va ayudar a conseguirlo.

Antes de entrar en materia, es necesario entender el contexto tecnológico en el que se enmarcan este tipo de aplicaciones web.

Por su naturaleza, tendremos que resolver necesidades técnicas tanto en el ámbito del frontend, como en el del backend. De modo que se tratará de un desarrollo «fullstack» donde trabajaremos con términos de ambos lados. 

Para ayudarte a entender en qué punto interviene cada concepto, he preparado el siguiente esquema. En él podrás ver dónde se ubican las tecnologías que vayamos mencionando.

Esquema Socket.io + ExpressJs
Esquema Socket.io + ExpressJs

Para que varios usuarios entren en contacto, los navegadores deben establecer una conexión con un servidor Express construido con NodeJS. En ese punto, una capa intermedia (o middleware) manejada por la librería Socket.io, se encargará de abrir y mantener un canal de comunicación.

A nivel técnico, Socket.io utiliza websockets, un protocolo “peer-to-peer” integrado en los  navegadores.

Establecer una comunicación mediante websockets consiste, a grandes rasgos, en abrir un canal de comunicación directo entre dos clientes. Una vez fijada esta conexión, eventualmente se intercambiarán información en forma de eventos

Por un lado, una aplicación emite eventos que suelen ir acompañados de datos estructurados como JSONs. Por el otro, las aplicaciones destino capturan el evento mandado, y llaman a una función de respuesta para trabajar con el objeto JSON recibido.

Entonces, para qué inicialmente dos navegadores establezcan una comunicación de este tipo, es necesario que previamente exista un nodo servidor que haga de puente entre ellos.

No te preocupes, en seguida veremos cómo funciona toda esta vaina.

Crear un sistema de mensajería instantánea con JavaScript y Socket.io

A pesar de que el caso práctico que veremos hoy consiste en crear un chat, la realidad es que vamos a sentar las bases para construir cualquier proyecto de comunicación a tiempo real.

Para no perderte en ningún punto, es recomendable que tengas a mano el código del proyecto acabado. Lo encontrarás en el enlace al repositorio que te dejo a continuación.

https://github.com/Danivalldo/libreriasjs/tree/master/socket-io-chat

He dividido el tutorial en dos partes

En la primera, nos centraremos en preparar el servidor NodeJS con Express y Socket.io.

En la segunda, crearemos la aplicación cliente utilizando React para construir la UI.

Tan solo queda que te prepares un buen café, y empezaremos a realizar la práctica. ¿Listo? Pues vamos allá, que queda mucho por delante.

Crear un servidor NodeJS con Express y Socket.io

A pesar de que comenzaremos creando el Backend, vamos a preparar el entorno de trabajo mediante el siguiente comando

npx create-react-app

Si tienes experiencia con React, ya habrás identificado que se trata de la instrucción para crear un proyecto nuevo con ReactJs.

Éste entorno está basado en NodeJs, de modo que nos resultará útil para el desarrollo de ambas partes.

Una vez se complete el proceso de instalación crearemos un directorio nuevo llamado server, en él incluiremos un archivo “index.js” vacío. De momento, ignoramos el resto de archivos y directorios.

Para crear el servidor será necesario instalar un conjunto de dependencias. En el siguiente listado te indico cuales son, y qué propósito tienen.

  • Socket.io. Por supuesto vamos a necesitar el recurso encargado de manejar las conexiones entre clientes.
  • Express. Con ésta librería se simplifica enormemente la creación de un servidor web.
  • Nodemon. Aunque esta herramienta es opcional, recomiendo ampliamente su instalación. Gracias a Nodemon, podrás automatizar el reinicio del servidor cada vez que se aplique un cambio. Más adelante te daré más detalles.
  • Dotenv. En ocasiones es útil trabajar con variables de entorno. Dotenv nos facilitará la carga del archivo .env en el servidor.

Para instalar todos estos paquetes se debe ejecutar el siguiente comando:

npm install express socket.io dotenv nodemon

Aprovecha para darle unos sorbos a tu café, mientras se instalan los archivos en el directorio node_modules.

Antes de empezar a programar el servidor, ampliaremos el documento package.json. La idea es agregar algunos scripts adicionales.

En la propiedad “scripts” añadiremos estas instrucciones:

"server": "node ./server/index.js"

Se encargará de ejecutar el servidor que programaremos en breve.

"server-dev": "nodemon --inspect ./server/index.js"

Adicionalmente al primer script, añadiremos un segundo con la misma funcionalidad. No obstante, éste otro está pensado para usarlo durante la fase de desarrollo. Por ese motivo utilizaremos Nodemon, y acompañaremos la instrucción con el “flag” –inspect.

Tal y como te he comentado antes, Nodemon reiniciará el servidor cada vez que apliquemos un cambio en el código. Por otra parte, la opción “–inspect” nos va permitir debugar cualquier problema directamente en una consola del navegador Chrome.

También añadiremos la siguiente instrucción:

"deploy": "npm run build && npm run server"

Ésta integra en una sola ejecución el proceso de compilar la aplicación React, e iniciar el servidor web.

Con esto estamos listos para empezar a escribir el código del servidor con NodeJs.

Abrimos el archivo index.js de dentro del directorio server, y empezamos importando cuatro dependencias.

require("dotenv").config();
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");

Tras hacerlo, creamos una constante “app” a partir de “express”. Seguidamente aprovecharemos la variable “app” para crear un servidor local mediante el método “createServer”, del módulo “http” de NodeJS.

Esta variable actuará de primer argumento en el momento de instanciar el servidor de websockets de Socket.io. Llamaremos al objeto instanciado “io”.

La clase Server de Socket.io, admite un segundo parámetro opcional. En él podemos definir políticas de CORS, especificando el origen de las peticiones, y los métodos válidos. En mi caso, definiré el origen, en una variable, dentro de un archivo .env.

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: process.env.CLIENT_HOST,
    methods: ["GET", "POST"],
  },
});

Con una sencilla instrucción, pediremos a Express que utilice el directorio “build” como raíz del servidor web estático. 

Utilizaremos el directorio “build” porque es el que se genera tras compilar la aplicación React. De este modo, cuando se ejecute el comando “deploy”, el entorno generará la aplicación web en ese directorio, y el servidor la servirá como raíz del dominio.

app.use(express.static("build"));

Cuando decía que Express simplifica extremadamente el proceso de creación de un servidor, me refería precisamente a cosas como ésta.

A continuación, preparamos la puesta en marcha del servidor, escuchando un puerto determinado. Una vez más, definiremos ésta variable en el archivo .env

server.listen(process.env.SOCKET_PORT, () => {
  console.log(`listening on *:${process.env.SOCKET_PORT}`);
});

Ya solo queda fijar los criterios a seguir para las conexiones de “io”. Primero, declaramos una variable donde guardaremos el número de usuarios conectados. 

let users = 0;

Seguidamente, describimos la lógica a seguir cuando el servidor detecta una nueva conexión vía websockets.

La API de Socket.io funciona en gran medida a través de la escucha o publicación de eventos, y funciones de respuesta asociadas.

Y precisamente, el primer evento que debemos captar es “connection”. Para detectarlo, se utiliza el método “on” del objeto “io”. En su función de respuesta obtendremos el objeto “socket” como argumento.

Este objeto representa la conexión establecida por el cliente. 

La primera acción a realizar dentro de la función, es actualizar el número de usuarios activos.

io.on("connection", (socket) => {
  console.log("a user connected!");
  users++;
});

A través del método “emit” informaremos a todos los clientes conectados de la llegada de un nuevo usuario, incluido el propio “socket” que acaba de llegar. 

El método “emit” acepta dos parámetros, el primero es el nombre del evento, y el segundo un objeto JSON con los datos que lo acompañan. De tal modo que enviaremos un evento llamado “USER_CONNECTED”, y lo acompañaremos del identificador del nuevo cliente, así como del número de “users” actualizado.

io.emit("USER_CONNECTED", {
  id: socket.id,
  totalUsers: users,
});

Desde la parte cliente, cada usuario conectado podrá emitir mensajes. Por ese motivo, el siguiente paso consiste en preparar el “socket” para que pueda “escuchar” la llegada del evento “MESSAGE” a través de su canal.

Para capturarlo utilizaremos el método “.on()” acompañado del nombre del evento y de la función de respuesta con los datos del mensaje como único parámetro. En el cuerpo de esta función emitimos otro evento con el mismo nombre, y con una versión ampliada de los datos. En concreto, añadiremos el id del usuario emisor.

Llamar al método “emit” des del “socket”, procediéndose de la propiedad “broadcast”, nos va a permitir mandar el evento a todos los clientes, menos al propio emisor.

socket.on("MESSAGE", (data) => {
  socket.broadcast.emit("MESSAGE", {
    ...data,
    userId: socket.id,
  });
});

Por último solo queda gestionar qué sucede cuando un cliente se desconecta. Atendemos al evento “disconnect”, y en su “callback” actualizamos en número de usuarios, y mandamos el evento “USER_DISCONNECTED” a modo de broadcast. Los datos que acompañarán esta acción serán, de nuevo, el id del usuario desconectado, y el número actual de conexiones abiertas.

socket.on("disconnect", () => {
  console.log("user disconnected");
  users--;
  socket.broadcast.emit("USER_DISCONNECTED", {
    id: socket.id,
    totalUsers: users,
  });
});

Con esto damos por finalizada la programación de la parte servidora de nuestra aplicación de mensajería instantánea con JavaScript y Socket.io.

Antes de entrar de lleno en la segunda parte de ésta guía, es necesario preparar el archivo .env en la raíz del proyecto. En ese archivo añadimos tanto las constantes que hemos declarado para el servidor, como algunas nuevas de cliente.

SOCKET_PORT=9000
CLIENT_HOST=http://localhost:3000
REACT_APP_SOCKET_HOST=http://localhost:9000

Para asegurarte de que llegado a este punto está todo correcto, puedes lanzar la siguiente instrucción en tu CLI.

npm run server-dev

Deberías obtener una respuesta parecida a esta:

CLI servidor express en marcha
CLI servidor express en marcha

Crear una app cliente con React y Socket.io-client

Ahora sí, a partir de este punto, nos centraremos en crear la aplicación cliente. Como dije, vamos a aprovechar los estilos creados en el anterior post, de modo que no me extenderé mucho explicando el código SCSS.

Utilizar React es opcional, siéntete libre de usar la librería que más cómoda te resulte. Por otra parte, si decides seguir el tutorial tal y como lo he planteado, pero no tienes mucha experiencia integrando recursos de terceros con React, te recomiendo que le dediques unos minutos a este otro artículo.

Cómo integrar cualquier librería JavaScript con React

No es imprescindible leerlo, pero hacerlo te puede ayudar a comprender ciertas estratégias que aplicaremos en esta segunda fase.

Iniciaremos esta etapa de la guía instalando nuevas dependencias necesarias. A través del siguiente comando, añadiremos a nuestro proyecto la librería socket.io-client y la librería dayjs.

npm install socket.io-client dayjs

Tal como su nombre sugiere, “socket.io-client” es la versión para navegador de Socket.io. A partir de ella, crearemos un servicio que nos permitirá controlar las conexiones que nuestra aplicación establezca.

Dayjs, es una biblioteca realmente útil para manejar “datetimes”. Si quieres profundizar en ella, en su momento le dedicamos un pequeño análisis.

Controlar fechas con DayJS

Entonces, dentro de “src” generamos un directorio nuevo llamado “services”, allí creamos un archivo JavaScript llamado “SocketCtrl.js”.

En éste documento importamos la librería que acabamos de instalar.

import { io } from "socket.io-client";

Seguidamente, creamos una clase llamada SocketCtrl y la exportamos justo al final.

class SocketCtrl {
}
export default SocketCtrl;

El constructor de esta clase, sencillamente preparará una propiedad llamada socket. Tal y como veremos a continuación, declararemos qué guardar en ella en el método “connect”.

constructor() {
  this.socket = undefined;
}

El método “connect” recibirá como único argumento una función de callback opcional. Comprobaremos si la propiedad socket ya se ha definido. De ser así, trataremos de establecer conexión con el servidor a través del endpoint definido en el archivo .env. usando la función “io”.

Cerraremos el método, añadiendo un “listener” que actuará cuando se complete la conexión. En la función asignada, llamaremos a “onConnected” (en caso de que exista) pasando el objeto socket.

connect(onConnected) {
  if (this.socket) {
    return;
  }
  this.socket = io(process.env.REACT_APP_SOCKET_HOST);
  this.socket.on("connect", () => {
    if (typeof onConnected === "function") {
      onConnected(this.socket);
    }
  });
}

Los dos métodos siguientes replican el funcionamiento de escuchar y emitir eventos del socket. No obstante, solo se ejecutarán si “this.socket” ha sido definido previamente.

emit(key, data) {
  if (!this.socket) {
    return;
  }
  this.socket.emit(key, data);
}
on(key, cb) {
  if (!this.socket) {
    return;
  }
  this.socket.on(key, cb);
}

Como ves, los métodos son idénticos a los que hemos visto en la parte del servidor. En este caso, solo manejaremos el “socket” del navegador.

La última función del servicio es “destroy()”. Allí cerramos la conexión en caso de que exista, y seteamos de nuevo la propiedad como al inicio.

destroy() {
  if (!this.socket) {
    return;
  }
  if (this.socket.connected) {
    this.socket.disconnect();
  }
  this.socket = undefined;
}

En este punto ya podríamos pasar a construir el componente que haga uso de la clase que acabamos de generar. Sin embargo, en esta ocasión vamos a agregar una capa de abstracción adicional creando un “custom hook”.

Separar la actualización del componente de la lógica de conexión, nos va a permitir una mayor escalabilidad en nuestro proyecto. En seguida entenderás porqué.

Crearemos el “hook” personalizado dentro de un directorio nuevo llamado “hooks”. Nombraremos el archivo como “useSocketCtrl.js” para respetar la convención que establece React.

Al inicio de este archivo, importamos una serie de funciones de React, la librería “dayjs” y por supuesto, el servicio SocketCtrl.

import { useState, useEffect, useMemo, useCallback } from "react";
import dayjs from "dayjs";
import SocketCtrl from "../services/SocketCtrl";

Declaramos la función useSocketCtrl sin dependencias, y la exportamos justo al final.

const useSocketCtrl = () => {
};
export default useSocketCtrl;

Dentro de la función declaramos los tres estados siguientes:

  • messages: Guardará el conjunto de mensajes que se vayan listando en la aplicación. Inicialmente será una array vacía.
  • socketId: Este estado nos resultará útil para mostrar el id asignado por Socket.io a nuestro cliente.
  • totalUsers: Ésta variable nos permitirá mostrar al usuario el número de clientes conectados al servidor

Por supuesto, declaramos cada una de estas variables utilizando la función “useState” de React.

const [messages, setMessages] = useState([]);
const [socketId, setSocketId] = useState(null);
const [totalUsers, setTotalUsers] = useState(1);

Después instanciamos la clase SocketCtrl y la guardamos bajo el nombre de “socketCtrl”. Es importante hacer uso de la función useMemo, ya que este objeto sólo se debe crear una única vez.

const socketCtrl = useMemo(() => {
  return new SocketCtrl();
}, []);

Acto seguido utilizaremos “useEffect” sin dependencias para establecer la conexión, agregar los “listeners” de los eventos, y preparar la destrucción de la conexión.

Utilizaremos el método “connect” de nuestro servicio, para iniciar la comunicación con el servidor. En la llamada de retorno obtendremos el objeto socket, en ese momento aprovecharemos para setear el valor de socketId.

useEffect(() => {
  socketCtrl.connect((socket) => {
    setSocketId(socket.id);
  });
}, []);

Mediante el método “on” prepararemos la escucha de los posibles eventos que puedan llegar del servidor.

El primero será “USER_CONNECTED”, a partir de la información del usuario conectado, añadiremos un mensaje nuevo al array “messages”.

Si el identificador coincide con el del usuario de la aplicación web, significa que hemos capturado nuestra propia conexión. Por consiguiente, añadiremos un mensaje de bienvenida. Por el contrario, informaremos en el mensaje, de la llegada de un usuario nuevo en la sala.

La estructura JSON de cada mensaje responderá a las siguientes propiedades:

  • id: Identificador único del mensaje. Está compuesto por una combinación del id de cliente + el datetime.
  • time: Como ves, utilizaremos “dayjs” para formatear correctamente la hora del mensaje.
  • message: Corresponde, evidentemente, al mensaje a mostrar.
  • userName: Nombre del autor del mensaje.
  • position: Puede ser “l” o “r”, para posicionar el mensaje a la izquierda (left) o a la derecha (right) del chat.

“EnterUser” también provee de la información del total de usuarios, así que aprovechamos para actualizar ese estado.

socketCtrl.on("USER_CONNECTED", (enterUser) => {
  const time = Date.now();
  setMessages((prevMessages) => {
    return [
      ...prevMessages,
      socketCtrl.socket.id === enterUser.id
        ? {
            id: `i-copy-u-${time}`,
            time: dayjs(time).format("HH:mm"),
            message: `Bienvenido a iCopyU!, se respetuoso con todo el mundo`,
            userName: "iCopyU!",
            position: "l",
          }
        : {
            id: `${enterUser.id}-${time}`,
            time: dayjs(time).format("HH:mm"),
            message: `El usuario ${enterUser.id} ha entrado en la sala`,
            userName: "iCopyU!",
            position: "l",
          },
    ];
  });
  setTotalUsers(enterUser.totalUsers);
});

De forma muy parecida, capturamos el evento “USER_DISCONNECTED”. Como verás, el código es prácticamente idéntico, solo que adaptando el mensaje infromativo.

socketCtrl.on("USER_DISCONNECTED", (goneUser) => {
  const time = Date.now();
  setMessages((prevMessages) => {
    return [
      ...prevMessages,
      {
        id: `${goneUser.id}-${time}`,
        time: dayjs(time).format("HH:mm"),
        message: `El usuario ${goneUser.id} ha dejado la sala`,
        userName: "iCopyU!",
        position: "l",
      },
    ];
  });
  setTotalUsers(goneUser.totalUsers);
});

El último evento a detectar es “MESSAGE”. Éste llegará cuando un usuario de la sala envíe un mensaje. Su instrucción es muy parecida a las anteriores, tras detectar el evento, ampliamos el estado “messages” con nueva información.

socketCtrl.on("MESSAGE", (messageData) => {
  const time = Date.now();
  setMessages((prevMessages) => {
    return [
      ...prevMessages,
      {
        id: `${messageData.userId}-${time}`,
        time: dayjs(messageData.time).format("HH:mm"),
        message: messageData.message,
        userName: messageData.userId,
        position: "l",
      },
    ];
  });
});

Concluímos el useEffect, devolviendo una función para cerrar la conexión y limpiar el servicio con el método “destroy”.

return () => {
  socketCtrl.destroy();
};

Naturalmente, en el “custom hook” también prepararemos una función para emitir y guardar un mensaje nuevo.

Llamaremos a esta función “sendMessage”, y nos serviremos del recurso “useCallback” para declararla de forma óptima. Las dependencias de ésta serán “socketId” y “socketCtrl”.

En el cuerpo de la función incluimos un mensaje nuevo en la interfaz, y emitimos un evento mediante el método “emit”. Deberemos acompañar la llamada al método con los argumentos correspondientes.

const sendMessage = useCallback(
  (message) => {
    const time = Date.now();
    setMessages((prevMessages) => {
      return [
        ...prevMessages,
        {
          id: `${socketId}-${time}`,
          time: dayjs(time).format("HH:mm"),
          message,
          userName: socketId,
          position: "r",
        },
      ];
    });
    socketCtrl.emit("MESSAGE", {
      time,
      message,
    });
  },
  [socketId, socketCtrl]
);

Finalmente, cerramos el “custom hook” devolviendo todos los estados y funciones necesarios.

return { messages, socketId, totalUsers, sendMessage };

Antes de integrar este hook en nuestra app, prepararemos un par de componentes simples.

Añadimos un directorio nuevo, llamado components. Dentro creamos dos directorios más, “ConversationBubble” y “TotalUsersBadge”. Dentro de cada uno generamos un archivo index.js y un archivo de estilos SCSS.

El componente ConversationBubble responderá al siguiente código JSX.

import React from "react";
import "./conversarion-bubble.scss";
const ConversationBubble = ({ position = "l", userName, message, time }) => {
  return (
    <div className={`conversation-bubble ${position}`}>
      <div className="wrapper-bubble">
        <span className="name">{userName}</span>
        <span className="content">{message}</span>
        <span className="time">{time}</span>
      </div>
    </div>
  );
};
export default ConversationBubble;

Tal y como se aprecia, admite propiedades relativas a la posición en el listado del chat, el userName, el mensaje y la hora. El layout HTML que devuelve, se encarga de estructurar y añadir los estilos necesarios.

El componente “TotalUsersBadge”, es más sencillo todavía. Sólo recibe “totalUsers”, y también conecta el dato con los estilos definidos en el archivo SCSS.

import React from "react";
import "./totalusers-badge.scss";
const TotalUsersBadge = ({ totalUsers }) => {
  return (
    <div className="total-users-badge-container">
      <span className="label">Usuarios conectados:</span>{" "}
      <span className="number">{totalUsers}</span>
    </div>
  );
};
export default TotalUsersBadge;

Finalmente editaremos el componente App, para incluir todos los recursos preparados hasta el momento.

Inicialmente, importamos el siguiente material.

import { useEffect } from "react";
import ConversationBubble from "./components/ConversationBubble";
import TotalUsersBadge from "./components/TotalUsersBadge";
import useSocketCtrl from "./hooks/useSocketCtrl";
import Logo from "./resources/logo_copy_u.png";
import "./App.scss";

Dentro de la función del componente, comenzamos extrayendo los métodos y propiedades del hook que preparamos.

const { messages, socketId, totalUsers, sendMessage } = useSocketCtrl();

Preparamos un useEffect para que actualice la posición del “scroll” cada vez que la variable de estado “messages” cambie.

useEffect(() => {
  window.scrollTo(0, document.body.scrollHeight);
}, [messages]);

Declaramos una función llamada “handleKeyDown”. Ésta función nos permitirá detectar si el usuario ha presionado la tecla de retorno en un campo de texto. De ser así, enviará un mensaje nuevo, y vaciará el campo de texto.

const handleKeyDown = (e) => {
  const message = e.target.value;
  if (e.keyCode === 13 && message) {
    sendMessage(message);
    e.preventDefault();
    e.target.value = "";
  }
};

Terminamos “App” devolviendo el siguiente código JSX. Éste layout se compondrá de una cabecera que muestra el total de usuarios conectados. Un listado de los mensajes recibidos y enviados. Y una caja de texto para escribir nuevos mensajes vinculada a “handleKeyDown”.

return (
  <div className="chat-container">
    <div className="container-logo">
      <img src={Logo} alt="" className="logo-app" />
      <TotalUsersBadge totalUsers={totalUsers} />
    </div>
    {messages.map((message) => {
      return (
        <ConversationBubble
          key={message.id}
          userName={message.userName}
          message={message.message}
          time={message.time}
          position={message.position}
        />
      );
    })}
    <div className="chat-box__container">
      <textarea
        name=""
        id=""
        cols="30"
        rows="10"
        placeholder={`Tu nombre de usuario es ${socketId}. Escribe algo aquí`}
        onKeyDown={handleKeyDown}
      ></textarea>
    </div>
  </div>
);

Si has llegado hasta aquí solo me queda darte la enhorabuena! Has conseguido crear un sistema de mensajería instantánea con JavaScript y Socket.io.

Para poner en marcha la aplicación en un entorno local, solo tienes que ejecutar dos comandos en ventanas independientes de tu CLI.

En la primera ventana ejecuta el script para iniciar el servidor local, si aún no lo habías hecho.

npm run server-dev

En la segunda, ejecuta el script para iniciar el proyecto React.

npm run start

Para corroborar que el sistema de mensajería funciona, puedes abrir múltiples pestañas en tu navegador, para simular distintos usuarios simultáneamente.

Pese a que acabas de crear una app de mensajería, ya habrás deducido que estos mismos principios técnicos se pueden aplicar otros conceptos. Por poner un ejemplo, mediante websockets también podrías crear una aplicación para jugar a ajedrez en línea. ¿Serías capaz?

Por si te animas a lograr este reto, te dejo un enlace al estudio de una librería que seguro te ayudará un montón con la parte frontend.

Programar una partida de ajedrez con JavaScript y ChessJs

Más documentación para ampliar tu aplicación de mensajería instantánea

Te agradezco que hayas llegado hasta este punto, espero de verdad que te haya sido de utilidad. 

Si has detectado alguna imprecisión y quieres comunicármelo, o sencillamente quieres estar al día de otros artículos que vaya publicando, te animo a que me sigas a través de Instagram o Twitter

Te dejo algunos enlaces adicionales, por si te apetece seguir aprendiendo más sobre NodeJS, Socket.io o Express.

Nos vemos pronto, un abrazo desarrolladores!

Deja un comentario