NestJS та WebSockets у 2026: Комунікація в Реальному Часі, Gateway та Питання на Співбесідах

Повний посібник по WebSockets у NestJS з патерном Gateway, інтеграцією Socket.IO, автентифікацією, обробкою помилок та питаннями на співбесідах 2026 року.

NestJS та WebSockets у 2026

NestJS WebSockets пропонують структурований підхід до комунікації в реальному часі з використанням патерну Gateway. На відміну від сирих імплементацій WebSocket, NestJS абстрагує складність через декоратори та безшовно інтегрується з системою впровадження залежностей.

Швидке Визначення

WebSocket Gateway у NestJS — це клас, позначений декоратором @WebSocketGateway(), який керує двонаправленою комунікацією між клієнтом і сервером. Він підтримує кілька адаптерів (Socket.IO, ws) та інтегрується з Guards, Pipes та Interceptors у NestJS.

Налаштування WebSocket Gateway з Socket.IO

Стандартний адаптер у NestJS використовує Socket.IO, який забезпечує автоматичне перепідключення, підтримку кімнат та fallback до HTTP long-polling. Встановлення вимагає пакету, специфічного для платформи, разом з основним модулем 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() витягує payload, тоді як @ConnectedSocket() надає доступ до сокету клієнта для цілеспрямованих відповідей.

Хуки Життєвого Циклу для Керування З'єднаннями

Gateway NestJS імплементують інтерфейси життєвого циклу для обробки підключень та відключень клієнтів. Ці хуки дозволяють очищення ресурсів, відстеження присутності та валідацію з'єднань.

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}`);
  }
}

Три інтерфейси життєвого циклу служать різним цілям: OnGatewayInit виконується один раз при старті, OnGatewayConnection спрацьовує при кожному підключенні клієнта, а OnGatewayDisconnect обробляє очищення, коли клієнти від'єднуються.

Автентифікація з WebSocket Guards

WebSocket з'єднання вимагають валідації автентифікації перед наданням доступу до захищених подій. NestJS Guards працюють з gateway, хоча контекст виконання відрізняється від HTTP запитів. Офіційна документація NestJS WebSockets детально описує інтеграцію guards.

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');
    }
  }
}

Guard застосовується до класу gateway або окремих обробників повідомлень за допомогою декоратора @UseGuards(). Клас WsException кидає помилки, які клієнт отримує через подію error 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
  }
}

Для питань автентифікації на рівні модуля варто ознайомитися з модулем питань на співбесідах NestJS про автентифікацію, що охоплює стратегії JWT та керування сесіями.

Готовий до співбесід з 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 щодо декораторів та фільтрів, описану в модулі middleware та interceptors.

Масштабування WebSockets з Redis Адаптером

Розгортання WebSocket на одному сервері виходять з ладу, коли балансування навантаження розподіляє клієнтів між кількома інстансами. Redis адаптер Socket.IO синхронізує події між інстансами через механізм 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 адаптером повідомлення, відправлене на одному інстансі сервера, досягає клієнтів, підключених до будь-якого інстансу в кластері.

Поширені Питання на Співбесідах про NestJS WebSockets

Технічні співбесіди часто перевіряють розуміння архітектурних рішень для реального часу та деталей імплементації, специфічних для NestJS.

Часто Запитують

Як NestJS обробляє автентифікацію WebSocket інакше, ніж HTTP?

HTTP middleware не виконується для WebSocket з'єднань. Автентифікація відбувається під час фази handshake або через Guards, застосовані до gateway. Валідація токенів зазвичай відбувається в handleConnection() або в кастомному Guard, який читає з socket.handshake.auth.

Яка різниця між конфігурацією порту @WebSocketGateway() та основним портом додатку?

За замовчуванням WebSocket gateway поділяють порт HTTP сервера. Вказівка порту в @WebSocketGateway(3001) створює окремий WebSocket сервер на цьому порту. Спільні порти спрощують розгортання, але вимагають маршрутизації на основі шляхів (/socket.io) при використанні reverse proxy.

Як тестувати WebSocket gateway в NestJS?

Інструменти тестування NestJS підтримують тестування gateway через Test.createTestingModule(). Бібліотека 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();
    });
  });
});

Коли слід використовувати адаптер ws замість Socket.IO?

Адаптер бібліотеки ws (@nestjs/platform-ws) пропонує менший overhead для додатків, яким не потрібні функції Socket.IO, такі як автоматичне перепідключення, кімнати чи fallback транспорти. Системи високочастотної торгівлі та ігрові сервери часто віддають перевагу ws через зменшену затримку.

Для ширших концепцій архітектури NestJS, пов'язана стаття про Guards, Interceptors та Модульну Архітектуру описує, як ці патерни інтегруються в контекстах HTTP та WebSocket.

Висновок

  • WebSocket Gateways інтегруються з dependency injection NestJS, Guards та Filters для консистентних патернів у HTTP та real-time endpoints
  • Хуки життєвого циклу (OnGatewayConnection, OnGatewayDisconnect) керують відстеженням присутності та очищенням ресурсів
  • Потоки автентифікації відрізняються від HTTP; валідація токенів відбувається під час handshake або через спеціалізовані WebSocket Guards
  • Розсилка на основі кімнат з Socket.IO дозволяє цілеспрямовані повідомлення без ручного керування списками сокетів
  • Redis адаптер вирішує горизонтальне масштабування шляхом синхронізації подій між кількома інстансами сервера
  • Фільтри винятків вимагають імплементацій, специфічних для WebSocket, з використанням WsException та кастомних фільтрів

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Fullstack-розробник, засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 10 серпня 2026 р.

Теги

#nestjs
#websockets
#real-time
#socket.io
#gateway

Поділитися

Пов'язані статті