ws の使い方

Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js

v8.20.0/週MITWeb API
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

ws の使い方 — Node.js WebSocketライブラリの決定版

一言でいうと

ws は、Node.js 向けの高速かつ軽量な WebSocket クライアント/サーバー実装です。RFC-6455 に完全準拠し、Autobahn テストスイートをパスしている、Node.js の WebSocket ライブラリとして最も広く使われているパッケージです。

どんな時に使う?

  • リアルタイムチャットや通知システム — サーバーからクライアントへのプッシュ通信が必要な場面
  • マイクロサービス間のリアルタイム通信 — Node.js バックエンド同士を WebSocket で接続し、低レイテンシなデータ交換を行いたい場合
  • IoT デバイスとの双方向通信 — センサーデータのストリーミングやデバイス制御など、持続的な接続が必要な場面

注意: ws はブラウザでは動作しません。ブラウザ側ではネイティブの WebSocket API を使用してください。Node.js とブラウザで同一コードを使いたい場合は isomorphic-ws を検討してください。

インストール

# npm
npm install ws

# yarn
yarn add ws

# pnpm
pnpm add ws

TypeScript を使う場合は型定義もインストールします。

npm install -D @types/ws

パフォーマンス最適化(オプション)

バイナリアドオンを追加すると、フレームのマスク/アンマスク処理が高速化されます。

npm install --save-optional bufferutil

基本的な使い方

最も典型的なパターンとして、WebSocket サーバーを立ち上げ、クライアントから接続する例を示します。

サーバー側

import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws: WebSocket) => {
  ws.on('error', console.error);

  ws.on('message', (data: Buffer) => {
    console.log('received: %s', data.toString());
  });

  ws.send('Hello from server!');
});

console.log('WebSocket server is running on ws://localhost:8080');

クライアント側

import WebSocket from 'ws';

const ws = new WebSocket('ws://localhost:8080');

ws.on('error', console.error);

ws.on('open', () => {
  ws.send('Hello from client!');
});

ws.on('message', (data: Buffer) => {
  console.log('received: %s', data.toString());
});

よく使う API — ws の主要機能5選

1. 外部 HTTP/HTTPS サーバーとの統合

Express や既存の HTTP サーバーに WebSocket を相乗りさせるパターンです。

import { createServer } from 'http';
import express from 'express';
import { WebSocketServer, WebSocket } from 'ws';

const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });

app.get('/', (_req, res) => {
  res.send('HTTP server is running');
});

wss.on('connection', (ws: WebSocket) => {
  ws.on('error', console.error);

  ws.on('message', (data: Buffer) => {
    console.log('received: %s', data.toString());
  });
});

server.listen(8080, () => {
  console.log('Server listening on port 8080');
});

2. ブロードキャスト(全クライアントへの一斉送信)

import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws: WebSocket) => {
  ws.on('error', console.error);

  ws.on('message', (data: Buffer) => {
    // 接続中の全クライアントに転送
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data.toString());
      }
    });
  });
});

3. noServer モード(パスベースのルーティング)

1つの HTTP サーバーで複数の WebSocket エンドポイントを提供できます。

import { createServer, IncomingMessage } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
import { Duplex } from 'stream';

const server = createServer();
const wssChat = new WebSocketServer({ noServer: true });
const wssNotify = new WebSocketServer({ noServer: true });

wssChat.on('connection', (ws: WebSocket) => {
  ws.on('error', console.error);
  ws.send('Connected to chat');
});

wssNotify.on('connection', (ws: WebSocket) => {
  ws.on('error', console.error);
  ws.send('Connected to notifications');
});

server.on('upgrade', (request: IncomingMessage, socket: Duplex, head: Buffer) => {
  const { pathname } = new URL(request.url!, `http://${request.headers.host}`);

  if (pathname === '/chat') {
    wssChat.handleUpgrade(request, socket, head, (ws) => {
      wssChat.emit('connection', ws, request);
    });
  } else if (pathname === '/notify') {
    wssNotify.handleUpgrade(request, socket, head, (ws) => {
      wssNotify.emit('connection', ws, request);
    });
  } else {
    socket.destroy();
  }
});

server.listen(8080);

4. Ping/Pong による切断検知

長時間接続を維持する場合、死んだコネクションを検出して切断するのは必須です。

import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

// WebSocket に isAlive プロパティを追加するための拡張
interface ExtWebSocket extends WebSocket {
  isAlive: boolean;
}

wss.on('connection', (ws: ExtWebSocket) => {
  ws.isAlive = true;

  ws.on('error', console.error);

  ws.on('pong', () => {
    // pong を受信したら生存フラグを立てる
    ws.isAlive = true;
  });
});

