Bangun API Obrolan Waktu Nyata Menggunakan WebSockets di NestJS

NestJS adalah framework populer untuk membangun aplikasi sisi server dengan Node.js. Dengan dukungannya untuk WebSockets, NestJS sangat cocok untuk mengembangkan aplikasi obrolan waktu nyata.


Jadi, apa itu WebSockets, dan bagaimana cara membuat aplikasi obrolan waktu nyata di NestJS?


Apa itu WebSocket?

WebSockets adalah protokol untuk komunikasi terus-menerus, real-time, dan dua arah antara klien dan server.

Tidak seperti di HTTP di mana koneksi ditutup saat siklus permintaan selesai antara klien dan server, koneksi WebSocket tetap terbuka dan tidak ditutup bahkan setelah respons dikembalikan untuk permintaan.

Gambar di bawah ini adalah visualisasi tentang cara kerja komunikasi WebSocket antara server dan klien:

Visualisasi komunikasi klien-server WebSocket

Untuk membangun komunikasi dua arah, klien mengirimkan permintaan jabat tangan WebSocket ke server. Header permintaan berisi kunci WebSocket yang aman (Sec-WebSocket-Key), dan sebuah Tingkatkan: WebSocket header yang bersama-sama dengan Koneksi: Tingkatkan header memberi tahu server untuk memutakhirkan protokol dari HTTP ke WebSocket, dan menjaga koneksi tetap terbuka. Mempelajari tentang WebSockets dalam JavaScript membantu memahami konsep dengan lebih baik.

Membangun API Obrolan Waktu Nyata Menggunakan Modul NestJS WebSocket

Node.js menyediakan dua implementasi utama WebSockets. Yang pertama adalah ws yang mengimplementasikan WebSockets telanjang. Dan yang kedua adalah socket.ioyang menyediakan lebih banyak fitur tingkat tinggi.

NestJS memiliki modul untuk keduanya socket.io Dan ws. Artikel ini menggunakan socket.io modul untuk fitur WebSocket aplikasi contoh.

Kode yang digunakan dalam proyek ini tersedia di a repositori GitHub. Anda disarankan untuk mengkloningnya secara lokal untuk lebih memahami struktur direktori dan melihat bagaimana semua kode berinteraksi satu sama lain.

Setup dan Instalasi Proyek

Buka terminal Anda dan hasilkan aplikasi NestJS baru menggunakan sarang baru perintah (mis sarang aplikasi obrolan baru). Perintah menghasilkan direktori baru yang berisi file proyek. Sekarang Anda siap untuk memulai proses pengembangan.

Siapkan Koneksi MongoDB

Untuk mempertahankan pesan obrolan di aplikasi, Anda memerlukan database. Artikel ini menggunakan database MongoDB untuk aplikasi NestJS kami, dan cara termudah untuk menjalankannya adalah menyiapkan cluster MongoDB di cloud dan mendapatkan URL MongoDB Anda. Salin URL dan simpan sebagai MONGO_URI variabel di Anda .env mengajukan.

Anda juga akan membutuhkan Mongoose nanti saat membuat kueri ke MongoDB. Instal dengan menjalankan npm instal luwak di terminal Anda.

Dalam src folder, buat file bernama mongo.config.ts dan rekatkan kode berikut ke dalamnya.

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


* Mongo database connection config
*/

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

proyek Anda main.ts file akan terlihat seperti ini:

 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();

Membangun Modul Obrolan

Untuk memulai fitur obrolan waktu nyata, langkah pertama adalah menginstal paket NestJS WebSockets. Ini dapat dilakukan dengan menjalankan perintah berikut di terminal.

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

Setelah menginstal paket, Anda perlu membuat modul obrolan dengan menjalankan perintah berikut

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

Setelah selesai membuat modul, langkah selanjutnya adalah membuat koneksi WebSockets di NestJS. Membuat obrolan.gateway.ts file di dalam obrolan folder, di sinilah gateway yang mengirim dan menerima pesan diimplementasikan.

Rekatkan kode berikut ke obrolan.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);
    }
}

Otentikasi Pengguna yang Terhubung

Otentikasi adalah bagian penting dari aplikasi web, dan tidak berbeda dengan aplikasi obrolan. Fungsi untuk mengautentikasi koneksi klien ke soket ditemukan di obrolan.layanan.ts seperti yang ditunjukkan di sini:

 @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;
    }
}

Itu getUserFromSocket penggunaan metode getUserFromAuthenticationToken untuk mendapatkan pengguna yang saat ini masuk dari token JWT dengan mengekstraksi token Bearer. Itu getUserFromAuthenticationToken fungsi diimplementasikan dalam auth.service.ts file seperti yang ditunjukkan di sini:

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

Soket saat ini diteruskan sebagai parameter ke getUserFromSocket ketika handleConnection metode dari ChatGateway menerapkan OnGatewayConnection antarmuka. Ini memungkinkan untuk menerima pesan dan informasi tentang pengguna yang saat ini terhubung.

Kode di bawah ini menunjukkan ini:

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

Anda dapat mereferensikan file yang terlibat dalam sistem autentikasi di atas di repositori GitHub untuk melihat kode lengkap (termasuk impor), untuk pemahaman implementasi yang lebih baik.

Pertahankan Obrolan ke Basis Data

Agar pengguna dapat melihat histori perpesanan mereka, Anda memerlukan skema untuk menyimpan pesan. Buat file baru bernama pesan.skema.ts dan rekatkan kode di bawah ini ke dalamnya (ingat untuk mengimpor file skema pengguna atau periksa repositori untuk satu).

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

Di bawah ini adalah implementasi layanan untuk membuat pesan baru dan memasukkan semua pesan obrolan.layanan.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')
    }
}

Itu MessageDto diimplementasikan dalam a pesan.dto.ts berkas di dto folder di obrolan direktori. Anda juga dapat menemukannya di repositori.

Anda perlu menambahkan pesan model dan skema ke daftar impor di 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 {}

Akhirnya, dapatkan_semua_pesan event handler ditambahkan ke ChatGateway kelas di obrolan.gateway.ts seperti yang terlihat pada kode berikut:

 

@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
    }
}

Ketika klien (pengguna) yang terhubung memancarkan dapatkan_semua_pesan acara, semua pesan mereka akan diambil, dan ketika mereka memancarkan mengirim pesan, pesan dibuat dan disimpan dalam database, lalu dikirim ke semua klien lain yang terhubung.

Setelah selesai dengan semua langkah di atas, Anda dapat mulai menggunakan aplikasi Anda npm jalankan mulai: devdan uji dengan klien WebSocket seperti Postman.

Membangun Aplikasi Real-Time Dengan NestJS

Meskipun ada teknologi lain untuk membangun sistem real-time, WebSockets sangat populer dan mudah diimplementasikan dalam banyak kasus, dan merupakan opsi terbaik untuk aplikasi obrolan.

Aplikasi waktu nyata tidak hanya terbatas pada aplikasi obrolan, contoh lain termasuk streaming video atau aplikasi panggilan, dan aplikasi cuaca langsung, dan NestJS menyediakan alat yang hebat untuk membuat aplikasi waktu nyata.

Tags

Related Post