NestJSとWebSocket 2026年版:リアルタイム通信、Gatewayパターン、面接対策

NestJSでのWebSocket実装を徹底解説。Gatewayパターン、Socket.IO統合、認証、エラーハンドリング、2026年の技術面接で頻出の質問まで網羅します。

NestJS WebSocketリアルタイム通信ゲートウェイ

NestJSのWebSocketは、Gatewayパターンを活用した構造化されたリアルタイム通信を提供します。生のWebSocket実装とは異なり、NestJSはデコレータを通じて複雑さを抽象化し、依存性注入システムとシームレスに統合されます。

基本概念

NestJSにおけるWebSocket Gatewayは、@WebSocketGateway()デコレータが付与されたクラスであり、クライアントとサーバー間の双方向通信を処理します。複数のアダプター(Socket.IO、ws)をサポートし、NestJSのGuards、Pipes、Interceptorsと統合可能です。

Socket.IOを使用したWebSocket Gatewayの構築

NestJSのデフォルトアダプターはSocket.IOを使用しており、自動再接続、ルームサポート、HTTPロングポーリングへのフォールバック機能を提供します。インストールには、コアWebSocketsモジュールとともにプラットフォーム固有のパッケージが必要です。

bash
# terminal
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io

基本的なGatewayは、受信メッセージをリッスンし、接続されたクライアントにレスポンスをブロードキャストできます。

chat.gateway.tstypescript
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
  cors: { origin: '*' }, // Configure CORS for browser clients
})
export class ChatGateway {
  @WebSocketServer()
  server: Server; // Access to the underlying Socket.IO server

  @SubscribeMessage('message') // Listen for 'message' events
  handleMessage(
    @MessageBody() data: { text: string; room: string },
    @ConnectedSocket() client: Socket,
  ): void {
    // Broadcast to all clients in the room except sender
    client.to(data.room).emit('message', {
      text: data.text,
      senderId: client.id,
      timestamp: Date.now(),
    });
  }
}

@SubscribeMessageデコレータは、メソッドを特定のイベント名にバインドします。@MessageBody()はペイロードを抽出し、@ConnectedSocket()はターゲットを絞ったレスポンスのためにクライアントソケットへのアクセスを提供します。

接続管理のためのライフサイクルフック

NestJSのGatewayは、クライアントの接続と切断を処理するためのライフサイクルインターフェースを実装します。これらのフックにより、リソースのクリーンアップ、プレゼンストラッキング、接続の検証が可能になります。

presence.gateway.tstypescript
import {
  WebSocketGateway,
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnGatewayInit,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';

@WebSocketGateway()
export class PresenceGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  private readonly logger = new Logger(PresenceGateway.name);
  private connectedUsers = new Map<string, { joinedAt: Date }>();

  afterInit(server: Server): void {
    // Called once when the gateway initializes
    this.logger.log('WebSocket Gateway initialized');
  }

  handleConnection(client: Socket): void {
    // Track new connections
    this.connectedUsers.set(client.id, { joinedAt: new Date() });
    this.logger.log(`Client connected: ${client.id}`);
  }

  handleDisconnect(client: Socket): void {
    // Cleanup on disconnect
    this.connectedUsers.delete(client.id);
    this.logger.log(`Client disconnected: ${client.id}`);
  }
}

3つのライフサイクルインターフェースはそれぞれ異なる目的を持ちます。OnGatewayInitは起動時に一度実行され、OnGatewayConnectionはクライアント接続ごとにトリガーされ、OnGatewayDisconnectはクライアント離脱時のクリーンアップを処理します。

WebSocket Guardsによる認証

WebSocket接続は、保護されたイベントへのアクセスを許可する前に認証検証が必要です。NestJS GuardsはGatewayで機能しますが、実行コンテキストはHTTPリクエストとは異なります。公式のNestJS WebSocketsドキュメントでは、Guard統合について詳しく説明されています。

ws-auth.guard.tstypescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { JwtService } from '@nestjs/jwt';
import { Socket } from 'socket.io';

@Injectable()
export class WsAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const client: Socket = context.switchToWs().getClient();
    // Extract token from handshake auth or query params
    const token =
      client.handshake.auth?.token ||
      client.handshake.query?.token;

    if (!token) {
      throw new WsException('Missing authentication token');
    }

    try {
      const payload = this.jwtService.verify(token as string);
      // Attach user data to socket for later use
      client.data.user = payload;
      return true;
    } catch {
      throw new WsException('Invalid token');
    }
  }
}

