+3

Sử dụng Socket.io trong NestJS

1. Giới thiệu Socket.io

Để xây dựng một ứng dụng realtime cần sử dụng socketio. Socketio sẽ giúp các bên ở những địa điểm khác nhau kết nối với nhau, truyền dữ liệu ngay lập tức thông qua server trung gian. Socketio có thể được sử dụng trong nhiều ứng dụng như chat, game online, đặt bàn,...

Socket.io là một thư viện JavaScript phổ biến cho phép chúng ta tạo giao tiếp hai chiều theo thời gian thực giữa trình duyệt web và máy chủ. Đây là một thư viện hiệu suất cao và đáng tin cậy được thiết kế để xử lý một lượng lớn dữ liệu. Nó tuân theo giao thức WebSocket và cung cấp các chức năng tốt hơn, cho phép chúng tôi xây dựng các ứng dụng thời gian thực hiệu quả. Ở trong bài viết này chúng tôi sử dụng framework NestJS với TypeScript để sử dụng socket IO.

2. NestJS

NestJS là một framework Node.js mạnh mẽ và linh hoạt được xây dựng để phát triển các ứng dụng server-side. Được thiết kế với mục tiêu giúp phát triển các ứng dụng quy mô lớn dễ dàng hơn, NestJS kết hợp các khái niệm từ lập trình hướng đối tượng (OOP), lập trình hướng chức năng (FP), và lập trình phản ứng (Reactive Programming).

NestJS tuân theo mô hình MVC (Model-View-Controller) và kiến trúc module hóa. Các thành phần chính bao gồm:

  • Modules: Được sử dụng để tổ chức mã nguồn thành các phần nhỏ, dễ quản lý. Mỗi module có thể chứa các controllers, services, và các thành phần khác.
  • Controllers: Xử lý các yêu cầu HTTP và trả về phản hồi cho người dùng.
  • Services: Chứa logic nghiệp vụ của ứng dụng, có thể được inject vào controllers hoặc các services khác.
  • Providers: Bao gồm các services, repositories, factories, và helpers, có thể được inject vào các thành phần khác nhờ cơ chế Dependency Injection của NestJS.

Một vài ưu điểm của NestJS có thể kể đến:

  • Modular Architecture: Cho phép chia nhỏ ứng dụng thành các module độc lập, dễ dàng quản lý và tái sử dụng.
  • TypeScript: Sử dụng TypeScript, cung cấp tính năng tự động hoàn thành mã, kiểm tra kiểu tĩnh, và các lợi ích khác của TypeScript.
  • Dependency Injection: Sử dụng cơ chế Dependency Injection, giúp quản lý các phụ thuộc một cách hiệu quả.
  • Extensibility: Dễ dàng mở rộng và tích hợp với các thư viện khác như TypeORM, Mongoose, hoặc các giải pháp authentication như Passport.

3. Sử dụng Socket.io trong NestJS

Đầu tiên, bạn cần cài đặt các gói @nestjs/websockets và socket.io thông qua câu lệnh sau trong terminal:

npm install --save @nestjs/websockets @nestjs/platform-socket.io socket.io

Tiếp theo chúng ta cần tạo Gateway. Gateway là thành phần xử lý các kết nối WebSocket trong NestJS. Bạn cần tạo một Gateway để quản lý các sự kiện WebSocket. Tạo một file mới chat.gateway.ts và cấu hình như sau:

import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import {
	ConnectedSocket,
	MessageBody,
	OnGatewayConnection,
	OnGatewayDisconnect,
	OnGatewayInit,
	SubscribeMessage,
	WebSocketGateway,	
	WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
	cors: {
		origin: '*',
	},
})
export class tableGateway
	implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
	@WebSocketServer()
	server: Server;
	constructor(
		private configService: ConfigService,
		private jwtService: JwtService,
	) {}

	afterInit(server: Socket) {
		console.log('Server initialized');
	}

	handleConnection(client: Socket, ...args: any[]) {
		console.log('Client connected ' + client.id);
	}

	handleDisconnect(client: Socket) {
		console.log('Client disconnected ' + client.id);
	}
}

@WebSocketGateway là một decorator được sử dụng để đánh dấu một class là WebSocket gateway. Nó có thể nhận một đối số là cổng mà gateway nên lắng nghe (ví dụ: @WebSocketGateway(3001)), hoặc một đối tượng tùy chọn (ví dụ: @WebSocketGateway({ namespace: 'events' })). Lưu ý bạn nên cấu hình cors để phía client có thể connect đến socket.

