Пошук уроків, статей та іншого контенту
Створите WebSocket Gateway і налаштуєте двонапрямну комунікацію клієнта із сервером.
WebSocket дає змогу встановити постійне двонапрямне з’єднання між клієнтом і сервером:
клієнт може надсилати події серверу;
сервер може надсилати події клієнту без нового HTTP-запиту;
з’єднання залишається відкритим доти, доки клієнт або сервер його не закриє.
У NestJS WebSocket-логіка описується в спеціальному класі — Gateway. Gateway схожий на контролер, але замість HTTP-маршрутів він обробляє події WebSocket.
NestJS підтримує кілька WebSocket-бібліотек. За замовчуванням для Gateway використовується адаптер Socket.IO, якщо встановлено @nestjs/platform-socket.io.
У наявному NestJS-проєкті встановіть WebSocket-пакети:
npm install @nestjs/websockets @nestjs/platform-socket.io socket.ioДля запуску окремого Node.js-клієнта також встановіть клієнтську бібліотеку:
npm install socket.io-clientСтворімо Gateway для простого чату:
// src/chat.gateway.ts
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayDisconnect,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: {
origin: '*',
},
})
export class ChatGateway
implements OnGatewayConnection, OnGatewayDisconnect
{
@WebSocketServer()
server!: Server;
handleConnection(client: Socket): void {
console.log(`Клієнт підключився: ${client.id}`);
client.emit('chat:system', {
message: 'Ви успішно підключилися до чату',
});
}
handleDisconnect(client: Socket): void {
console.log(`Клієнт відключився: ${client.id}`);
}
@SubscribeMessage('chat:send')
handleChatMessage(
@MessageBody() text: string,
@ConnectedSocket() client: Socket,
): void {
const message = {
clientId: client.id,
text,
createdAt: new Date().toISOString(),
};
// Надсилаємо повідомлення всім підключеним клієнтам
this.server.emit('chat:message', message);
}
}@WebSocketGateway() позначає клас як WebSocket Gateway.
@SubscribeMessage('chat:send') реєструє обробник події chat:send.
@MessageBody() отримує дані, які надіслав клієнт.
@ConnectedSocket() надає доступ до поточного підключення.
@WebSocketServer() додає до класу екземпляр Socket.IO-сервера.
handleConnection() викликається під час підключення клієнта.
handleDisconnect() викликається після розриву з’єднання.
Gateway потрібно додати до providers модуля:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
@Module({
providers: [ChatGateway],
})
export class AppModule {}Якщо Gateway не зареєстрований у модулі, NestJS не створить його екземпляр і обробники подій не працюватимуть.
Стандартний файл запуску застосунку може залишатися без змін:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Запустіть сервер:
npm run start:devGateway буде доступний через Socket.IO на тому самому порту, що й HTTP-застосунок: 3000.
Створімо Node.js-клієнт, який підключається до Gateway, надсилає повідомлення та слухає відповіді:
// client.cjs
const { io } = require('socket.io-client');
const socket = io('http://localhost:3000');
socket.on('connect', () => {
console.log(`Підключено. ID клієнта: ${socket.id}`);
socket.emit('chat:send', 'Привіт від клієнта');
});
socket.on('chat:system', (data) => {
console.log('Системне повідомлення:', data.message);
});
socket.on('chat:message', (message) => {
console.log('Нове повідомлення:', message);
});
socket.on('disconnect', (reason) => {
console.log('З’єднання закрито:', reason);
});
socket.on('connect_error', (error) => {
console.error('Помилка підключення:', error.message);
});Запустіть клієнт в окремому терміналі:
node client.cjsПісля підключення клієнт:
отримує подію chat:system;
надсилає серверу подію chat:send;
отримує від сервера подію chat:message.
У прикладі використовуються два напрямки комунікації.
Клієнт надсилає подію методом emit:
socket.emit('chat:send', 'Текст повідомлення');На сервері цю подію обробляє метод із декоратором @SubscribeMessage:
@SubscribeMessage('chat:send')
handleChatMessage(@MessageBody() text: string): void {
console.log(text);
}Назви подій на клієнті та сервері повинні збігатися.
Сервер надсилає подію через server.emit:
this.server.emit('chat:message', {
text: 'Нове повідомлення',
});Клієнт слухає цю подію через socket.on:
socket.on('chat:message', (message) => {
console.log(message.text);
});Щоб відповісти лише клієнту, який надіслав повідомлення, використовуйте об’єкт client:
@SubscribeMessage('chat:send')
handleChatMessage(
@MessageBody() text: string,
@ConnectedSocket() client: Socket,
): void {
client.emit('chat:message', {
text,
onlyFor: client.id,
});
}У такому разі інші підключені клієнти подію не отримають.
Socket.IO надає broadcast для надсилання події всім клієнтам, крім відправника:
@SubscribeMessage('chat:send')
handleChatMessage(
@MessageBody() text: string,
@ConnectedSocket() client: Socket,
): void {
client.broadcast.emit('chat:message', {
text,
from: client.id,
});
}Це зручно, коли клієнт сам одразу додає власне повідомлення до інтерфейсу, а сервер повинен повідомити лише інших учасників.
Подія може містити не лише рядок, а й об’єкт:
socket.emit('chat:send', {
text: 'Привіт',
author: 'Olena',
});Тип вхідних даних можна описати в TypeScript:
// src/chat-message.dto.ts
export class ChatMessageDto {
text!: string;
author!: string;
}Після цього використовуйте DTO як тип параметра:
import { ChatMessageDto } from './chat-message.dto';
@SubscribeMessage('chat:send')
handleChatMessage(
@MessageBody() payload: ChatMessageDto,
@ConnectedSocket() client: Socket,
): void {
this.server.emit('chat:message', {
clientId: client.id,
text: payload.text,
author: payload.author,
createdAt: new Date().toISOString(),
});
}Клієнт повинен надсилати об’єкт із відповідними властивостями:
socket.emit('chat:send', {
text: 'Привіт',
author: 'Olena',
});Клієнт і сервер часто працюють на різних портах, наприклад:
NestJS-сервер — http://localhost:3000;
клієнтський застосунок — http://localhost:5173.
У такому випадку потрібно дозволити відповідне джерело:
@WebSocketGateway({
cors: {
origin: 'http://localhost:5173',
},
})
export class ChatGateway {}Для локальної розробки можна тимчасово використовувати:
@WebSocketGateway({
cors: {
origin: '*',
},
})
export class ChatGateway {}У реальному застосунку краще вказувати конкретні дозволені джерела, а не '*'.
У цьому прикладі NestJS використовує Socket.IO. Тому клієнт також має підключатися через socket.io-client.
Події connect, disconnect і connect_error є службовими подіями Socket.IO. Події chat:send, chat:message і chat:system — це події, які визначили ми самостійно.
Зручно використовувати єдину схему іменування:
chat:send — команда від клієнта;
chat:message — нове повідомлення для клієнтів;
chat:system — системна інформація.
Це зменшує ризик плутанини між подіями різних частин застосунку.
Якщо клас Gateway не вказано в providers, він не буде активований:
@Module({
providers: [ChatGateway],
})
export class AppModule {}Клієнт:
socket.emit('chat:send', 'Привіт');Сервер:
@SubscribeMessage('chat:message')У цьому випадку обробник не виконається, оскільки сервер очікує іншу назву події.
Gateway із Socket.IO потрібно підключати через socket.io-client. Звичайний клієнт нативного WebSocket-протоколу не є еквівалентом Socket.IO-клієнта.
Якщо клієнт працює на іншому origin, підключення може бути заблоковане браузером. Перевірте параметр cors у @WebSocketGateway.
this.server.emit(...) — усім підключеним клієнтам;
client.emit(...) — лише поточному клієнту;
client.broadcast.emit(...) — усім, крім поточного клієнта.
WebSocket Gateway — це клас NestJS для обробки WebSocket-подій.
@WebSocketGateway() створює Gateway.
@SubscribeMessage() підписує метод на подію клієнта.
@MessageBody() отримує дані події.
@ConnectedSocket() надає доступ до поточного клієнта.
@WebSocketServer() дає змогу надсилати події всім клієнтам.
client.emit() надсилає дані одному клієнту.
this.server.emit() надсилає подію всім підключеним клієнтам.
Для Socket.IO Gateway використовуйте клієнт socket.io-client.
Gateway потрібно зареєструвати в providers відповідного модуля.