@UseGuards()デコレータを使用して、Gatewayクラスまたは個別のメッセージハンドラーにGuardを適用します。WsExceptionクラスは、クライアントがSocket.IOエラーイベントを通じて受信するエラーをスローします。

secure-chat.gateway.tstypescript
import { UseGuards } from '@nestjs/common';
import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
import { WsAuthGuard } from './ws-auth.guard';

@WebSocketGateway()
@UseGuards(WsAuthGuard) // Protect all handlers in this gateway
export class SecureChatGateway {
  @SubscribeMessage('privateMessage')
  handlePrivateMessage(): void {
    // Only authenticated clients reach this handler
  }
}

モジュールレベルの認証に関する質問については、JWTストラテジーとセッション管理を網羅したNestJS認証面接モジュールをご覧ください。

Node.js / NestJSの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

ルームベースのブロードキャストパターン

Socket.IOのルーム機能により、接続されたクライアントのサブセットへのターゲットを絞ったメッセージ配信が可能になります。一般的なユースケースには、チャットルーム、ゲームロビー、ライブコラボレーション機能があります。

room.gateway.tstypescript
import {
  WebSocketGateway,
  SubscribeMessage,
  ConnectedSocket,
  MessageBody,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway()
export class RoomGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('joinRoom')
  handleJoinRoom(
    @MessageBody() roomId: string,
    @ConnectedSocket() client: Socket,
  ): { event: string; data: string } {
    // Add client to the specified room
    client.join(roomId);
    // Notify others in the room
    client.to(roomId).emit('userJoined', { userId: client.id });
    return { event: 'joinedRoom', data: roomId };
  }

  @SubscribeMessage('leaveRoom')
  handleLeaveRoom(
    @MessageBody() roomId: string,
    @ConnectedSocket() client: Socket,
  ): void {
    client.leave(roomId);
    client.to(roomId).emit('userLeft', { userId: client.id });
  }

  @SubscribeMessage('roomBroadcast')
  handleRoomBroadcast(
    @MessageBody() payload: { roomId: string; message: string },
  ): void {
    // Send to all clients in room, including sender
    this.server.to(payload.roomId).emit('roomMessage', payload.message);
  }
}

client.to(room).emit()server.to(room).emit()の違いは重要です。前者は送信者を除外し、後者はすべてのルームメンバーを含みます。

例外フィルターによるエラーハンドリング

WebSocket例外は、HTTP例外フィルターがGatewayコンテキストに適用されないため、専用のフィルターが必要です。カスタムフィルターはWsExceptionをキャッチし、クライアント向けにエラーレスポンスをフォーマットします。

