📘

TypeScript で WebSocket サーバを立てる

2024/06/12に公開

はじめに

TypeScript で WebSocket サーバを立てる方法を記載します。

こちらの記事の内容を参考にしながら備忘録のために記載しました。

https://lef237.hatenablog.com/entry/2023/12/04/083322

作業プロジェクトの準備

TypeScript の簡易プロジェクトを作成します。

長いので折りたたんでおきます。

package.json を作成

package.json を作成します。

$ mkdir -p node-ws-sample
$ cd node-ws-sample
$ pnpm init

package.json を変更します。

package.json
{
  "name": "mute-sample",
  "version": "1.0.0",
  "description": "",
- "main": "index.js",
- "scripts": {
-   "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
+ "main": "index.ts",
+ "scripts": {
+   "typecheck": "tsc --noEmit",
+   "dev": "vite-node index.ts",
+   "build": "tsc"
+ },
+ "keywords": [],
+ "author": "",
}

TypeScript & vite-node をインストール

TypeScript と vite-node をインストールします。補足としてこちらの理由のため ts-node ではなく vite-node を利用します。

$ pnpm install -D typescript vite-node @types/node

TypeScriptの設定ファイルを作成

tsconfig.json を作成します。

$ npx tsc --init

tsconfig.json を上書きします。

tsconfig.json
{
  "compilerOptions": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "ES2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,

    /* Strictness */
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "checkJs": true,

    /* Bundled projects */
    "noEmit": true,
    "outDir": "dist",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "preserve",
    "incremental": true,
    "sourceMap": true,
  },
  "include": ["**/*.ts", "**/*.js"],
  "exclude": ["node_modules", "dist"]
}

git を初期化します。

$ git init

.gitignore を作成します。

$ touch .gitignore
.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build
dist/

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

動作確認コードを作成

動作を確認するためのコードを作成します。

$ touch index.ts
index.ts
console.log('Hello, World');

型チェック

型チェックします。

$ pnpm run typecheck

動作確認

動作確認を実施します。

$ pnpm run dev

Hello, World

コミットします。

$ git add .
$ git commit -m "初回コミット"

Node.js で WebSocket を利用

インストール

WebSocket を利用するためのライブラリと型をインストールします。

$ pnpm install ws
$ pnpm install --save-dev @types/ws

サーバ

サーバ用のコードを作成します。

$ touch server.ts
server.ts
import { WebSocket, WebSocketServer } from "ws";

// サーバーのポート番号
const port = 8080; 
// WebSocketサーバーを作成
const server = new WebSocketServer({ port });
// 接続中のクライアントを格納するSet
const clients = new Set<WebSocket>();

console.log(`WebSocket Server is running on port ${port}`);

// 接続が確立されたときの処理
server.on("connection", (ws) => {
  console.log("Client has connected");
  // クライアントを追加
  clients.add(ws);

  ws.on("message", (message) => {
    console.log(`Received message => ${message}`);
    // すべてのクライアントにメッセージをブロードキャスト
    clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(`\nClient said: ${message}`);
      }
    });
  });

  // クライアントが切断されたときの処理
  ws.on("close", () => {
    console.log("Client has disconnected");
    clients.delete(ws); // クライアントを削除
  });
});

クライアント

クライアント用のコードを作成します。

$ touch client.ts
client.ts
import WebSocket from "ws";
import readline from "readline";

// 接続先のURL
const url = "ws://localhost:8080";
// WebSocketクライアントを作成
const ws = new WebSocket(url);

// コマンドラインからの入力を受け付けるためのインターフェースを作成
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

// open イベントは、 WebSocket のコネクションが開かれたときに発生します
ws.onopen = () => {
  console.log("Connected to the server.");
  // ユーザーからの入力を受け付けます
  promptInput();
};

// ユーザーからの入力を受け付ける関数です
function promptInput() {
  rl.question("Enter message to send: ", (message) => {
    if (message === "exit") {
      // 'exit'と入力した場合、プログラムを終了します
      rl.close();
      ws.close();
    } else {
      ws.send(message);
    }
  });
}

// onmessage イベントは、WebSocketからメッセージを受信したときに発生します。
ws.onmessage = (e) => {
  console.log(`Received: ${e.data}`);
  promptInput();
};

// onerror イベントは、WebSocketのエラーが発生したときに発生します。
ws.onerror = (error) => {
  console.error(`WebSocket Error: ${error}`);
};

// oncloseは、WebSocketのコネクションが閉じられたときに発生します。
ws.onclose = () => {
  console.log("Disconnected from the server");
  rl.close();
};

動作確認

サーバーを起動します。

$ pnpm vite-node server.ts

クライアントで接続します。今回は接続するクライアントを 2 つ準備しています。

$ pnpm vite-node client.ts

クライアント1,クライアント2からメッセージを送受信できます。

alt text

コミットします。

$ git add .
$ git commit -m "WebSocketの機能を追加"

作業リポジトリ

https://github.com/hayato94087/node-ws-sample

参考

https://apidog.com/jp/blog/nodejs-websocket/

https://lef237.hatenablog.com/entry/2023/12/04/083322

Discussion