بیاموزید که چگونه با استفاده از NestJS، یک API چت همزمان با استفاده از قدرت WebSockets بسازید.
NestJS یک چارچوب محبوب برای ساخت برنامه های سمت سرور با Node.js است. NestJS با پشتیبانی از WebSockets برای توسعه برنامه های چت بلادرنگ مناسب است.
بنابراین، WebSockets چیست و چگونه می توانید یک برنامه چت بلادرنگ در NestJS بسازید؟
WebSockets چیست؟
WebSocket ها پروتکلی برای ارتباط دائمی، بلادرنگ و دو طرفه بین مشتری و سرور هستند.
برخلاف HTTP که وقتی یک چرخه درخواست بین کلاینت و سرور تکمیل میشود، اتصال بسته میشود، اتصال WebSocket باز نگه داشته میشود و حتی پس از بازگشت پاسخ برای یک درخواست، بسته نمیشود.
تصویر زیر تصویری از نحوه عملکرد یک ارتباط WebSocket بین سرور و کلاینت است:
برای برقراری ارتباط دو طرفه، کلاینت درخواست دست دادن WebSocket را به سرور ارسال می کند. هدرهای درخواست شامل یک کلید WebSocket ایمن (Sec-WebSocket-Key) و یک هدر Upgrade: WebSocket است که همراه با هدر Connection: Upgrade به سرور می گوید که پروتکل را از HTTP به WebSocket ارتقا دهد و اتصال را باز نگه دارد. یادگیری در مورد WebSockets در جاوا اسکریپت به درک بهتر این مفهوم کمک می کند.
ساخت یک API چت بلادرنگ با استفاده از ماژول NestJS WebSocket
Node.js دو پیاده سازی اصلی WebSockets را ارائه می دهد. اولین مورد ws است که WebSockets برهنه را پیاده سازی می کند. و مورد دوم socket.io است که ویژگی های سطح بالاتری را ارائه می دهد.
NestJS دارای ماژول هایی برای socket.io و ws است. این مقاله از ماژول socket.io برای ویژگی های WebSocket برنامه نمونه استفاده می کند.
کد مورد استفاده در این پروژه در a موجود است
مخزن GitHub
. توصیه می شود برای درک بهتر ساختار دایرکتوری و نحوه تعامل همه کدها با یکدیگر، آن را به صورت محلی کلون کنید.
راه اندازی و نصب پروژه
ترمینال خود را باز کنید و با استفاده از دستور nest new (به عنوان مثال nest new chat-app) یک برنامه NestJS جدید ایجاد کنید. این دستور یک دایرکتوری جدید ایجاد می کند که حاوی فایل های پروژه است. اکنون برای شروع فرآیند توسعه آماده هستید.
یک اتصال MongoDB را تنظیم کنید
برای تداوم پیام های چت در برنامه، به یک پایگاه داده نیاز دارید. این مقاله از پایگاه داده MongoDB برای برنامه NestJS ما استفاده می کند، و ساده ترین راه برای اجرا این است که یک خوشه MongoDB در فضای ابری راه اندازی کنید و URL MongoDB خود را دریافت کنید. URL را کپی کنید و آن را به عنوان متغیر MONGO_URI در فایل env. خود ذخیره کنید.
بعداً هنگامی که در MongoDB پرس و جو می کنید، به Mongoose نیز نیاز خواهید داشت. آن را با اجرای npm install mongoose در ترمینال خود نصب کنید.
در پوشه src فایلی به نام mongo.config.ts ایجاد کنید و کد زیر را در آن قرار دهید.
import { registerAs } from '@nestjs/config';
/**
* Mongo database connection config
*/
export default registerAs('mongodb', () => {
const { MONGO_URI } = process.env; // from .env file
return {
uri:`${MONGO_URI}`,
};
});
فایل main.ts پروژه شما باید به شکل زیر باشد:
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)
// log docs
const baseUrl = AppModule.getBaseUrl(app)
const url = `http://${baseUrl}:${AppModule.port}`
logger.log(`API Documentation available at ${url}/docs`);
}
bootstrap();
ساخت ماژول چت
برای شروع کار با ویژگی چت بلادرنگ، اولین قدم نصب بستههای NestJS WebSockets است. این کار را می توان با اجرای دستور زیر در ترمینال انجام داد.
npm install @nestjs/websockets @nestjs/platform-socket.io @types/socket.io
پس از نصب بسته ها، باید ماژول چت را با اجرای دستورات زیر تولید کنید
nest g module chats
nest g controller chats
nest g service chats
پس از تولید ماژول، مرحله بعدی ایجاد یک اتصال WebSockets در NestJS است. یک فایل chat.gateway.ts در داخل پوشه چت ایجاد کنید، اینجا جایی است که دروازه ای که پیام ها را ارسال و دریافت می کند پیاده سازی می شود.
کد زیر را در chat.gateway.ts قرار دهید.
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer()
server: Server;
// listen for send_message events
@SubscribeMessage('send_message')
listenForMessages(@MessageBody() message: string) {
this.server.sockets.emit('receive_message', message);
}
}
احراز هویت کاربران متصل
احراز هویت بخشی ضروری از برنامه های کاربردی وب است و برای یک برنامه چت تفاوتی ندارد. تابع تأیید اعتبار اتصالات مشتری به سوکت در chats.service.ts همانطور که در اینجا نشان داده شده است یافت می شود:
@Injectable()
export class ChatsService {
constructor(private authService: AuthService) {}
async getUserFromSocket(socket: Socket) {
let auth_token = socket.handshake.headers.authorization;
// get the token itself without "Bearer"
auth_token = auth_token.split(' ')[1];
const user = this.authService.getUserFromAuthenticationToken(
auth_token
);
if (!user) {
throw new WsException('Invalid credentials.');
}
return user;
}
}
متد getUserFromSocket از getUserFromAuthenticationToken استفاده می کند تا با استخراج توکن Bearer، کاربر وارد شده فعلی را از توکن JWT دریافت کند. تابع getUserFromAuthenticationToken در فایل auth.service.ts همانطور که در اینجا نشان داده شده است پیاده سازی می شود:
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);
}
}
هنگامی که متد handleConnection ChatGateway رابط OnGatewayConnection را پیاده سازی می کند، سوکت فعلی به عنوان یک پارامتر به getUserFromSocket ارسال می شود. این امکان دریافت پیام ها و اطلاعات مربوط به کاربر فعلی را فراهم می کند.
کد زیر این را نشان می دهد:
// chat.gateway.ts
@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
});
}
}
میتوانید فایلهای مربوط به سیستم احراز هویت در بالا را در آدرس زیر ارجاع دهید
مخزن GitHub
برای مشاهده کدهای کامل (از جمله واردات)، برای درک بهتر پیاده سازی.
گفتگوهای مداوم با پایگاه داده
برای اینکه کاربران بتوانند تاریخچه پیام خود را ببینند، به طرحی برای ذخیره پیام ها نیاز دارید. یک فایل جدید به نام message.schema.ts ایجاد کنید و کد زیر را در آن قرار دهید (به یاد داشته باشید که طرح کاربری خود را وارد کنید یا مخزن را بررسی کنید).
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 };
در زیر پیاده سازی خدمات برای ایجاد یک پیام جدید و دریافت همه پیام ها در chats.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')
}
}
MessageDto در فایل message.dto.ts در پوشه dto در دایرکتوری چت پیاده سازی می شود. شما همچنین می توانید آن را در مخزن پیدا کنید.
شما باید مدل پیام و طرحواره را به لیست واردات در chats.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 {}
در نهایت، کنترل کننده رویداد get_all_messages به کلاس ChatGateway در chat.gateway.ts اضافه می شود، همانطور که در کد زیر مشاهده می شود:
// imports...
@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
}
}
هنگامی که یک کلاینت (کاربر) متصل رویداد get_all_messages را منتشر می کند، تمام پیام های آنها بازیابی می شود و زمانی که send_message را ارسال می کنند، یک پیام ایجاد و در پایگاه داده ذخیره می شود و سپس به سایر کلاینت های متصل ارسال می شود.
پس از انجام تمام مراحل بالا، می توانید برنامه خود را با استفاده از npm run start:dev راه اندازی کنید و آن را با یک سرویس گیرنده WebSocket مانند Postman آزمایش کنید.
ساخت برنامه های بلادرنگ با NestJS
اگرچه فناوری های دیگری برای ساختن سیستم های بلادرنگ وجود دارد، اما WebSocket ها در بسیاری از موارد بسیار محبوب و آسان هستند و بهترین گزینه برای برنامه های چت هستند.
برنامههای بلادرنگ تنها به برنامههای چت محدود نمیشوند، نمونههای دیگر شامل برنامههای پخش ویدئو یا تماس و برنامههای آب و هوای زنده هستند و NestJS ابزار عالی برای ساخت برنامههای بلادرنگ ارائه میکند.