/ / Xây dựng API trò chuyện thời gian thực bằng WebSockets trong NestJS

Xây dựng API trò chuyện thời gian thực bằng WebSockets trong NestJS

NestJS là một framework phổ biến để xây dựng các ứng dụng phía máy chủ với Node.js. Với sự hỗ trợ cho WebSockets, NestJS rất phù hợp để phát triển các ứng dụng trò chuyện thời gian thực.


Vậy WebSockets là gì và làm cách nào bạn có thể xây dựng ứng dụng trò chuyện thời gian thực trong NestJS?


WebSockets là gì?

WebSockets là một giao thức để liên lạc liên tục, thời gian thực và hai chiều giữa máy khách và máy chủ.

Không giống như trong HTTP, nơi kết nối bị đóng khi hoàn thành chu kỳ yêu cầu giữa máy khách và máy chủ, kết nối WebSocket được giữ mở và không đóng ngay cả sau khi phản hồi đã được trả lại cho yêu cầu.

Hình ảnh bên dưới là hình ảnh trực quan về cách thức hoạt động của giao tiếp WebSocket giữa máy chủ và máy khách:

Trực quan hóa giao tiếp máy khách-máy chủ WebSocket

Để thiết lập giao tiếp hai chiều, máy khách sẽ gửi yêu cầu bắt tay WebSocket đến máy chủ. Các tiêu đề yêu cầu chứa khóa WebSocket an toàn (Sec-WebSocket-Key), và một Nâng cấp: WebSocket tiêu đề cùng với Kết nối: Nâng cấp tiêu đề yêu cầu máy chủ nâng cấp giao thức từ HTTP lên WebSocket và giữ cho kết nối luôn mở. Tìm hiểu về WebSockets trong JavaScript giúp hiểu rõ hơn về khái niệm này.

Xây dựng API trò chuyện thời gian thực bằng cách sử dụng Mô-đun NestJS WebSocket

Node.js cung cấp hai triển khai WebSockets chính. Đầu tiên là ws triển khai WebSockets trần. Và cái thứ hai là socket.io, cung cấp nhiều tính năng cấp cao hơn.

NestJS có các mô-đun cho cả socket.io và ws. Bài viết này sử dụng socket.io mô-đun cho các tính năng WebSocket của ứng dụng mẫu.

Mã được sử dụng trong dự án này có sẵn trong kho lưu trữ GitHub. Bạn nên sao chép nó cục bộ để hiểu rõ hơn về cấu trúc thư mục và xem tất cả các mã tương tác với nhau như thế nào.

Thiết lập và cài đặt dự án

Mở thiết bị đầu cuối của bạn và tạo ứng dụng NestJS mới bằng cách sử dụng tổ mới lệnh (ví dụ Nest ứng dụng trò chuyện mới). Lệnh tạo một thư mục mới chứa các tệp dự án. Bây giờ bạn đã sẵn sàng để bắt đầu quá trình phát triển.

Thiết lập kết nối MongoDB

Để duy trì các tin nhắn trò chuyện trong ứng dụng, bạn cần có cơ sở dữ liệu. Bài viết này sử dụng cơ sở dữ liệu MongoDB cho ứng dụng NestJS của chúng tôi và cách dễ nhất để chạy là thiết lập cụm MongoDB trên đám mây và lấy URL MongoDB của bạn. Sao chép URL và lưu trữ dưới dạng MONGO_URI biến trong của bạn .env tài liệu.

Bạn cũng sẽ cần Mongoose sau này khi bạn thực hiện các truy vấn tới MongoDB. Cài đặt nó bằng cách chạy npm cài đặt cầy mangut trong thiết bị đầu cuối của bạn.

bên trong src thư mục, tạo một tệp có tên mongo.config.ts và dán đoạn mã sau vào đó.

 import { registerAs } from '@nestjs/config';


* Mongo database connection config
*/

export default registerAs('mongodb', () => {
  const { MONGO_URI } = process.env;
  return {
    uri:`${MONGO_URI}`,
  };
});

dự án của bạn chính.ts tập tin sẽ trông như thế này:

 import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser'
