このチュートリアルでは、Web ソケットを使用してチャット アプリケーションを構築します。 Web ソケットは、リアルタイムのデータ転送を必要とするアプリケーションを構築する場合に非常に役立ちます。
このチュートリアルを終えると、独自のソケット サーバーをセットアップし、リアルタイムでメッセージを送受信し、Redis にデータを保存し、レンダリングおよび Google Cloud 実行時にアプリケーションをデプロイできるようになります。
チャットアプリケーションを構築していきます。簡潔にするために、サーバーのセットアップのみを行います。独自のフロントエンド フレームワークを使用して、それに従うことができます。
このチャット アプリケーションにはルームがあり、ユーザーはルームに参加してチャットを開始できます。すべてを単純にするために、ユーザー名は一意ではないと仮定します。ただし、各ルームに特定のユーザー名を持つユーザーは 1 人だけです。
まず、必要な依存関係をインストールする必要があります。
npm i express cors socket.io -D @types/node
http モジュールを使用してソケット サーバーをセットアップします。アプリはターミナルで実行されるため、すべてのオリジンを許可する必要があります。
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}`));
ルームとユーザーの情報とともにメッセージを保存するために Redis を使用します。 upstash redis (無料) を使用できます。 Upstash ダッシュボードで新しい Redis インスタンスを作成します。作成後、redis インスタンスへの接続に使用できる redis URL を受け取ります。
選択した Redis クライアントをインストールします。私はioredisを使用します。
npm i ioredis
次に、Redis クライアントを初期化し、取得した接続 URL を使用して Redis サーバーに接続します。
/** /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)
ユーザーはルームを作成したり、既存のルームに参加したりできます。ルームは一意のルーム ID によって識別されます。各メンバーには、グローバルではなくルーム内で一意のユーザー名があります。
ルーム ID を Redis セット内に保存することで、サーバー内のすべてのアクティブなルームを追跡できます。
目的上、ユーザー名はルーム内でのみ一意です。したがって、それらをルーム ID とセットで保存します。これにより、ルーム ID とメンバー ID の組み合わせがグローバルに一意になることが保証されます。
ルームを作成するためのソケットイベントを設定できます。ルームを作成すると、その作成をリクエストしたメンバーもルームに追加されます。
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) }) }
既存のルームに新しいメンバーを追加するには、まずメンバーがそのルームにすでに存在するかどうかを確認する必要があります。
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) }) }
最後に、チャット イベントを作成します。
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) }) }
ソケット サーバーには永続的な接続が必要です。サーバーレス環境では機能しません。したがって、vercel.
にソケット サーバーをデプロイすることはできません。Render、fly.io、Google Cloud Run などのさまざまな場所にデプロイできます。
レンダーシンプルでデプロイします。 dockerfile がある場合は、その dockerfile からプロジェクトが自動的にビルドされます。 Render には無料利用枠がありますが、無料利用枠ではコールド スタートがあることに注意してください。
これが私の dockerfile です。
# 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 ## pnpm をインストールします。 RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} # ------------ ベース AS 配備から # Docker のキャッシュを利用するには、別の手順として依存関係をダウンロードします。 # /root/.local/share/pnpm/store へのキャッシュ マウントを利用して、後続のビルドを高速化します。 # package.json と pnpm-lock.yaml へのバインド マウントを利用して、それらをコピーする手間を省きます。 # このレイヤーに。 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 # ----------- DEPS AS ビルドから ## 開発依存関係をダウンロードしています。 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 コピー 。 。 RUN pnpm ビルドを実行 # ------------- FROMベースASファイナル ENV NODE_ENV=本番環境 ユーザーノード package.json をコピーします。 # 運用環境の依存関係を deps ステージからコピーし、 # ビルドされたアプリケーションをビルド段階からイメージに取り込みます。 COPY --from=deps /usr/src/app/node_modules ./node_modules COPY --from=build /usr/src/app/dist ./dist エクスポーズ3000 エントリーポイント
Googleクラウドラン
の形式に変更します。
このチュートリアルは以上です。
読んでいただきありがとうございます❣️
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3