// 30秒ごとに全クライアントをチェック
const interval = setInterval(() => {
  (wss.clients as Set<ExtWebSocket>).forEach((ws) => {
    if (!ws.isAlive) {
      // 前回の ping に応答がなかった → 切断
      return ws.terminate();
    }

    ws.isAlive = false;
    ws.ping();
  });
}, 30_000);

wss.on('close', () => {
  clearInterval(interval);
});

5. クライアント認証(upgrade 時の検証)

import { createServer, IncomingMessage } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
import { Duplex } from 'stream';

const server = createServer();
const wss = new WebSocketServer({ noServer: true });

function authenticate(
  request: IncomingMessage,
  callback: (err: Error | null, client?: { userId: string }) => void
) {
  const url = new URL(request.url!, `http://${request.headers.host}`);
  const token = url.searchParams.get('token');

  if (token === 'valid-secret-token') {
    callback(null, { userId: 'user-123' });
  } else {
    callback(new Error('Authentication failed'));
  }
}

server.on('upgrade', (request: IncomingMessage, socket: Duplex, head: Buffer) => {
  authenticate(request, (err, client) => {
    if (err || !client) {
      socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
      socket.destroy();
      return;
    }

    wss.handleUpgrade(request, socket, head, (ws) => {
      wss.emit('connection', ws, request, client);
    });
  });
});

wss.on('connection', (ws: WebSocket, _request: IncomingMessage, client: { userId: string }) => {
  ws.on('error', console.error);
  ws.send(`Welcome, ${client.userId}!`);
});

server.listen(8080);

類似パッケージとの比較

特徴wsSocket.IOµWebSockets.js
プロトコル純粋な WebSocket (RFC-6455)WebSocket + 独自プロトコルWebSocket + HTTP
ブラウザクライアントなし(ネイティブ API を使用)専用クライアントライブラリ付属なし
自動再接続なしありなし
ルームやネームスペースなしありなし
パフォーマンス◎ 高速△ オーバーヘッドあり◎◎ 最速クラス
フォールバック (polling)なしありなし
依存関係ゼロ多いゼロ(ネイティブ)
学習コスト低い中程度中程度
npm 週間DL数非常に多い(事実上の標準)多い少なめ

選び方の目安:

  • シンプルな WebSocket 通信ws が最適
  • ブラウザとの通信でルーム管理や自動再接続が欲しい → Socket.IO
  • 極限のパフォーマンスが必要 → µWebSockets.js

注意点・Tips

1. message イベントのデータ型に注意

message イベントで受け取るデータは、デフォルトで Buffer です。文字列として扱いたい場合は明示的に変換してください。

ws.on('message', (data: Buffer, isBinary: boolean) => {
  if (isBinary) {
    // バイナリデータとして処理
    console.log('Binary data:', data);
  } else {
    // テキストデータとして処理
    const text = data.toString('utf-8');
    console.log('Text:', text);
  }
});

2. perMessageDeflate はデフォルトで無効(サーバー側)

圧縮はメモリ消費とCPU負荷が大きいため、サーバー側ではデフォルトで無効になっています。有効にする場合は、本番環境のワークロードで必ず負荷テストを行ってください。特に Linux 環境では zlib のメモリフラグメンテーション問題が報告されています。

3. エラーハンドリングは必ず設定する

error イベントのリスナーを設定しないと、未処理例外でプロセスがクラッシュします。

// 必ず設定する
ws.on('error', (err: Error) => {
  console.error('WebSocket error:', err.message);
});

4. ブラウザでは使えない

ws は Node.js 専用です。ブラウザ側は new WebSocket('ws://...') というネイティブ API を使ってください。サーバー側が ws であっても、ブラウザのネイティブ WebSocket と問題なく通信できます。

5. クライアントの IP アドレス取得

wss.on('connection', (ws: WebSocket, request: IncomingMessage) => {
  // リバースプロキシ経由の場合は X-Forwarded-For を参照
  const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
  console.log('Client IP:', ip);
});

6. bufferutil の無効化

セキュリティ上の理由でネイティブアドオンを使いたくない場合は、環境変数で無効化できます。

WS_NO_BUFFER_UTIL=1 node server.js

まとめ

ws は、Node.js で WebSocket を扱うなら最初に検討すべきライブラリです。依存関係ゼロで軽量、API もシンプルでありながら、noServer モードによるパスベースルーティングや外部 HTTP サーバーとの統合など、実運用に必要な機能は一通り揃っています。Socket.IO のような高レベルな抽象化が不要で、純粋な WebSocket 通信を実装したい場合には、ws が最も堅実な選択肢です。