Trong NestJS, OnGatewayInit, OnGatewayConnection, và OnGatewayDisconnect là các interface được sử dụng để xử lý các sự kiện cụ thể của WebSocket Gateway.

  • OnGatewayInit: Interface này có một phương thức là afterInit(server: Server). Phương thức này được gọi sau khi gateway khởi tạo.** server** là thể hiện của server WebSocket.
  • OnGatewayConnection: Interface này có một phương thức là handleConnection(client: any, ...args: any[]). Phương thức này được gọi khi một client kết nối đến gateway. client là thể hiện của client kết nối và args là danh sách các tham số bổ sung.
  • OnGatewayDisconnect: Interface này có một phương thức là handleDisconnect(client: any). Phương thức này được gọi khi một client ngắt kết nối từ gateway. client là thể hiện của client ngắt kết nối.

Khi một class implements OnGatewayInit, OnGatewayConnection, và OnGatewayDisconnect, nó phải cung cấp các phương thức afterInit, handleConnection, và handleDisconnect. Điều này cho phép bạn xử lý các sự kiện khởi tạo, kết nối, và ngắt kết nối của gateway.

Để có thể chạy gateway trên server của bạn. Bạn cần đăng ký Gateway trong module của bạn. Mở file app.module.ts hoặc module bạn muốn sử dụng Gateway và thêm vào:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ChatGateway } from './chat.gateway';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, ChatGateway],
})
export class AppModule {}

Nếu bạn muốn sử dụng JWT để xác minh User trong Socket.io. bạn sẽ cần đăng ký JWTService trong constructor và kiểm tra trong phương thức handleConnection. Bạn có thể hiểu rõ hơn qua ví dụ dưới đây:

handleConnection(client: Socket, ...args: any[]) {
		console.log('Client connected ' + client.id);
		const authHeader = client.handshake.headers['authorization'];
		if (authHeader) {
			try {
				const token = authHeader;
				const decoded = this.jwtService.verify(token, {
					secret: this.configService.get('JWT_SECRET'),
				});
				client.data = decoded;
			} catch (error) {
				console.log(error);

				client.emit('error', {
					message: 'Unauthorized',
				});
				// send error message to client
				client.disconnect();
			}
		} else {
			client.emit('error', {
				message: 'Unauthorized',
			});
			client.disconnect();
		}
	}

Trong NestJS, @SubscribeMessage() là một decorator được sử dụng trong WebSocket gateway để xử lý các tin nhắn đến từ clients. Decorator này nhận một đối số là tên của sự kiện mà bạn muốn lắng nghe. Khi một tin nhắn với sự kiện này được nhận, phương thức được cấu hình bởi @SubscribeMessage() sẽ được gọi.

Đặc biệt nếu bạn muốn lấy thông tin của user sau khi xác thực accessToken thì chúng ta phải dùng @ConnectedSocket(). Đây là một decorator được sử dụng để truy cập đến socket client hiện tại trong một phương thức xử lý sự kiện của WebSocket gateway. Dưới đây là một ví dụ về cách sử dụng @SubscribeMessage():

@SubscribeMessage('events')
handleEvent(@ConnectedSocket() client: Socket, @MessageBody() data: any): string {
    console.log(client.data.id); 
    // logs the id of the client
    const message:string = data.message;
    return message;
}

Cuối cùng chúng ta có thê kết nối đến socket phía client. Tôi sẽ tạo một xử lý bắt sự kiến mà server trả message về.

const io = require('socket.io-client');
const socket = io('http://localhost:3000', {
  extraHeaders: {
    authorization: 'Bearer your_jwt_token_here'
  }
});

socket.on('connect', () => {
  console.log('Connected to the server');
});

socket.on('error', (error) => {
  console.log('Error:', error.message);
});

socket.on('events', (data) => {
  console.log('Received data:', data);
});

socket.emit('events', { message: 'Hello server!' });

Một phút quảng cáo

Nếu bạn quan tâm, hãy ghé thăm Bếp UIT của chúng tôi để khám phá những món ăn vô cũng hấp dẫn , Ví dụ, bạn không thể bỏ qua món Phở bò Kobe nè o((>ω< ))o. Trên trang web của chúng tôi, chúng tôi sử dụng Socket.io để xử lý chức năng đặt bàn, mang lại trải nghiệm mượt mà và tiện lợi cho khách hàng.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.