使用NestJS和PostgreSQL构建实时聊天应用程序

本教程的代码可以在我的GitHub存储库中找到。您可以按照步骤自由克隆它。开始吧!

什么是NestJS?

NestJS是一个节点。js框架,用于创建使用TypeScript的快速、可测试、可扩展、松散耦合的服务器端应用程序。它利用了强大的HTTP服务器框架,如Express或Fastfy。嵌套为节点添加了一层抽象。js框架,并向开发人员公开其API。它支持PostgreSQL和MySQL等数据库管理系统。NestJS还提供依赖注入WebSocket和Apigetaway。

什么是WebSocket?

WebSocket是一种通过单个TCP连接提供全双工通信信道的计算机通信协议。IETF于2011年将WebSocket协议标准化为RFC 6455。目前的规范称为HTML生活标准。与HTTP/HTTPS不同,WebSocket是有状态协议,这意味着在服务器和客户端之间建立的连接将是活动的,除非由服务器或客户端终止;一旦WebSocket连接的一端闭合,它就会延伸到另一端。

先决条件

本教程是一个实践演示。要继续操作,请确保已安装以下内容:

  • Arctype
  • NodeJS
  • PostgreSQL

项目设置

在开始编码之前,让我们先设置我们的NestJS项目和项目结构。我们将从创建项目文件夹开始。然后,打开终端并运行以下命令:

mkdir chatapp && cd chatapp

创建项目文件夹

然后使用以下命令安装NestJS CLI:

npm i -g @nestjs/cli

安装完成后,运行下面的命令构建一个NestJS项目。

nest new chat

选择您首选的npm包管理器。在本教程中,我们将使用npm并等待安装必要的软件包。安装完成后,安装WebSocket和Socket。io使用以下命令:

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

然后,使用以下命令创建网关应用程序:

nest g gateway app

现在,让我们运行下面的命令来启动服务器:

npm run start:dev 

设置Postgres数据库

现在,我们可以通过服务器设置设置Postgres数据库来存储用户记录。首先,我们将使用TypeORM(对象关系映射器)将数据库与应用程序连接起来。首先,我们需要使用以下步骤创建数据库。首先,切换到系统的Postgres用户帐户。

sudo su - postgres

然后,使用下面的命令创建一个新的用户帐户。

createuser --interactive

接下来,创建一个新数据库。您可以使用以下命令执行此操作:

createdb chat

现在,我们将连接到刚刚创建的数据库。首先,打开app.module.ts文件,并在导入数组[]中添加以下代码段:

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Chat } from './chat.entity';
imports: [
   TypeOrmModule.forRoot({
     type: 'postgres',
     host: 'localhost',
     username: '<USERNAME>',
     password: '<PASSWORD>',
     database: 'chat',
     entities: [Chat],
     synchronize: true,
   }),
   TypeOrmModule.forFeature([Chat]),
 ],
...

在上面的代码片段中,我们使用TypeOrmModule forRoot方法将应用程序连接到PostgresSQL数据库,并传入数据库凭据。将<用户名>和<密码>替换为您为聊天数据库创建的用户和密码。

创建我们的聊天实体

现在我们已经将应用程序连接到您的数据库,创建一个聊天实体来保存用户的消息。为此,创建一个chat.entity。ts文件,并添加下面的代码段:

import {
 Entity,
 Column,
 PrimaryGeneratedColumn,
 CreateDateColumn,
} from 'typeorm';
 
@Entity()
export class Chat {
 @PrimaryGeneratedColumn('uuid')
 id: number;
 
 @Column()
 email: string;
 
 @Column({ unique: true })
 text: string;
 
 @CreateDateColumn()
 createdAt: Date;
}

在上面的代码片段中,我们使用TypeOrm提供的Entity、Column、CreatedDateColumn和PrimaryGenerateColumn装饰符为聊天创建了列。

设置WebSocket

让我们在服务器中设置一个WebSocket连接以发送实时消息。首先,我们将使用下面的代码片段导入所需的模块。

