Neste tutorial, construiremos um aplicativo de bate-papo usando web sockets. Os soquetes da Web são realmente úteis quando você deseja criar aplicativos que exigem transferência de dados em tempo real.
Ao final deste tutorial, você será capaz de configurar seu próprio servidor de soquete, enviar e receber mensagens em tempo real, armazenar dados no Redis e implantar seu aplicativo no render e no Google Cloud Run.
Estaremos construindo um aplicativo de chat. Para ser breve, iremos apenas configurar o servidor. Você pode usar sua própria estrutura de front-end e acompanhar.
Neste aplicativo de chat, haverá salas e os usuários poderão entrar em uma sala e começar a conversar. Para manter tudo simples, assumiremos que os nomes de usuário não são exclusivos. Porém cada sala pode ter apenas um usuário com um nome de usuário específico.
Primeiro precisamos instalar as dependências necessárias.
npm i express cors socket.io -D @types/node
Estaremos usando o módulo http para configurar nosso servidor de soquete. Como nosso aplicativo estará rodando no terminal, teremos que permitir todas as origens.
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 o redis para armazenar nossas mensagens junto com as informações da sala e do usuário. Você pode usar upstash redis (gratuito). Crie uma nova instância redis em seu painel upstash. Após a criação, você receberá um URL redis que poderá usar para se conectar à sua instância redis.
Instale qualquer cliente redis de sua escolha. Usarei ioredis.
npm i ioredis
Em seguida, inicializaremos nosso cliente redis e o conectaremos ao nosso servidor redis usando o URL de conexão que obtivemos.
/** /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)
Os usuários podem criar salas ou ingressar em salas existentes. Os quartos são identificados por IDs exclusivos. Cada membro tem um nome de usuário único dentro de uma sala, e não globalmente.
Podemos acompanhar todas as salas ativas em nosso servidor, armazenando seus IDs de sala dentro de um conjunto redis.
Para nosso propósito, os nomes de usuário são exclusivos apenas dentro de uma sala. Então, nós os armazenamos em um conjunto junto com o id da sala. Isso garante que a combinação do ID da sala com o ID do membro seja única globalmente.
Podemos configurar o evento de soquete para criar espaço. Quando criamos uma sala, também adicionamos na sala o membro que solicitou sua criação.
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 adicionar um novo membro a uma sala existente, primeiro precisamos verificar se o membro já existe naquela 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, criamos o 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) }) }
O servidor de soquete requer conexões persistentes e não funciona em ambientes sem servidor. Portanto, você não pode implantar seu servidor de soquete no vercel.
Você pode implantá-lo em vários lugares, como Render, fly.io ou Google Cloud Run.
Implantação em renderização simples. Se você tiver um dockerfile, ele criará automaticamente seu projeto a partir desse dockerfile. A renderização tem um nível gratuito, mas lembre-se de que haverá inicialização a frio no nível gratuito.
Aqui está meu arquivo 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"]WORKDIR /usr/src/app ## instale o pnpm. EXECUTAR --mount=type=cache,target=/root/.npm \ instalação npm -g pnpm@${PNPM_VERSION} # ------------ DA base AS deps # Baixe as dependências como uma etapa separada para aproveitar as vantagens do cache do Docker. # Aproveite uma montagem de cache em /root/.local/share/pnpm/store para acelerar compilações subsequentes. # Aproveite montagens de ligação para package.json e pnpm-lock.yaml para evitar ter que copiá-los # nesta camada. EXECUTAR --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 \ instalação pnpm --prod --frozen-lockfile # ----------- DE deps AS build ## baixando dependências de desenvolvimento. EXECUTAR --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 \ instalação pnpm --frozen-lockfile CÓPIA. . EXECUTAR compilação de execução pnpm # ------------- DA base COMO final ENV NODE_ENV=produção Nó USUÁRIO COPIE pacote.json . # Copie as dependências de produção do estágio deps e também # o aplicativo construído desde o estágio de construção até a imagem. COPIAR --from=deps /usr/src/app/node_modules ./node_modules COPIAR --from=build /usr/src/app/dist ./dist EXPOSIÇÃO 3000 PONTO DE ENTRADA ["pnpm"] CMD ["executar", "iniciar"]
Se você deseja uma alternativa gratuita para renderizar e evitar inicializações a frio, você deve usar o Google Cloud Run. As etapas para implantar na nuvem estão além do escopo deste artigo, mas aqui está uma pequena lista de coisas que você precisa fazer.
Este tutorial é isso.
Obrigado por ler ❣️
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3