import helmet from 'helmet'
import { Logger, ValidationPipe } from '@nestjs/common';
import { setupSwagger } from './utils/swagger';
import { HttpExceptionFilter } from './filters/http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { cors: true });
  app.enableCors({
    origin: '*',
    credentials: true
  })
  app.use(cookieParser())
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true
    })
  )
  const logger = new Logger('Main')

  app.setGlobalPrefix('api/v1')
  app.useGlobalFilters(new HttpExceptionFilter());

  setupSwagger(app)
  app.use(helmet())

  await app.listen(AppModule.port)

  
  const baseUrl = AppModule.getBaseUrl(app)
  const url = `http://${baseUrl}:${AppModule.port}`
  logger.log(`API Documentation available at ${url}/docs`);
}
bootstrap();

Xây dựng mô-đun trò chuyện

Để bắt đầu với tính năng trò chuyện thời gian thực, bước đầu tiên là cài đặt gói NestJS WebSockets. Điều này có thể được thực hiện bằng cách chạy lệnh sau trong terminal.

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

Sau khi cài đặt các gói, bạn cần tạo mô-đun trò chuyện bằng cách chạy các lệnh sau

 nest g module chats
nest g controller chats
nest g service chats

Sau khi tạo xong mô-đun, bước tiếp theo là tạo kết nối WebSockets trong NestJS. Tạo một trò chuyện.gateway.ts tập tin bên trong cuộc trò chuyện thư mục, đây là nơi triển khai cổng gửi và nhận tin nhắn.

Dán đoạn mã sau vào trò chuyện.gateway.ts.

 import {
    MessageBody,
    SubscribeMessage,
    WebSocketGateway,
    WebSocketServer,
  } from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway()
export class ChatGateway {
    @WebSocketServer()
    server: Server;
    
    @SubscribeMessage('send_message')
    listenForMessages(@MessageBody() message: string) {
      this.server.sockets.emit('receive_message', message);
    }
}

Xác thực người dùng được kết nối

Xác thực là một phần thiết yếu của các ứng dụng web và nó không khác gì đối với ứng dụng trò chuyện. Chức năng xác thực kết nối máy khách với ổ cắm được tìm thấy trong trò chuyện.service.ts như được hiển thị ở đây:

 @Injectable()
export class ChatsService {
    constructor(private authService: AuthService) {}

    async getUserFromSocket(socket: Socket) {
        let auth_token = socket.handshake.headers.authorization;
        
        auth_token = auth_token.split(' ')[1];

        const user = this.authService.getUserFromAuthenticationToken(
            auth_token
        );

        if (!user) {
            throw new WsException('Invalid credentials.');
        }
        return user;
    }
}

Các getUserFromSocket phương pháp sử dụng getUserFromAuthenticationToken để lấy người dùng hiện đang đăng nhập từ mã thông báo JWT bằng cách trích xuất mã thông báo Bearer. Các getUserFromAuthenticationToken chức năng được thực hiện trong auth.service.ts tập tin như được hiển thị ở đây:

 public async getUserFromAuthenticationToken(token: string) {
        const payload: JwtPayload = this.jwtService.verify(token, {
          secret: this.configService.get('JWT_ACCESS_TOKEN_SECRET'),
        });

        const userId = payload.sub

        if (userId) {
            return this.usersService.findById(userId);
        }
      }

Ổ cắm hiện tại được truyền dưới dạng tham số cho getUserFromSocket khi mà kết nối xử lý phương pháp của Trò chuyệnGateway thực hiện các OnGatewayConnection giao diện. Điều này cho phép nhận tin nhắn và thông tin về người dùng hiện được kết nối.

Đoạn mã dưới đây chứng minh điều này:

 
@WebSocketGateway()
export class ChatGateway implements OnGatewayConnection {
    @WebSocketServer()
    server: Server;

    constructor(private chatsService: ChatsService) {}

    async handleConnection(socket: Socket) {
        await this.chatsService.getUserFromSocket(socket)
    }

    @SubscribeMessage('send_message')
    async listenForMessages(@MessageBody() message: string, @ConnectedSocket() socket: Socket) {

        const user = await this.chatsService.getUserFromSocket(socket)
        this.server.sockets.emit('receive_message', {
            message,
            user
        });
    }
}

Bạn có thể tham khảo các tệp liên quan đến hệ thống xác thực ở trên trong kho lưu trữ GitHub để xem mã hoàn chỉnh (bao gồm cả nhập), để hiểu rõ hơn về việc triển khai.