ws-exception.filter.tstypescript
import { Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';

@Catch(WsException)
export class WsExceptionFilter implements ExceptionFilter {
  catch(exception: WsException, host: ArgumentsHost): void {
    const client: Socket = host.switchToWs().getClient();
    const error = exception.getError();
    // Send structured error to client
    client.emit('error', {
      type: 'WsException',
      message: typeof error === 'string' ? error : error,
      timestamp: new Date().toISOString(),
    });
  }
}

すべてのメッセージハンドラーで一貫したエラーハンドリングを実現するために、Gatewayレベルでフィルターを適用します。

filtered.gateway.tstypescript
import { UseFilters } from '@nestjs/common';
import { WebSocketGateway } from '@nestjs/websockets';
import { WsExceptionFilter } from './ws-exception.filter';

@WebSocketGateway()
@UseFilters(WsExceptionFilter)
export class FilteredGateway {
  // All handlers benefit from centralized error handling
}

このパターンは、ミドルウェアとインターセプターモジュールで説明されているNestJSのデコレータとフィルターの哲学を反映しています。

Redisアダプターによるスケーリング

単一サーバーのWebSocketデプロイメントは、ロードバランシングがクライアントを複数のインスタンスに分散する場合に失敗します。Socket.IO Redisアダプターは、pub/subメカニズムを通じてインスタンス間でイベントを同期します。

app.module.tstypescript
import { Module } from '@nestjs/common';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
import { ChatGateway } from './chat.gateway';

@Module({
  providers: [
    ChatGateway,
    {
      provide: 'REDIS_ADAPTER',
      useFactory: async () => {
        const pubClient = createClient({ url: process.env.REDIS_URL });
        const subClient = pubClient.duplicate();
        await Promise.all([pubClient.connect(), subClient.connect()]);
        return createAdapter(pubClient, subClient);
      },
    },
  ],
})
export class AppModule {}

Gatewayは、インスタンス間通信のためにアダプターを使用します。

scalable.gateway.tstypescript
import {
  WebSocketGateway,
  WebSocketServer,
  OnGatewayInit,
} from '@nestjs/websockets';
import { Inject } from '@nestjs/common';
import { Server } from 'socket.io';
import { Adapter } from 'socket.io-adapter';

@WebSocketGateway()
export class ScalableGateway implements OnGatewayInit {
  @WebSocketServer()
  server: Server;

  constructor(@Inject('REDIS_ADAPTER') private redisAdapter: Adapter) {}

  afterInit(): void {
    // Attach Redis adapter for multi-instance support
    this.server.adapter(this.redisAdapter as any);
  }
}

Redisアダプターを使用すると、1つのサーバーインスタンスで発行されたメッセージが、クラスター内の任意のインスタンスに接続されているクライアントに到達します。

NestJS WebSocketの面接頻出質問

技術面接では、リアルタイムアーキテクチャの決定とNestJS固有の実装詳細についての理解が問われることが多いです。

よくある質問

NestJSはWebSocket認証をHTTPとどのように異なる方法で処理しますか?

HTTPミドルウェアはWebSocket接続に対して実行されません。認証はハンドシェイクフェーズ中、またはGatewayに適用されたGuardsを通じて行われます。トークン検証は通常、handleConnection()またはsocket.handshake.authから読み取るカスタムGuardで行われます。

@WebSocketGateway()のポート設定とメインアプリケーションポートの違いは何ですか?

デフォルトでは、WebSocket GatewayはHTTPサーバーポートを共有します。@WebSocketGateway(3001)でポートを指定すると、そのポートに別のWebSocketサーバーが作成されます。共有ポートはデプロイメントを簡素化しますが、リバースプロキシを使用する場合はパスベースのルーティング(/socket.io)が必要です。

NestJSでWebSocket Gatewayをテストする方法は?

NestJSのテストユーティリティは、Test.createTestingModule()を通じてGatewayテストをサポートしています。socket.io-clientライブラリがテストサーバーに接続します。ライフサイクルフックとメッセージハンドラーは、イベントを発行してレスポンスや副作用をアサートすることでテストされます。

chat.gateway.spec.tstypescript
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { io, Socket } from 'socket.io-client';
import { ChatGateway } from './chat.gateway';

describe('ChatGateway', () => {
  let app: INestApplication;
  let client: Socket;

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      providers: [ChatGateway],
    }).compile();

    app = module.createNestApplication();
    await app.listen(3000);
    client = io('http://localhost:3000');
  });

  afterAll(async () => {
    client.disconnect();
    await app.close();
  });

  it('receives message acknowledgment', (done) => {
    client.emit('message', { text: 'test', room: 'lobby' });
    client.on('message', (data) => {
      expect(data.text).toBe('test');
      done();
    });
  });
});

Socket.IOの代わりにwsアダプターを使用すべき場面は?

wsライブラリアダプター(@nestjs/platform-ws)は、Socket.IOの自動再接続、ルーム、フォールバックトランスポートなどの機能を必要としないアプリケーションに対して、より低いオーバーヘッドを提供します。高頻度取引システムやゲームサーバーでは、レイテンシ削減のためにwsが好まれることが多いです。

より広範なNestJSアーキテクチャの概念については、Guards、Interceptors、モジュラーアーキテクチャに関する関連記事で、これらのパターンがHTTPとWebSocketコンテキスト間でどのように統合されるかを説明しています。

まとめ

  • WebSocket GatewayはNestJSの依存性注入、Guards、Filtersと統合され、HTTPとリアルタイムエンドポイント間で一貫したパターンを提供します
  • ライフサイクルフック(OnGatewayConnectionOnGatewayDisconnect)は、プレゼンストラッキングとリソースクリーンアップを管理します
  • 認証フローはHTTPとは異なり、トークン検証はハンドシェイク中または専用のWebSocket Guardsを通じて行われます
  • Socket.IOによるルームベースのブロードキャストにより、ソケットリストを手動で管理することなくターゲットを絞ったメッセージングが可能です
  • Redisアダプターは、複数のサーバーインスタンス間でイベントを同期することで水平スケーリングの問題を解決します
  • 例外フィルターには、WsExceptionとカスタムフィルターを使用したWebSocket固有の実装が必要です

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

フルスタック開発者、SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月10日 更新

共有

関連記事