import {
 SubscribeMessage,
 WebSocketGateway,
 OnGatewayInit,
 WebSocketServer,
 OnGatewayConnection,
 OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { AppService } from './app.service';
import { Chat } from './chat.entity';

在上面的代码片段中,我们导入了SubscribeMessage()以侦听来自客户端WebSocketGateway()的事件,该客户端将提供对套接字的访问。io;我们还导入了OnGatewayInit、OnGatewayConnection和OnGatewayDisconnect实例。此WebSocket实例使您能够了解应用程序的状态。例如,当服务器加入或断开聊天时,我们可以让服务器做一些事情。然后,我们导入了聊天实体和AppService,它公开了保存用户消息所需的方法。

@WebSocketGateway({
 cors: {
   origin: '*',
 },
})

为了使客户机能够与服务器通信,我们通过初始化WebSocketGateway来启用CORS。

export class AppGateway
 implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
 constructor(private appService: AppService) {}
 
 @WebSocketServer() server: Server;
 
 @SubscribeMessage('sendMessage')
 async handleSendMessage(client: Socket, payload: Chat): Promise<void> {
   await this.appService.createMessage(payload);
   this.server.emit('recMessage', payload);
 }
 
 afterInit(server: Server) {
   console.log(server);
   //Do stuffs
 }
 
 handleDisconnect(client: Socket) {
   console.log(`Disconnected: ${client.id}`);
   //Do stuffs
 }
 
 handleConnection(client: Socket, ...args: any[]) {
   console.log(`Connected ${client.id}`);
   //Do stuffs
 }
}

接下来,在AppGateWay类中,我们实现了上面导入的WebSocket实例。我们创建了一个构造函数方法,并绑定AppService以访问其方法。我们从WebSocketServer装饰器创建了一个服务器实例。

然后,我们使用@SubscribeMessage()实例和handleMessage()方法创建handleSendMessage,以将数据发送到客户端。

当一条消息从客户端发送到此函数时,我们将其保存在数据库中,并将消息发送回客户端的所有连接用户。我们还有许多其他方法可以尝试,比如afterInit,在客户端连接后触发,handleDisconnect,在用户断开连接时触发。handleConnection方法在用户加入连接时启动。

创建控制器/服务

现在,让我们创建服务和控制器来保存聊天并呈现静态页面。打开app.service。ts文件并使用以下代码段更新内容:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Chat } from './chat.entity';
 
@Injectable()
export class AppService {
 constructor(
   @InjectRepository(Chat) private chatRepository: Repository<Chat>,
 ) {}
 async createMessage(chat: Chat): Promise<Chat> {
   return await this.chatRepository.save(chat);
 }
 
 async getMessages(): Promise<Chat[]> {
   return await this.chatRepository.find();
 }
}

然后更新app.controller.ts文件,代码段如下:

import { Controller, Render, Get, Res } from '@nestjs/common';
import { AppService } from './app.service';
import { Chat } from './chat.entity';
 
@Controller()
export class AppController {
 constructor(private readonly appService: AppService) {}
 
 @Get('/chat')
 @Render('index')
 Home() {
   return;
 }
 
 @Get('/api/chat')
 async Chat(@Res() res) {
   const messages = await this.appService.getMessages();
   res.json(messages);
 }
}

在上面的代码片段中,我们创建了两个路由来呈现静态页面和用户的消息。

服务于我们的静态页面

现在,让我们配置应用程序以呈现静态文件和页面。为此,我们将实现服务器端渲染。首先,在你的主要。ts文件,使用以下命令将应用程序配置为静态服务器文件:

async function bootstrap() {
 ...
 app.useStaticAssets(join(__dirname, '..', 'static'));
 app.setBaseViewsDir(join(__dirname, '..', 'views'));
 app.setViewEngine('ejs');
 ...
}```
接下来,在src目录中创建静态和视图文件夹。在“视图”文件夹中,创建index.ejs文件并添加下面的代码段:
```language
<!DOCTYPE html>
<html lang="en">
 
<head>
 <!-- Required meta tags -->
 <meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 
 <!-- Bootstrap CSS -->
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
   integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
 
 <title>Let Chat</title>
</head>
 