Liên tục trò chuyện với cơ sở dữ liệu

Để người dùng xem lịch sử nhắn tin của họ, bạn cần một giản đồ để lưu trữ tin nhắn. Tạo một tệp mới có tên tin nhắn.schema.ts và dán mã bên dưới vào đó (nhớ nhập mã của bạn lược đồ người dùng hoặc kiểm tra kho lưu trữ cho một).

 import { User } from './../users/schemas/user.schema';
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import mongoose, { Document } from "mongoose";

export type MessageDocument = Message & Document;

@Schema({
    toJSON: {
        getters: true,
        virtuals: true,
    },
    timestamps: true,
})
export class Message {
    @Prop({ required: true, unique: true })
    message: string

    @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
    user: User
}

const MessageSchema = SchemaFactory.createForClass(Message)

export { MessageSchema };

Dưới đây là triển khai các dịch vụ để tạo một tin nhắn mới và nhận tất cả các tin nhắn trong trò chuyện.service.ts.

 import { Message, MessageDocument } from './message.schema'; 
import { Socket } from 'socket.io';
import { AuthService } from './../auth/auth.service';
import { Injectable } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { MessageDto } from './dto/message.dto';

@Injectable()
 export class ChatsService {
    constructor(private authService: AuthService, @InjectModel(Message.name) private messageModel: Model<MessageDocument>) {}
    ....
    async createMessage(message: MessageDto, userId: string) {
        const newMessage = new this.messageModel({...message, userId})
        await newMessage.save
       return newMessage
    }
    async getAllMessages() {
       return this.messageModel.find().populate('user')
    }
}

Các Tin nhắnDto được thực hiện trong một tin nhắn.dto.ts tập tin trong dto thư mục trong cuộc trò chuyện danh mục. Bạn cũng có thể tìm thấy nó trong kho lưu trữ.

Bạn cần thêm tin nhắn mô hình và lược đồ vào danh sách nhập khẩu trong chat.module.ts.

 import { Message, MessageSchema } from './message.schema';
import { Module } from '@nestjs/common';
import { ChatGateway } from './chats.gateway';
import { ChatsService } from './chats.service';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forFeature([
    { name: Message.name, schema: MessageSchema }
  ])],
  controllers: [],
  providers: [ChatsService, ChatGateway]
})
export class ChatsModule {}

cuối cùng get_all_messages trình xử lý sự kiện được thêm vào Trò chuyệnGateway lớp trong trò chuyện.gateway.ts như đã thấy trong đoạn mã sau:

 

@WebSocketGateway()
export class ChatGateway implements OnGatewayConnection {
    ....

    @SubscribeMessage('get_all_messages')
    async getAllMessages(@ConnectedSocket() socket: Socket) {

        await this.chatsService.getUserFromSocket(socket)
        const messages = await this.chatsService.getAllMessages()

        this.server.sockets.emit('receive_message', messages);

        return messages
    }
}

Khi một máy khách (người dùng) được kết nối phát ra get_all_messages sự kiện, tất cả tin nhắn của họ sẽ được truy xuất và khi họ phát ra gửi tin nhắn, một tin nhắn được tạo và lưu trữ trong cơ sở dữ liệu, sau đó được gửi đến tất cả các máy khách được kết nối khác.

Sau khi hoàn thành tất cả các bước trên, bạn có thể bắt đầu ứng dụng của mình bằng cách sử dụng bắt đầu chạy npm: devvà kiểm tra nó với ứng dụng khách WebSocket như Postman.

Xây dựng ứng dụng thời gian thực với NestJS

Mặc dù có các công nghệ khác để xây dựng hệ thống thời gian thực, nhưng WebSockets rất phổ biến và dễ thực hiện trong nhiều trường hợp và chúng là lựa chọn tốt nhất cho các ứng dụng trò chuyện.

Các ứng dụng thời gian thực không chỉ giới hạn ở các ứng dụng trò chuyện, các ví dụ khác bao gồm các ứng dụng gọi điện hoặc phát trực tuyến video cũng như các ứng dụng thời tiết trực tiếp và NestJS cung cấp công cụ tuyệt vời để xây dựng các ứng dụng thời gian thực.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *