En este tutorial, crearemos una aplicación de chat utilizando sockets web. Los sockets web son realmente útiles cuando desea crear aplicaciones que requieren transferencia de datos en tiempo real.
Al final de este tutorial, podrá configurar su propio servidor de socket, enviar y recibir mensajes en tiempo real, almacenar datos en Redis e implementar su aplicación en render y Google Cloud Run.
Crearemos una aplicación de chat. Para ser breve, solo configuraremos el servidor. Puede utilizar su propio marco de interfaz de usuario y seguirlo.
En esta aplicación de chat, habrá salas y los usuarios podrán unirse a una sala y comenzar a chatear. Para simplificar todo, asumiremos que los nombres de usuario no son únicos. Sin embargo, cada sala solo puede tener un usuario con un nombre de usuario específico.
Primero necesitamos instalar las dependencias requeridas.
npm i express cors socket.io -D @types/node
Usaremos el módulo http para configurar nuestro servidor de socket. Dado que nuestra aplicación se ejecutará en la terminal, tendremos que permitir todos los orígenes.
import express from "express"; import cors from "cors" import { Server } from "socket.io"; import { createServer } from "http" const app = express(); const server = createServer(app); // create a socket server. const io = new Server(server, { cors: { origin: "*", credentials: true, } }); // listen to connections errors io.engine.on("connection_error", console.log) app.use(cors()) const PORT = 3000; server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Usaremos redis para almacenar nuestros mensajes junto con la información de la sala y del usuario. Puedes usar upstash redis (gratis). Cree una nueva instancia de Redis en su panel de control superior. Después de la creación, recibirá una URL de Redis que podrá utilizar para conectarse a su instancia de Redis.
Instala cualquier cliente redis de tu elección. Usaré ioredis.
npm i ioredis
A continuación, inicializaremos nuestro cliente Redis y lo conectaremos a nuestro servidor Redis usando la URL de conexión que obtuvimos.
/** /src/index.ts */ import { Redis } from "ioredis" if (!process.env.REDIS_URL) throw new Error("REDIS_URL env variable is not set"); const redis = new Redis(process.env.REDIS_URL); // listen to connection events. redis.on("connect", () => console.log("Redis connected")) redis.on("error", console.log)
Los usuarios pueden crear salas o unirse a salas existentes. Las habitaciones se identifican mediante identificadores de habitación únicos. Cada miembro tiene un nombre de usuario que es único dentro de una sala y no a nivel mundial.
Podemos realizar un seguimiento de todas las salas activas en nuestro servidor, almacenando sus identificadores de sala dentro de un conjunto de redis.
Para nuestro propósito, los nombres de usuario solo son únicos dentro de una sala. Entonces, los almacenamos en un conjunto junto con la identificación de la habitación. Esto garantiza que la combinación de la identificación de la sala junto con la identificación del miembro sea única a nivel mundial.
Podemos configurar un evento de socket para crear espacio. Cuando creamos una sala, también agregamos a la sala el miembro que solicitó su creación.
io.on("connection", () => { // ... socket.on("create:room", async (message) => { console.log("create:room", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 1) return socket.emit("error", { message: "Room already exist."}) const roomStatus = await redis.sadd("rooms", message.roomId) const memStatus = await redis.sadd("members", message.roomId "::" message.username) if (roomStatus === 0 || memStatus === 0) return socket.emit("error", { message: "Room creation failed." }) socket.join(message.roomId) io.sockets.in(message.roomId).emit("create:room:success", message) io.sockets.in(message.roomId).emit("add:member:success", message) }) }
Para agregar un nuevo miembro a una sala existente, primero debemos verificar si el miembro ya existe en esa sala.
io.on("connection", () => { // ... socket.on("add:member", async (message) => { console.log("add:member", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 0) return socket.emit("error", { message: "Room does not exist." }) const doesMemExist = await redis.sismember("members", message.roomId "::" message.username) if (doesMemExist === 1) return socket.emit("error", { message: "Username already exists, please choose another username." }) const memStatus = await redis.sadd("members", message.roomId "::" message.username) if (memStatus === 0) return socket.emit("error", { message: "User creation failed." }) socket.join(message.roomId) io.sockets.in(message.roomId).emit("add:member:success", message) }) socket.on("remove:member", async (message) => { console.log("remove:member", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 0) return socket.emit("error", { message: "Room does not exist." }) await redis.srem("members", message.roomId "::" message.username) socket.leave(message.roomId) io.sockets.in(message.roomId).emit("remove:member:success", message) }) }
Finalmente, creamos el evento de chat.
io.on("connection", () => { socket.on("create:chat", (message) => { console.log("create:chat", message) redis.lpush("chat::" message.roomId, message.username "::" message.message) io.sockets.in(message.roomId).emit("create:chat:success", message) }) }
El servidor de socket requiere conexiones persistentes, no funcionará en entornos sin servidor. Por lo tanto, no puede implementar su servidor de socket en vercel.
Puedes implementarlo en muchos lugares como Render, fly.io o Google Cloud Run.
Implementación en renderizado simple. Si tiene un archivo acoplable, creará automáticamente su proyecto a partir de ese archivo acoplable. Render tiene un nivel gratuito, pero tenga en cuenta que habrá un inicio en frío en el nivel gratuito.
Aquí está mi archivo Docker.
# syntax=docker/dockerfile:1 ARG NODE_VERSION=20.13.1 ARG PNPM_VERSION=9.4.0 FROM node:${NODE_VERSION}-bookworm AS base ## set shell to bash SHELL [ "/usr/bin/bash", "-c" ] WORKDIR /usr/src/app ## install pnpm. RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} # ------------ FROM base AS deps # Download dependencies as a separate step to take advantage of Docker's caching. # Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds. # Leverage bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them # into this layer. RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --prod --frozen-lockfile # ----------- FROM deps AS build ## downloading dev dependencies. RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile COPY . . RUN pnpm run build # ------------- FROM base AS final ENV NODE_ENV=production USER node COPY package.json . # Copy the production dependencies from the deps stage and also # the built application from the build stage into the image. COPY --from=deps /usr/src/app/node_modules ./node_modules COPY --from=build /usr/src/app/dist ./dist EXPOSE 3000 ENTRYPOINT [ "pnpm" ] CMD ["run", "start"]DIRTRABAJO /usr/src/app ## instalar pnpm. EJECUTAR --mount=tipo=cache,target=/root/.npm \ instalación npm -g pnpm@${PNPM_VERSION} # ------------ DESDE los departamentos AS de la base # Descargue las dependencias como un paso independiente para aprovechar el almacenamiento en caché de Docker. # Aproveche un montaje de caché en /root/.local/share/pnpm/store para acelerar las compilaciones posteriores. # Aprovechar los montajes de enlace en package.json y pnpm-lock.yaml para evitar tener que copiarlos # en esta capa. EJECUTAR --mount=tipo=bind,fuente=paquete.json,target=paquete.json \ --mount=tipo=bind,fuente=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=tipo=caché,target=/root/.local/share/pnpm/store \ instalación pnpm --prod --frozen-lockfile # ----------- DESDE los departamentos AS construir ## descargando dependencias de desarrollo. EJECUTAR --mount=tipo=bind,fuente=paquete.json,target=paquete.json \ --mount=tipo=bind,fuente=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=tipo=caché,target=/root/.local/share/pnpm/store \ instalación pnpm --frozen-lockfile COPIAR . . EJECUTAR pnpm ejecutar compilación # ------------- DE base AS final ENV NODE_ENV=producción nodo USUARIO COPIAR paquete.json . # Copie las dependencias de producción de la etapa de departamentos y también # la aplicación construida desde la etapa de construcción hasta la imagen. COPIAR --from=deps /usr/src/app/node_modules ./node_modules COPIAR --from=build /usr/src/app/dist ./dist EXPONER 3000 PUNTO DE ENTRADA
Ejecutar en la nube de Google
Eso es todo por este tutorial.
Gracias por leer ❣️
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3