<body>
 <nav class="navbar navbar-light bg-light">
   <div class="container-fluid">
     <a class="navbar-brand">Lets Chat</a>
   </div>
 </nav>
 <div class="container">
   <div class="mb-3 mt-3">
     <ul style="list-style: none" id="data-container"></ul>
   </div>
   <div class="mb-3 mt-4">
     <input class="form-control" id="email" rows="3" placeholder="Your Email" />
   </div>
   <div class="mb-3 mt-4">
     <input class="form-control" id="exampleFormControlTextarea1" rows="3" placeholder="Say something..." />
   </div>
 </div>
 <script src="https://cdn.socket.io/4.3.2/socket.io.min.js"
   integrity="sha384-KAZ4DtjNhLChOB/hxXuKqhMLYvx3b5MlT55xPEiNmREKRzeEm+RVPlTnAn0ajQNs"
   crossorigin="anonymous"></script>
 <script src="app.js"></script>
 <!-- Option 1: Bootstrap Bundle with Popper -->
 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
   integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
   crossorigin="anonymous"></script>
</body>
</html>

为了加快模板的速度,我们使用Bootstrap添加了一些样式。然后,我们添加了两个输入字段和一个无序列表来显示用户的消息。我们还包括了我们的应用程序。js文件,我们将在本节稍后创建该文件,以及到套接字的链接。io客户端。

现在创建一个应用程序。js文件并添加下面的代码片段:

const socket = io('http://localhost:3002');
const msgBox = document.getElementById('exampleFormControlTextarea1');
const msgCont = document.getElementById('data-container');
const email = document.getElementById('email');
 
//get old messages from the server
const messages = [];
function getMessages() {
 fetch('http://localhost:3002/api/chat')
   .then((response) => response.json())
   .then((data) => {
     loadDate(data);
     data.forEach((el) => {
       messages.push(el);
     });
   })
   .catch((err) => console.error(err));
}
getMessages();
 
//When a user press the enter key,send message.
msgBox.addEventListener('keydown', (e) => {
 if (e.keyCode === 13) {
   sendMessage({ email: email.value, text: e.target.value });
   e.target.value = '';
 }
});
 
//Display messages to the users
function loadDate(data) {
 let messages = '';
 data.map((message) => {
   messages += ` <li class="bg-primary p-2 rounded mb-2 text-light">
      <span class="fw-bolder">${message.email}</span>
      ${message.text}
    </li>`;
 });
 msgCont.innerHTML = messages;
}
 
//socket.io
//emit sendMessage event to send message
function sendMessage(message) {
 socket.emit('sendMessage', message);
}
//Listen to recMessage event to get the messages sent by users
socket.on('recMessage', (message) => {
 messages.push(message);
 loadDate(messages);
})

在上面的代码片段中,我们创建了一个套接字。io实例,并监听服务器上的事件以发送和接收来自服务器的消息。我们希望当用户默认加入聊天时,旧聊天可用。我们的应用程序应该像下面的屏幕截图:

使用Arctype查看用户数据

我们现在已经成功创建了聊天应用程序。首先,让我们使用Arctype查看用户的数据。首先,启动Arctype,单击MySQL选项卡,并输入以下MySQL凭据,如下面的屏幕截图所示:

然后,单击聊天表以显示用户的聊天消息,如下图所示:

测试应用程序

现在在两个不同的选项卡或窗口中打开应用程序,并尝试使用不同的电子邮件地址发送消息,如下面的屏幕截图所示:

此外,当您查看控制台时,您会看到用户加入和断开与服务器的连接时的日志,这由handleDisconnect和handleConnection方法处理。

结论

在本教程中,我们探索了如何使用Nestjs和PostgreSQL创建实时聊天应用程序。我们首先简要介绍了Nestjs和WebSocket。然后,我们创建了一个演示应用程序来演示实现。希望你得到了你想要的知识。也许您可以从Nestjs文档中了解更多关于WebSocket实现的信息,并添加扩展应用程序。

原文标题:Build a Real-Time Chat App With NestJS and PostgreSQL
原文作者:Ekekenta Odionyenfe
原文链接:https://dzone.com/articles/build-a-real-time-chat-application-with-nestjs-and-postgresql


免责声明:

1、本站资源由自动抓取工具收集整理于网络。

2、本站不承担由于内容的合法性及真实性所引起的一切争议和法律责任。

3、电子书、小说等仅供网友预览使用,书籍版权归作者或出版社所有。

4、如作者、出版社认为资源涉及侵权,请联系本站,本站将在收到通知书后尽快删除您认为侵权的作品。

5、如果您喜欢本资源,请您支持作者,购买正版内容。

6、资源失效,请下方留言,欢迎分享资源链接

文章评论

0条评论