ws の使い方 — Node.js WebSocketライブラリの決定版
一言でいうと
ws は、Node.js 向けの高速かつ軽量な WebSocket クライアント/サーバー実装です。RFC-6455 に完全準拠し、Autobahn テストスイートをパスしている、Node.js の WebSocket ライブラリとして最も広く使われているパッケージです。
どんな時に使う?
- リアルタイムチャットや通知システム — サーバーからクライアントへのプッシュ通信が必要な場面
- マイクロサービス間のリアルタイム通信 — Node.js バックエンド同士を WebSocket で接続し、低レイテンシなデータ交換を行いたい場合
- IoT デバイスとの双方向通信 — センサーデータのストリーミングやデバイス制御など、持続的な接続が必要な場面
注意: ws はブラウザでは動作しません。ブラウザ側ではネイティブの
WebSocketAPI を使用してください。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);
類似パッケージとの比較
| 特徴 | ws | Socket.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 が最も堅実な選択肢です。