nodejs入门_如何在NodeJS中使用套接字创建专业的Chat API解决方案[入门级]

nodejs入门

Have you ever wondered how chat applications work behind the scenes? Well, today I am going to walk you through how to make a REST + Sockets-based application built on top of NodeJS/ExpressJS using MongoDB.

您是否想过聊天应用程序在后台如何工作? 好吧,今天,我将向您介绍如何使用MongoDBNodeJS / ExpressJS之上构建基于REST +套接字的应用程序。

I have been working on the content for this article for over a week now – I really hope it helps someone out there.

我一直在研究本文的内容已有一个多星期了,我真的希望它能对那里的人们有所帮助。

先决条件 (Prerequisites)

我们将讨论的主题 (Topics we'll cover)

一般 (General)

  • Create an express server

    创建快递服务器
  • How to do API validations

    如何进行API验证
  • Create basic skeleton for the entire application

    为整个应用程序创建基本框架
  • Setting up MongoDB (installation, setup in express)

    设置MongoDB(安装,在Express中设置)
  • Creating users API + Database (Create a user, Get a user by id, Get all users, Delete a user by id)

    创建用户API +数据库(创建用户,按ID获取用户,获取所有用户,按ID删除用户)
  • Understanding what a middleware is

    了解什么是中间件
  • JWT (JSON web tokens) authentication (decode/encode) - Login middleware

    JWT(JSON Web令牌)认证(解码/编码)-登录中间件
  • Web socket class that handles events when a user disconnects, adds its identity, joins a chat room, wants to mute a chat room

    Web套接字类,可在用户断开连接时处理事件,添加其身份,加入聊天室,想要使聊天室静音
  • Discussing chat room & chat message database model

    讨论聊天室和聊天消息数据库模型

对于API (For the API)

  • Initiate a chat between users

    在用户之间发起聊天
  • Create a message in chat room

    在聊天室中创建消息
  • See conversation for a chat room by its id

    通过ID查看聊天室的对话
  • Mark an entire conversation as read (similar to Whatsapp)

    将整个对话标记为已读(类似于Whatsapp)
  • Get recent conversation from all chats (similar to Facebook messenger)

    从所有聊天中获取最近的对话(类似于Facebook Messenger)

奖金-API (Bonus  - API    )

  • Delete a chat room by id along with all its associated messages

    按ID删除聊天室及其所有关联消息
  • Delete a message by id

    按ID删除邮件

Before we begin, I wanted to touch on some basics in the following videos.

在开始之前,我想介绍以下视频中的一些基础知识。

了解ExpressJS的基础 (Understanding the basics of ExpressJS)

What are routes? Controllers? How do we allow for CORS (cross origin resource sharing)? How do we allow enduser to send data in JSON format in API request?

什么是路线? 控制器? 我们如何允许CORS(跨源资源共享)? 我们如何允许最终用户在API请求中以JSON格式发送数据?

I talk about all this and more (including REST conventions) in this video:

我在视频中谈到了所有这些以及更多内容(包括REST约定):

Also, here's a GitHub link to the entire source code of this video [Chapter 0]

另外,这是该视频的完整源代码的GitHub链接 [第0章]

Do have a look at the README.md for "Chapter 0" source code. It has all the relevant learning links I mention in the video along with an amazing half hour tutorial on postman.

请查看“第0章”源代码的README.md。 它包含了我在视频中提到的所有相关学习链接,以及有关邮递员的令人惊叹的半小时教程。

将API验证添加到您的API端点 (Adding API validation to your API end-point )

In the below video, you'll learn how to write your own custom validation using a library called "make-validation":

在下面的视频中,您将学习如何使用名为“ make-validation”的库编写自己的自定义验证:

Here's the GitHub link to the entire source code of this video [Chapter 0].

是该视频的完整源代码GitHub链接 [第0章]。

And here's the make-validation library link [GitHub][npm][example].

这是制作验证库链接[G itHub ] [ npm ] [ 示例 ]。

The entire source code of this tutorial can be found here. If you have any feedback, please just reach out to me on http://twitter.com/adeelibr. If you like this tutorial kindly leave a star on the github repository.

本教程的完整源代码可以在这里找到。 如果您有任何反馈意见,请访问http://twitter.com/adeelibr与我联系。 如果您喜欢本教程,请在github存储库上加一个星号

Let's begin now that you know the basics of ExpressJS and how to validate a user response.

现在,让我们开始了解ExpressJS的基础知识以及如何验证用户响应。

入门 (Getting started)

Create a folder called chat-app:

创建一个名为chat-app的文件夹:

mkdir chat-app;
cd chat-app;

Next initialize a new npm project in your project root folder by typing the following:

接下来,通过键入以下命令在项目根文件夹中初始化一个新的npm项目:

npm init -y

and install the following packages:

并安装以下软件包:

npm i cors @withvoid/make-validation express jsonwebtoken mongoose morgan socket.io uuid --save;
npm i nodemon --save-dev;

And in your package.json scripts section add the following 2 scripts:

然后在您的package.json scripts部分中添加以下2个脚本:

"scripts": {
	"start": "nodemon server/index.js",
	"start:server": "node server/index.js"
},

Your package.json now should look something like this:

您的package.json现在应如下所示:

{
  "name": "chapter-1-chat",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "nodemon server/index.js",
    "start:server": "node server/index.js"
  },
  "dependencies": {
    "@withvoid/make-validation": "1.0.5",
    "cors": "2.8.5",
    "express": "4.16.1",
    "jsonwebtoken": "8.5.1",
    "mongoose": "5.9.18",
    "morgan": "1.9.1",
    "socket.io": "2.3.0",
    "uuid": "8.1.0"
  },
  "devDependencies": {
    "nodemon": "2.0.4"
  }
}

Awesome!

太棒了!

Now in your project's root folder create a new folder called server:

现在,在项目的根文件夹中,创建一个名为server的新文件夹:

cd chat-app;
mkdir server;
cd server;

Inside your server folder create a file called index.js and add the following content to it:

server文件夹中,创建一个名为index.js的文件,并向其中添加以下内容:

import http from "http";
import express from "express";
import logger from "morgan";
import cors from "cors";
// routes
import indexRouter from "./routes/index.js";
import userRouter from "./routes/user.js";
import chatRoomRouter from "./routes/chatRoom.js";
import deleteRouter from "./routes/delete.js";
// middlewares
import { decode } from './middlewares/jwt.js'

const app = express();

/** Get port from environment and store in Express. */
const port = process.env.PORT || "3000";
app.set("port", port);

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use("/", indexRouter);
app.use("/users", userRouter);
app.use("/room", decode, chatRoomRouter);
app.use("/delete", deleteRouter);

/** catch 404 and forward to error handler */
app.use('*', (req, res) => {
  return res.status(404).json({
    success: false,
    message: 'API endpoint doesnt exist'
  })
});

/** Create HTTP server. */
const server = http.createServer(app);
/** Listen on provided port, on all network interfaces. */
server.listen(port);
/** Event listener for HTTP server "listening" event. */
server.on("listening", () => {
  console.log(`Listening on port:: http://localhost:${port}/`)
});

Let's add the routes for indexRouter userRouter chatRoomRouter & deleteRouter.

让我们为indexRouter userRouter chatRoomRouterdeleteRouter添加路由。

In your project's root folder create a folder called routes. Inside the routes folder add the following files:

在项目的根文件夹中,创建一个名为routes的文件夹。 在routes文件夹内,添加以下文件:

  • index.js

    index.js

  • user.js

    user.js

  • chatRoom.js

    chatRoom.js

  • delete.js

    delete.js

Let's add content for routes/index.js first:

让我们首先添加routes/index.js内容:

import express from 'express';
// controllers
import users from '../controllers/user.js';
// middlewares
import { encode } from '../middlewares/jwt.js';

const router = express.Router();

router
  .post('/login/:userId', encode, (req, res, next) => { });

export default router;

Let's add content for routes/user.js next:

接下来让我们为routes/user.js添加内容:

import express from 'express';
// controllers
import user from '../controllers/user.js';

const router = express.Router();

router
  .get('/', user.onGetAllUsers)
  .post('/', user.onCreateUser)
  .get('/:id', user.onGetUserById)
  .delete('/:id', user.onDeleteUserById)

export default router;

And now let's add content for routes/chatRoom.js:

现在让我们为routes/chatRoom.js添加内容:

import express from 'express';
// controllers
import chatRoom from '../controllers/chatRoom.js';

const router = express.Router();

router
  .get('/', chatRoom.getRecentConversation)
  .get('/:roomId', chatRoom.getConversationByRoomId)
  .post('/initiate', chatRoom.initiate)
  .post('/:roomId/message', chatRoom.postMessage)
  .put('/:roomId/mark-read', chatRoom.markConversationReadByRoomId)

export default router;

Finally, let's add content for routes/delete.js:

最后,让我们为routes/delete.js添加内容:

import express from 'express';
// controllers
import deleteController from '../controllers/delete.js';

const router = express.Router();

router
  .delete('/room/:roomId', deleteController.deleteRoomById)
  .delete('/message/:messageId', deleteController.deleteMessageById)

export default router;

Awesome now that our routes are in place let's add the controllers for each route.

现在我们的路由已经到位,让我们为每个路由添加控制器。

Create a new folder called controllers. Inside that folder create the following files:

创建一个名为controllers的新文件夹。 在该文件夹中,创建以下文件:

  • user.js

    user.js

  • chatRoom.js

    chatRoom.js

  • delete.js

    delete.js

Let's start of with controllers/user.js:

让我们从controllers/user.js

export default {
  onGetAllUsers: async (req, res) => { },
  onGetUserById: async (req, res) => { },
  onCreateUser: async (req, res) => { },
  onDeleteUserById: async (req, res) => { },
}

Next let's add content in controllers/chatRoom.js:

接下来,让我们在controllers/chatRoom.js添加内容:

export default {
  initiate: async (req, res) => { },
  postMessage: async (req, res) => { },
  getRecentConversation: async (req, res) => { },
  getConversationByRoomId: async (req, res) => { },
  markConversationReadByRoomId: async (req, res) => { },
}

And finally let's add content for controllers/delete.js:

最后,让我们为controllers/delete.js添加内容:

export default {
  deleteRoomById: async (req, res) => {},
  deleteMessageById: async (req, res) => {},
}

So far we have added empty controllers for each route, so they don't do much yet. We'll add functionality in a bit.

到目前为止,我们已经为每个路由添加了空控制器,因此它们还没有做太多事情。 我们将稍后添加功能。

Just one more thing – let's add a new folder called middlewares and inside that folder create a file called jwt.js. Then add the following content to it:

只是一件事-让我们添加一个名为middlewares的新文件夹,并在该文件夹内创建一个名为jwt.js的文件。 然后向其中添加以下内容:

import jwt from 'jsonwebtoken';

export const decode = (req, res, next) => {}

export const encode = async (req, res, next) => {}

I will talk about what this file does in a bit, so for now let's just ignore it.

我将稍后讨论该文件的功能,所以现在让我们忽略它。

We have ended up doing the following:

我们最终做了以下工作:

  • Created an Express server that listens on port 3000

    创建了一个侦听端口3000的Express服务器
  • Added cross-origin-resource (CORS) to our server.js

    在我们的server.js添加了跨源资源(CORS)

  • Added a logger to our server.js

    在我们的server.js添加了一个记录器

  • And also added route handlers with empty controllers.

    并且还添加了带有空控制器的路由处理程序。

Nothing fancy so far that I haven't covered in the videos above.

到目前为止,上面的视频都还没有介绍我。

让我们在应用程序中设置MongoDB (Let's setup MongoDB in our application)

Before we add MongoDB to our code base, make sure it is installed in your machine by running one of the following:

在将MongoDB添加到我们的代码库之前,请通过运行以下操作之一确保它已安装在您的计算机中:

If you are having issues installing MongoDB, just let me know at https://twitter.com/adeelibr and I'll write a custom guide for you or make an installation video guide. :)

如果您在安装MongoDB时遇到问题,请通过https://twitter.com/adeelibr告诉我,我将为您编写自定义指南或制作安装视频指南。 :)

I am using Robo3T as my MongoDB GUI.

我正在使用Robo3T 作为我的MongoDB GUI。

Now you should have your MongoDB instance running and Robo3T installed. (You can use any GUI client that you like for this. I like Robo3T a lot so I'm using it. Also, it's open source.)

现在您应该运行MongoDB实例并运行Robo3T 已安装。 (您可以为此使用任何GUI客户端。我喜欢Robo3T 很多,所以我正在使用它。 此外,它是开源的。)

Here is a small video I found on YouTube that gives a 6 minute intro to Robo3T:

这是我在YouTube上找到的一个小视频,向您介绍了Robo3T的6分钟介绍:

Once your MongoDB instance is up and running let's begin integrating MongoDB in our code as well.

一旦您的MongoDB实例启动并运行,让我们也开始将MongoDB集成到我们的代码中。

In your root folder create a new folder called config. Inside that folder create a file called index.js and add the following content:

在您的根文件夹中,创建一个名为config的新文件夹。 在该文件夹中,创建一个名为index.js的文件,并添加以下内容:

const config = {
  db: {
    url: 'localhost:27017',
    name: 'chatdb'
  }
}

export default config

Usually the default port that MongoDB instances will run on is 27017.

通常, MongoDB实例将在其上运行的默认端口是27017

Here we set info about our database URL (which is in db) and the name of database which is chatdb (you can call this whatever you want).

在这里,我们设置有关数据库URL的信息(位于db )和name chatdb的数据库的name (您可以随意调用此名称)。

Next create a new file called config/mongo.js and add the following content:

接下来,创建一个名为config/mongo.js的新文件,并添加以下内容:

import mongoose from 'mongoose'
import config from './index.js'

const CONNECTION_URL = `mongodb://${config.db.url}/${config.db.name}`

mongoose.connect(CONNECTION_URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

mongoose.connection.on('connected', () => {
  console.log('Mongo has connected succesfully')
})
mongoose.connection.on('reconnected', () => {
  console.log('Mongo has reconnected')
})
mongoose.connection.on('error', error => {
  console.log('Mongo connection has an error', error)
  mongoose.disconnect()
})
mongoose.connection.on('disconnected', () => {
  console.log('Mongo connection is disconnected')
})

Next import config/mongo.js in your server/index.js file like this:

接下来像这样在您的server/index.js文件中导入config/mongo.js

.
.
// mongo connection
import "./config/mongo.js";
// routes
import indexRouter from "./routes/index.js";

If you get lost at any time, the entire source code for this tutorial is right here.

如果您随时迷路,本教程的整个源代码都在这里

Let's discuss what we are doing here step by step:

让我们一步一步地讨论我们在做什么:

We first import our config.js file in config/mongo.js. Next we pass in the value to our CONNECTION_URL like this:

我们首先将config.js文件导入config/mongo.js 。 接下来,我们将值传递给我们的CONNECTION_URL如下所示:

const CONNECTION_URL = `mongodb://${config.db.url}/${config.db.name}`

Then using the CONNECTION_URL we form a Mongo connection, by doing this:

然后使用CONNECTION_URL我们通过以下步骤形成一个Mongo连接:

mongoose.connect(CONNECTION_URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

This tells mongoose to make a connection with the database with our Node/Express application.

这告诉mongoose通过我们的Node / Express应用程序与数据库建立连接。

The options we are giving Mongo here are:

我们在这里为Mongo提供的选项有:

  • useNewUrlParser: MongoDB driver has deprecated their current connection string parser. useNewUrlParser: true tells mongoose to use the new parser by Mongo. (If it's set to true, we have to provide a database port in the CONNECTION_URL.)

    useNewUrlParser :MongoDB驱动程序已弃用其当前的连接字符串解析器。 useNewUrlParser: true告诉猫鼬使用Mongo的新解析器。 (如果将其设置为true,则必须在CONNECTION_URL提供数据库端口。)

  • useUnifiedTopology: False by default. Set to true to opt in to using MongoDB driver's new connection management engine. You should set this option to true, except for the unlikely case that it prevents you from maintaining a stable connection.

    useUnifiedTopology :默认情况下为False。 设置为true以选择使用MongoDB驱动程序的新连接管理引擎 。 您应该将此选项设置为true ,除非极少数情况会阻止您保持稳定的连接。

Next we simply add mongoose event handlers like this:

接下来,我们简单地添加mongoose事件处理程序,如下所示:

mongoose.connection.on('connected', () => {
  console.log('Mongo has connected succesfully')
})
mongoose.connection.on('reconnected', () => {
  console.log('Mongo has reconnected')
})
mongoose.connection.on('error', error => {
  console.log('Mongo connection has an error', error)
  mongoose.disconnect()
})
mongoose.connection.on('disconnected', () => {
  console.log('Mongo connection is disconnected')
})
  • connected will be called once the database connection is established

    建立数据库连接后将调用connected

  • disconnected will be called when your Mongo connection is disabled

    Mongo连接被禁用时,将disconnected连接

  • error is called if there is an error connecting to your Mongo database

    如果连接到您的Mongo数据库有error则调用error

  • reconnected event is called when the database loses connection and then makes an attempt to successfully reconnect.

    当数据库断开连接,然后尝试成功重新连接时,将调用reconnected事件。

Once you have this in place, simply go in your server/index.js file and import config/mongo.js. And that is it. Now when you start up your server by typing this:

完成此操作后,只需进入server/index.js文件并导入config/mongo.js 。 就是这样。 现在,当您通过键入以下内容启动服务器时:

npm start;

You should see something like this:

您应该会看到以下内容:

If you see this you have successfully added Mongo to your application.

如果看到此消息,则说明您已成功将Mongo添加到您的应用程序中。

Congratulations!

恭喜你!

If you got stuck here for some reason, let me know at twitter.com/adeelibr and I will try to sort it out for you. :)

如果您由于某种原因而被卡在这里, 请通过twitter.com/adeelibr告诉我,我将尽力为您解决。 :)

让我们为用户/设置第一个API部分/ (Let's setup our first API section for users/)

The setup of our API for users/ will have no authentication token for this tutorial, because my main focus is to teach you about the Chat application here.

在本教程中,针对users/的API的设置将没有身份验证令牌,因为我的主要重点是在此处向您介绍聊天应用程序。

用户模态方案 (User Modal Scheme)

Let's create our first model (database scheme) for the user collection.

让我们为user集合创建第一个模型(数据库方案)。

Create a new folder called models. Inside that folder create a file called User.js and add the following content:

创建一个名为models的新文件夹。 在该文件夹中,创建一个名为User.js的文件,并添加以下内容:

import mongoose from "mongoose";
import { v4 as uuidv4 } from "uuid";

export const USER_TYPES = {
  CONSUMER: "consumer",
  SUPPORT: "support",
};

const userSchema = new mongoose.Schema(
  {
    _id: {
      type: String,
      default: () => uuidv4().replace(/\-/g, ""),
    },
    firstName: String,
    lastName: String,
    type: String,
  },
  {
    timestamps: true,
    collection: "users",
  }
);

export default mongoose.model("User", userSchema);

Let's break this down into pieces:

让我们将其分解为几部分:

export const USER_TYPES = {
  CONSUMER: "consumer",
  SUPPORT: "support",
};

We are basically going to have 2 types of users, consumer and support. I have written it this way because I want to programmatically ensure API and DB validation, which I will talk about later.

我们基本上将拥有两种类型的用户: consumersupport 。 我之所以这样写,是因为我想以编程方式确保API和DB验证,这将在后面讨论。

Next we create a schema on how a single document (object/item/entry/row) will look inside our user collection (a collection is equivalent to a MySQL table). We define it like this:

接下来,我们创建一个模式,说明单个document (对象/项目/条目/行)在user集合中的外观(一个集合等效于一个MySQL表)。 我们这样定义它:

const userSchema = new mongoose.Schema(
  {
    _id: {
      type: String,
      default: () => uuidv4().replace(/\-/g, ""),
    },
    firstName: String,
    lastName: String,
    type: String,
  },
  {
    timestamps: true,
    collection: "users",
  }
);

Here we are telling mongoose that for a single document in our users collection we want the structure to be like this:

在这里,我们告诉mongoose ,对于我们的users集中的单个文档,我们希望结构如下所示:

{
	id: String // will get random string by default thanks to uuidv4
    	firstName: String,
    	lastName: String,
    	type: String // this can be of 2 types consumer/support
}

In the second part of the schema we have something like this:

在模式的第二部分,我们有如下内容:

{
    timestamps: true,
    collection: "users",
}

Setting timestamps to true will add 2 things to my schema: a createdAt and a updatedAt date value. Every time when we create a new entry the createdAt will be updated automatically and updatedAt will update once we update an entry in the database using mongoose. Both of these are done automatically by mongoose.

设置timestampstrue将增加2个东西我的架构:一个createdAtupdatedAt日期值。 当我们创建一个新条目每次createdAt将自动更新和updatedAt一旦我们更新使用猫鼬数据库中的条目将更新。 这两种都是mongoose自动完成的。

The second part is collection. This shows what my collection name will be inside my database. I am assigning it the name of users.

第二部分是collection 。 这显示了我的集合名称将在数据库中。 我给它分配了users名。

And then finally we'll export the object like this:

最后,我们将像这样导出对象:

export default mongoose.model("User", userSchema);

So mongoose.model takes in 2 parameters here.

因此mongoose.model在这里接受2个参数。

  • The name of the model, which is User here

    模型的名称,即User此处

  • The schema associated with that model, which is userSchema in this case

    与该模型关联的模式,在这种情况下为userSchema

Note: Based on the name of the model, which is User in this case, we don't add collection key in the schema section. It will take this User name and append an s to it and create a collection by its name, which becomes user.

注意:基于模型的名称(在这种情况下为User ,我们不会在模式部分中添加collection键。 它将使用该User名并在其后附加s ,并通过其名称创建一个集合,该集合将成为user

Great, now we have our first model.

太好了,现在我们有了第一个模型。

If you've gotten stuck anywhere, just have a look at the source code.

如果您被困在任何地方,请看一下源代码

创建一个新的用户API [POST请求] (Create a new user API [POST request] )

Next let's write our first controller for this route: .post('/', user.onCreateUser).

接下来,让我们为该路由编写第一个控制器: .post('/', user.onCreateUser)

Go inside controllers/user.js and import 2 things at the top:

进入controllers/user.js并在顶部导入两件事:

// utils
import makeValidation from '@withvoid/make-validation';
// models
import UserModel, { USER_TYPES } from '../models/User.js';

Here we are importing the validation library that I talked about in the video at the very top. We are also importing our user modal along with the USER_TYPES from the same file.

在这里,我们将导入我在视频最顶部讨论过的验证库。 我们还将从同一文件中导入用户模式以及USER_TYPES

This is what USER_TYPES represents:

这是USER_TYPES代表的:

export const USER_TYPES = {
  CONSUMER: "consumer",
  SUPPORT: "support",
};

Next find the controller onCreateUser and add the following content to it:

接下来找到控制器onCreateUser并向其中添加以下内容:

onCreateUser: async (req, res) => {
    try {
      const validation = makeValidation(types => ({
        payload: req.body,
        checks: {
          firstName: { type: types.string },
          lastName: { type: types.string },
          type: { type: types.enum, options: { enum: USER_TYPES } },
        }
      }));
      if (!validation.success) return res.status(400).json(validation);

      const { firstName, lastName, type } = req.body;
      const user = await UserModel.createUser(firstName, lastName, type);
      return res.status(200).json({ success: true, user });
    } catch (error) {
      return res.status(500).json({ success: false, error: error })
    }
  },

Let's divide this into 2 sections.

让我们将其分为2个部分。

First we validate the user response by doing this:

首先,我们通过执行以下操作来验证用户响应:

const validation = makeValidation(types => ({
  payload: req.body,
  checks: {
    firstName: { type: types.string },
    lastName: { type: types.string },
    type: { type: types.enum, options: { enum: USER_TYPES } },
  }
}));
if (!validation.success) return res.status(400).json({ ...validation });

Please make sure that you have seen the video (above) on validate an API request in Node using custom validation or by using make-validation library.

请确保您已观看视频(以上),该视频validate an API request in Node using custom validation or by using make-validation library

Here we are using the make-validation library (that I ended up making while writing this tutorial). I talk about it's usage in the video at the start of this tutorial.

在这里,我们使用了make-validation库(我在编写本教程时最终完成了该库)。 我将在本教程开始的视频中谈论它的用法。

All we are doing here is passing req.body to payload. Then in the checks we're adding an object where against each key we are telling what are the requirements for each type, for example:

我们在这里所做的只是将req.body传递给payload 。 然后在检查中添加一个对象,针对每个key在其中告诉每种类型的要求,例如:

firstName: { type: types.string },

Here we are telling it that firstName is of type string. If the user forgets to add this value while hitting the API, or if the type is not string, it will throw an error.

在这里,我们告诉它firstName是字符串类型。 如果用户在点击API时忘记添加此值,或者类型不是字符串,则将引发错误。

The validation variable will return an object with 3 things: {success: boolean, message: string, errors: object}.

validation变量将返回一个包含3个对象的对象: {success: boolean, message: string, errors: object}

If validation.success is false we simply return everything from the validation and give it to the user with a status code of 400.

如果validation.success为false,我们只需返回验证中的所有内容,并将状态代码为400给予用户。

Once our validation is in place and we know that the data we are getting are valid, then we do the following:

验证到位并且我们知道所获取的数据有效之后,我们将执行以下操作:

const { firstName, lastName, type } = req.body;
const user = await UserModel.createUser(firstName, lastName, type);
return res.status(200).json({ success: true, user });

Then we destruct firstName, lastName, type from req.body and pass those values to our UserModel.createUser. If everything goes right, it simply returns success: true with the new user created along with a status 200.

然后,我们销毁firstName, lastName, typereq.body firstName, lastName, type并将这些值传递给我们的UserModel.createUser 。 如果一切顺利,则只返回success: true创建新user以及状态为200 success: true

If anywhere in this process anything goes wrong, it throws an error and goes to the catch block:

如果此过程中的任何地方出了问题,它将引发错误并转到catch块:

catch (error) {
  return res.status(500).json({ success: false, error: error })
}

There we simply return an error message along with the HTTP status 500.

在那里,我们仅返回一条错误消息以及HTTP状态500

The only thing we are missing here is the UserModel.createUser() method.

我们在这里唯一缺少的是UserModel.createUser()方法。

So let's go back into our models/User.js file and add it:

因此,让我们回到我们的models/User.js文件并添加它:

userSchema.statics.createUser = async function (
	firstName, 
    	lastName, 
    	type
) {
  try {
    const user = await this.create({ firstName, lastName, type });
    return user;
  } catch (error) {
    throw error;
  }
}


export default mongoose.model("User", userSchema);

So all we are doing here is adding a static method to our userSchema called createUser that takes in 3 parameters: firstName, lastName, type.

因此,我们在此处所做的就是在userSchema添加一个名为createUser的静态方法,该方法userSchema 3个参数: firstName, lastName, type

Next we use this:

接下来我们使用这个:

const user = await this.create({ firstName, lastName, type });

Here the this part is very important, since we are writing a static method on userSchema. Writing this will ensure that we are using performing operations on the userSchema object

在这里, this部分非常重要,因为我们正在userSchema上编写静态方法。 编写this将确保我们正在使用对userSchema对象执行操作

One thing to note here is that userSchema.statics.createUser = async function (firstName, lastName, type) => {} won't work. If you use an => arrow function the this context will be lost and it won't work.

这里要注意的一件事是userSchema.statics.createUser = async function (firstName, lastName, type) => {}将不起作用。 如果使用=>箭头函数,则this上下文将丢失并且将无法使用。

If you want to learn more about static methods in mongoose, see this very short but helpful doc example here.

如果您想了解有关Mongoose中static方法的更多信息,请在此处查看此简短但有用的文档示例。

Now that we have everything set up, let's start our terminal by running the following command in the project's root folder:

现在我们已经完成了所有设置,让我们在项目的根文件夹中运行以下命令来启动终端:

npm start;

Go into postman, set up a POST request on this API http://localhost:3000/users, and add the following body to the API:

进入邮递员,在此API http://localhost:3000/users上设置POST请求,并将以下正文添加到API:

{
	firstName: 'John'
    	lastName: 'Doe',
    	type: 'consumer'
}

Like this:

像这样:

You can also get the entire postman API collection from here so that you don't have to write the APIs again and again.

您还可以从此处获取整个邮递员API集合,这样就不必一次又一次地编写API。

Awesome – we just ended up creating our first API. Let's create a couple more user APIs before we move to the chat part because there is no chat without users (unless we have robots, but robots are users as well 🧐).

太棒了–我们刚刚创建了第一个API。 在转到聊天部分之前,让我们创建更多的用户API,因为没有用户就不会聊天(除非我们有机器人,但机器人也是用户🧐)。

通过其ID API获取用户[获取请求] (Get a user by its ID API [GET request] )

Next we need to write an API that gets us a user by its ID. So for our route .get('/:id', user.onGetUserById) let's write down its controller.

接下来,我们需要编写一个API,通过其ID为我们吸引用户。 因此,对于我们的路由.get('/:id', user.onGetUserById)我们写下它的控制器。

Go to controllers/user.js and for the method onGetUserById write this:

转到controllers/user.js并为onGetUserById方法编写以下代码:

onGetUserById: async (req, res) => {
  try {
    const user = await UserModel.getUserById(req.params.id);
    return res.status(200).json({ success: true, user });
  } catch (error) {
    return res.status(500).json({ success: false, error: error })
  }
},

Cool, this looks straightforward. Let's add UserModel.getUserById() in our models/User.js file.

很酷,这看起来很简单。 让我们在models/User.js文件中添加UserModel.getUserById()

Add this method below the last static method you wrote:

将此方法添加到您最后编写的static方法下面:

userSchema.statics.getUserById = async function (id) {
  try {
    const user = await this.findOne({ _id: id });
    if (!user) throw ({ error: 'No user with this id found' });
    return user;
  } catch (error) {
    throw error;
  }
}

We pass in an id parameter and we wrap our function in try/catch. This is very important when you are using async/await. The lines to focus on here are these 2:

我们传入一个id参数,然后将函数包装在try/catch 。 当您使用async/await时,这非常重要。 这里重点介绍以下几行:

const user = await this.findOne({ _id: id });
if (!user) throw ({ error: 'No user with this id found' });

We use mongoose's  findOne method to find an entry by id. We know that only one item exists in the collection by this id because the id is unique. If no user is found we simply throw an error with the message No user with this id found.

我们使用mongoosefindOne方法通过id查找条目。 我们知道,此id中的集合中仅存在一项,因为该id是唯一的。 如果未找到用户,我们将简单地引发错误,并显示消息“ No user with this id found

And that is it! Let's start up our server:

就是这样! 让我们启动服务器:

npm start;

Open up postman and create a GET request http://localhost:3000/users/:id.

打开邮递员并创建GET请求http://localhost:3000/users/:id

Note: I am using the ID of the last user we just created.

注意:我使用的是我们刚创建的最后一个用户的ID。

Nicely done! Good job.

做得很好! 做得好。

Two more API's to go for our user section.

我们的用户部分还有两个API。

获取所有用户API [GET请求] (Get all users API [GET request])

For our router in .get('/', user.onGetAllUsers) let's add information to its controller.

对于.get('/', user.onGetAllUsers)的路由器,让我们向其控制器添加信息。

Go to controllers/user.js and add code in the onGetAllUsers() method:

转到controllers/user.js并在onGetAllUsers()方法中添加代码:

onGetAllUsers: async (req, res) => {
  try {
    const users = await UserModel.getUsers();
    return res.status(200).json({ success: true, users });
  } catch (error) {
    return res.status(500).json({ success: false, error: error })
  }
},

Next let's create the static method for getUsers() in the models/User.js file. Below the last static method you wrote in that file, type:

接下来,让我们在models/User.js文件中为getUsers()创建静态方法。 在您在该文件中编写的最后一个静态方法下面,键入:

userSchema.statics.getUsers = async function () {
  try {
    const users = await this.find();
    return users;
  } catch (error) {
    throw error;
  }
}

We use the mongoose method called await this.find(); to get all the records for our users collection and return it.

我们使用称为await this.find();mongoose方法await this.find(); 获取我们users收集的所有记录并返回。

Note: I am not handling pagination in our users API because that's not the main focus here. I'll talk about pagination once we move towards our chat APIs.

注意:我不在我们的用户API中处理分页,因为这不是这里的主要重点。 一旦我们使用聊天API,我将谈论分页。

Let's start our server:

让我们启动服务器:

npm start;

Open up postman and create a GET request for this route http://localhost:3000/users:

打开邮递员,并为此路由http://localhost:3000/users创建一个GET请求:

I went ahead and ended up creating a couple more users. 😄

我继续前进,最终创建了更多用户。 😄

通过ID API [删除请求]删除用户(更多奖金部分,如果需要,您可以跳过此部分) (Delete a user by ID API [DELETE request] (More of a bonus section, you can skip this if you want))

Let's create our final route to delete a user by their ID. For the route .delete('/:id', user.onDeleteUserById) go to its controller in controllers/user.js and write this code in the onDeleteUserById() method:

让我们创建最终路线以通过用户ID删除用户。 对于路由.delete('/:id', user.onDeleteUserById)转到其在controllers/user.js中的controllers/user.js并在onDeleteUserById()方法中编写以下代码:

onDeleteUserById: async (req, res) => {
  try {
    const user = await UserModel.deleteByUserById(req.params.id);
    return res.status(200).json({ 
      success: true, 
      message: `Deleted a count of ${user.deletedCount} user.` 
    });
  } catch (error) {
    return res.status(500).json({ success: false, error: error })
  }
},

Let's add the static method deleteByUserById in models/User.js:

让我们在models/User.js添加静态方法deleteByUserById

userSchema.statics.deleteByUserById = async function (id) {
  try {
    const result = await this.remove({ _id: id });
    return result;
  } catch (error) {
    throw error;
  }
}

We pass in the id here as a parameter and then use the mongoose method called this.remove to delete a record item from a specific collection. In this case, it's the users collection.

我们在此处传递id作为参数,然后使用名为this.removemongoose方法从特定集合中删除记录项。 在这种情况下,它是users集合。

Let's start up our server:

让我们启动服务器:

npm start;

Go to postman and create a new DELETE route:

转到邮递员并创建新的DELETE路线:

With this we'll conclude our USER API section.

这样,我们将结束USER API部分。

Next we will cover how to authenticate routes with an authentication token. This is the last thing I want to touch on before moving on to the chat section – because all of the chat APIs will be authenticated.

接下来,我们将介绍如何使用身份验证令牌对路由进行身份验证。 这是我继续讨论聊天部分之前要做的最后一件事–因为所有聊天API都将通过身份验证。

ExpressJS中的中间件是什么? (What are middlewares in ExpressJS? )

How can we write them? By adding JWT middleware in your application:

我们该怎么写? 通过在您的应用程序中添加JWT中间件:

And here's the GitHub link to the entire source code of this video [Chapter 0].

是该视频的完整源代码GitHub链接 [第0章]。

And again, all the relevant info can be found in the READ.ME.

同样,所有相关信息都可以在READ.ME中找到。

Coming back to our code base, let's create a JWT middleware to authenticate our routes. Go to middlewares/jwt.js and add the following:

回到我们的代码库,让我们创建一个JWT中间件来验证我们的路由。 转到middlewares/jwt.js并添加以下内容:

import jwt from 'jsonwebtoken';
// models
import UserModel from '../models/User.js';

const SECRET_KEY = 'some-secret-key';

export const encode = async (req, res, next) => {
  try {
    const { userId } = req.params;
    const user = await UserModel.getUserById(userId);
    const payload = {
      userId: user._id,
      userType: user.type,
    };
    const authToken = jwt.sign(payload, SECRET_KEY);
    console.log('Auth', authToken);
    req.authToken = authToken;
    next();
  } catch (error) {
    return res.status(400).json({ success: false, message: error.error });
  }
}

export const decode = (req, res, next) => {
  if (!req.headers['authorization']) {
    return res.status(400).json({ success: false, message: 'No access token provided' });
  }
  const accessToken = req.headers.authorization.split(' ')[1];
  try {
    const decoded = jwt.verify(accessToken, SECRET_KEY);
    req.userId = decoded.userId;
    req.userType = decoded.type;
    return next();
  } catch (error) {

    return res.status(401).json({ success: false, message: error.message });
  }
}

Let's discuss the encode method first:

让我们首先讨论encode方法:

export const encode = async (req, res, next) => {
  try {
    const { userId } = req.params;
    const user = await UserModel.getUserById(userId);
    const payload = {
      userId: user._id,
      userType: user.type,
    };
    const authToken = jwt.sign(payload, SECRET_KEY);
    console.log('Auth', authToken);
    req.authToken = authToken;
    next();
  } catch (error) {
    return res.status(400).json({ 
    	success: false, message: error.error 
    });
  }
}

Let's go through it step by step.

让我们逐步进行。

We get the userId from our req.params. If you remember from the video earlier, req.params is the /:<identifier> defined in our routes section.

我们从req.params获取userId 。 如果您还记得前面的视频,则req.params是我们的路线部分中定义的/:<identifier>

Next we use the const user = await UserModel.getUserById(userId); method we just created recently to get user information. If it exists, that is – otherwise this line will throw an error and it will directly go to the catch block where we will return the user with a 400 response and and an error message.

接下来,我们使用const user = await UserModel.getUserById(userId); 我们最近创建的用于获取用户信息的方法。 如果存在,则为-否则,此行将引发错误,并将直接转到catch块,在此我们将为用户返回400响应和一条错误消息。

But if we get a response from the getUserById method we then make a payload:

但是,如果我们从getUserById方法获得响应,则将创建有效负载:

const payload = {
      userId: user._id,
      userType: user.type,
};

Next we sign that payload in JWT using the following:

接下来,我们使用以下方法在JWT中对该有效负载进行签名:

const authToken = jwt.sign(payload, SECRET_KEY);

Once we have the JWT signed we then do this:

一旦JWT签名,我们就可以这样做:

req.authToken = authToken;
next();

Set it to our req.authToken and then forward this information as next().

将其设置为我们的req.authToken ,然后将此信息作为next()转发。

Next let's talk about the decode method:

接下来让我们讨论一下decode方法:

export const decode = (req, res, next) => {
  if (!req.headers['authorization']) {
    return res.status(400).json({ success: false, message: 'No access token provided' });
  }
  const accessToken = req.headers.authorization.split(' ')[1];
  try {
    const decoded = jwt.verify(accessToken, SECRET_KEY);
    req.userId = decoded.userId;
    req.userType = decoded.type;
    return next();
  } catch (error) {

    return res.status(401).json({ success: false, message: error.message });
  }
}

Let's break this down:

让我们分解一下:

if (!req.headers['authorization']) {
  return res.status(400).json({ 
  	success: false, 
    	message: 'No access token provided' 
  });
}

First we check if the authorization header is present or not. If not we simply return an error message to user.

首先,我们检查authorization标头是否存在。 如果没有,我们只是向用户返回一条错误消息。

Then we do this:

然后我们这样做:

const accessToken = req.headers.authorization.split(' ')[1];

It's being split(' ') by space and then we are getting the second index of the array by accessing its [1] index because the convention is authorization: Bearer <auth-token>. Want to read more on this? Check out this nice thread on quora.

它被空格split(' ') ,然后我们通过访问数组的[1]索引来获取数组的第二个索引,因为约定是authorization: Bearer <auth-token> 。 想了解更多吗? 在quora上查看这个漂亮的线程

Then we try to decode our token:

然后,我们尝试对令牌进行解码:

try {
  const decoded = jwt.verify(accessToken, SECRET_KEY);
  req.userId = decoded.userId;
  req.userType = decoded.type;
  return next();
} catch (error) {
  return res.status(401).json({ 
  	success: false, message: error.message 
  });
}

If this is not successful jwt.verify(accessToken, SECRET_KEY) will simply throw an error and our code will go in the catch block immediately. If it is successful, then we can decode it. We get userId and type from the token and save it as req.userId, req.userType and simply hit next().

如果这不成功,则jwt.verify(accessToken, SECRET_KEY)只会引发错误,我们的代码将立即进入catch块。 如果成功,则可以对其进行解码。 我们从令牌中获取userId并进行type ,然后将其另存为req.userId, req.userTypereq.userId, req.userType然后直接单击next()

Now, moving forward, every route that goes through this decode middleware will have the current user's id & it's type.

现在,继续前进,通过此decode中间件的每条路由都将具有当前用户的id & it's type

This was it for the middleware section. Let's create a login route so that we can ask a user for their information and give a token in return (because moving forward they'll need a token to access the rest of chat APIs).

中间件部分就是这样。 让我们创建一个login路径,以便我们可以向用户询问他们的信息并提供令牌作为回报(因为向前移动,他们将需要令牌来访问其余的聊天API)。

创建登录路径[POST请求] (Creating a login route [POST request])

Go to your routes/index.js file and paste the following content:

转到您的routes/index.js文件并粘贴以下内容:

import express from 'express';
// middlewares
import { encode } from '../middlewares/jwt.js';

const router = express.Router();

router
  .post('/login/:userId', encode, (req, res, next) => {
    return res
      .status(200)
      .json({
        success: true,
        authorization: req.authToken,
      });
  });

export default router;

So all we are doing is adding the encode middleware to our http://localhost:3000/login/:<user-id> [POST] route. If everything goes smoothly the user will get an authorization token.

因此,我们要做的就是将encode中间件添加到我们的http://localhost:3000/login/:<user-id> [POST]路由中。 如果一切顺利,用户将获得authorization令牌。

Note: I am not adding a login/signup flow, but I still wanted to touch on JWT/middleware in this tutorial.

注意:我没有添加登录/注册流程,但是我仍然想在本教程中介绍JWT /中间件。

Usually authentication is done in a similar way. The only addition here is that the user doesn't provide their ID. They provide their username, password (which we verify in the database), and if everything checks out we give them an authorization token.

通常,身份验证是通过类似的方式完成的。 这里唯一的补充是用户不提供其ID。 他们提供了用户名,密码(我们在数据库中进行了验证),如果一切都签出了,我们会给他们一个授权令牌。

If you got stuck anywhere up to this point, just write to me at twitter.com/adeelibr, so that way I can improve the content. You can also write to me if you would like to learn something else.

如果您到现在为止还停留在任何地方,只需在twitter.com/adeelibr上给我写信,这样我就可以改善内容。 如果您想学习其他内容,也可以给我写信。

As a reminder, the entire source code is available here. You don't have to code along with this tutorial, but if you do the concepts will stick better.

提醒一下,此处是完整的源代码。 您不必随本教程一起编写代码,但如果您这样做,这些概念将更好地坚持。

Let's just check our /login route now.

现在让我们检查/login路由。

Start your server:

启动服务器:

npm start;

Let's run postman. Create a new POST request http://localhost:3000/login/<user-id>:

让我们运行邮递员。 创建一个新的POST请求http://localhost:3000/login/<user-id>

With this we are done with our login flow as well.

这样,我们也完成了登录流程。

This was a lot. But now we can focus only on our chat routes.

好多 但是现在我们只能专注于聊天路线。

创建一个Web套接字类 (Create a web socket class )

This web socket class will handle events when a user disconnects, joins a chat room, or wants to mute a chat room.

当用户断开连接,加入聊天室或想要使聊天室静音时,此Web套接字类将处理事件。

So let's create a web-socket class that will manage sockets for us. Create a new folder called utils. Inside that folder create a file called WebSockets.js and add the following content:

因此,让我们创建一个Web-socket类,它将为我们管理套接字。 创建一个名为utils的新文件夹。 在该文件夹中,创建一个名为WebSockets.js的文件,并添加以下内容:

class WebSockets {
  users = [];
  connection(client) {
    // event fired when the chat room is disconnected
    client.on("disconnect", () => {
      this.users = this.users.filter((user) => user.socketId !== client.id);
    });
    // add identity of user mapped to the socket id
    client.on("identity", (userId) => {
      this.users.push({
        socketId: client.id,
        userId: userId,
      });
    });
    // subscribe person to chat & other user as well
    client.on("subscribe", (room, otherUserId = "") => {
      this.subscribeOtherUser(room, otherUserId);
      client.join(room);
    });
    // mute a chat room
    client.on("unsubscribe", (room) => {
      client.leave(room);
    });
  }

  subscribeOtherUser(room, otherUserId) {
    const userSockets = this.users.filter(
      (user) => user.userId === otherUserId
    );
    userSockets.map((userInfo) => {
      const socketConn = global.io.sockets.connected(userInfo.socketId);
      if (socketConn) {
        socketConn.join(room);
      }
    });
  }
}

export default new WebSockets();

The WebSockets class has three major things here:

WebSockets类在这里有三大方面:

  • users array

    用户数组
  • connection method

    连接方式
  • subscribing members of a chat room to it. subscribeOtherUser

    订阅聊天室的成员。 subscribeOtherUser

Let's break this down.

让我们分解一下。

We have a class:

我们有一堂课:

class WebSockets {

}

export default new WebSocket();

We create a class and export an instance of that class.

我们创建一个类并导出该类的实例。

Inside the class we have an empty users array. This array will hold a list of all the active users that are online using our application.

在类内部,我们有一个空的users数组。 该数组将包含使用我们的应用程序在线的所有活动用户的列表。

Next we have a connection method, the core of this class:

接下来,我们有一个connection方法,该类的核心:

connection(client) {
  // event fired when the chat room is disconnected
  client.on("disconnect", () => {
    this.users = this.users.filter((user) => user.socketId !== client.id);
  });
  // add identity of user mapped to the socket id
  client.on("identity", (userId) => {
    this.users.push({
      socketId: client.id,
      userId: userId,
    });
  });
  // subscribe person to chat & other user as well
  client.on("subscribe", (room, otherUserId = "") => {
    this.subscribeOtherUser(room, otherUserId);
    client.join(room);
  });
  // mute a chat room
  client.on("unsubscribe", (room) => {
    client.leave(room);
  });
}

The connection method takes in a parameter called client (client here will be our server instance, I will talk more about this in a bit).

connection方法接受一个名为client的参数(这里的client将是我们的服务器实例,稍后我将详细讨论)。

We take the param client and add some event to it

我们使用param client并向其中添加一些事件

  • client.on('disconnect') // when a user connection is lost this method will be called

    client.on('disconnect')//当失去用户连接时,将调用此方法
  • client.on('identity') // when user logs in from the front end they will make a connection with our server by giving their identity

    client.on('identity')//当用户从前端登录时,他们将通过提供其身份与我们的服务器建立连接
  • client.on('subscribe') // when a user joins a chat room this method is called

    client.on('subscribe')//当用户加入聊天室时,此方法称为
  • client.on('unsubscribe') // when a user leaves or wants to mute a chat room

    client.on('unsubscribe')//用户离开或想让聊天室静音

Let's talk about disconnect:

让我们来谈谈disconnect

client.on("disconnect", () => {
  this.users = this.users.filter((user) => user.socketId !== client.id);
});

As soon as the connection is disconnected, we run a filter on users array. Where we find user.id === client.id we remove it from our sockets array. ( client here is coming from the function param.)

一旦连接断开,我们就对用户数组运行筛选器。 在找到user.id === client.id我们将其从套接字数组中删除。 (这里的client来自功能参数。)

Let's talk about identity:

让我们谈谈identity

client.on("identity", (userId) => {
  this.users.push({
    socketId: client.id,
    userId: userId,
  });
});

When a user logs in through he front end application web/android/ios they will make a socket connection with our backend app and call this identity method. They'll also send their own user id.

当用户通过前端应用程序web / android / ios登录时,他们将与我们的后端应用程序建立套接字连接,并调用此标识方法。 他们还将发送自己的用户ID。

We will take that user id and the client id (the user's own unique socket id that socket.io creates when they make a connection with our BE).

我们将使用该用户ID和客户端ID(用户在与我们的BE建立连接时由socket.io创建的用户自己的唯一套接字ID)。

Next we have unsubscribe:

接下来,我们unsubscribe

client.on("unsubscribe", (room) => {
  client.leave(room);
});

The user passes in the room id and we just tell client.leave() to remove the current user calling this method from a particular chat room.

用户传入room ID,我们只是告诉client.leave()从当前聊天室中删除当前调用此方法的用户。

Next we have subscribe:

接下来,我们订阅:

client.on("subscribe", (room, otherUserId = "") => {
  this.subscribeOtherUser(room, otherUserId);
  client.join(room);
});

When a user joins a chat room, they will tell us about the room they want to join along with the other person who is part of that chat room.

当用户加入聊天室时,他们会告诉我们他们想与该聊天室中的其他人一起加入的房间。

Note: We will see later that when we initiate a chat room we get all the users associated with that room in the API response.

注意:稍后我们将看到,当我们启动聊天室时,会在API响应中获得与该聊天室关联的所有用户。

In my opinion: Another thing we could have done here was when the user sends in the room number, we can make a DB query to see all the members of the chat room and make them join if they are online at the moment (that is, in our users list).

我认为 :我们可以在这里做的另一件事是,当用户发送房间号时,我们可以进行数据库查询以查看聊天室的所有成员,并让他们在当前在线的情况下加入(即,在我们的用户列表中)。

The subscribeOtherUser method is defined like this:

subscribeOtherUser方法的定义如下:

subscribeOtherUser(room, otherUserId) {
  const userSockets = this.users.filter(
    (user) => user.userId === otherUserId
  );
  userSockets.map((userInfo) => {
    const socketConn = global.io.sockets.connected(userInfo.socketId);
    if (socketConn) {
      socketConn.join(room);
    }
  });
}

We pass in  room and otherUserId as params to this function.

我们将roomotherUserId作为参数传递给此函数。

Using the otherUserId we filter on our this.users array and all the results that match are stored in userSockets array.

使用otherUserId我们对this.users数组进行过滤,所有匹配的结果都存储在userSockets数组中。

You might be thinking – how can one user have multiple presences in the user array? Well, think of a scenario where the same user is logged in from both their web application and mobile phone. It will create multiple socket connections for the same user.

您可能在想–一个用户如何在用户阵列中具有多个状态? 好吧,请考虑一个场景,其中同一用户同时从其Web应用程序和移动电话登录。 它将为同一用户创建多个套接字连接。

Next we map on userSockets. For each item in this array we pass it into this method:  const socketConn = global.io.sockets.connected(userInfo.socketId)

接下来,我们映射到userSockets 。 对于此数组中的每个项目,我们将其传递给此方法: const socketConn = global.io.sockets.connected(userInfo.socketId)

I will talk more about this global.io.sockets.connected in a bit. But what this initially does is it takes in userInfo.socketId and if it exists in our socket connection, it will return the connection, otherwise null.

我将global.io.sockets.connected讨论一下这个global.io.sockets.connected 。 但是,此操作最初是将其userInfo.socketId ,如果它存在于我们的套接字连接中,它将返回该连接,否则返回null

Next we simply see if socketConn is available. If so, we take that socketConn and make this connection join the room passed in the function:

接下来,我们简单地看看socketConn是否可用。 如果是这样,我们采用该socketConn并使此连接加入函数中传递的room

if (socketConn) {
	socketConn.join(room);
}

And this is it for our WebSockets class.

这就是我们的WebSockets类。

Let's import this file in our server/index.js file:

让我们将此文件导入到server/index.js文件中:

import socketio from "socket.io";
// mongo connection
import "./config/mongo.js";
// socket configuration
import WebSockets from "./utils/WebSockets.js";

So just import socket.io and import WebSockets somewhere at the top.

因此,只需导入socket.io并在顶部的某个位置导入WebSockets

Next where we are creating our server add the content below this:

接下来,在我们创建服务器的地方,在下面添加内容:

/** Create HTTP server. */
const server = http.createServer(app);
/** Create socket connection */
global.io = socketio.listen(server);
global.io.on('connection', WebSockets.connection)

The server was created and we do two things:

server已创建,我们做两件事:

  • assign global.io to socketio.listen(server) (As soon as a port starts listening on the server, sockets starts listening for events happening on that port as well.)

    global.io分配给socketio.listen(server) (端口开始侦听server ,套接字也开始侦听该端口上发生的事件。)

  • then we assign global.io.on('connection', WebSockets.connection) method. Every time someone from the front end makes a socket connection, the connection method will be called which will invoke our Websockets class and inside that class the connection method.

    然后我们分配global.io.on('connection', WebSockets.connection)方法。 每次前端有人建立套接字连接时,都会调用该connection方法,该方法将调用我们的Websockets类,并在该类内部调用connection方法。

global.io is equivalent to windows object in browser. But since we don't have windows in NodeJS we use global.io. Whatever we put in global.io is available in the entire application.

global.io等效于浏览器中的windows对象。 但是由于global.io没有windows ,因此我们使用global.io 。 无论我们在global.ioglobal.io什么global.io都可以在整个应用程序中使用。

This is the same global.io we used in the WebSockets class inside the subscribeOtherUser method.

这是我们在subscribeOtherUser方法内的WebSockets类中使用的global.io

If you got lost here is the entire source code of this chat application. Also free to drop me a message with your feedback and I will try to improve the content of this tutorial.

如果您迷路了,这里是此聊天应用程序全部源代码 。 另外,请随时给我您的反馈信息,我将尝试改进本教程的内容。

讨论聊天室和聊天消息数据库模型 (Discussing chat room & chat message database model)

Before starting off with Chat, I think it is really important to discuss the database model on which we will create our chat application. Have a look at the below video:

在开始聊天之前,我认为讨论在其上创建聊天应用程序的数据库模型非常重要。 看下面的视频:

Now that you have a clear idea about what our chat structure will be like, let's start off by making our chat room model.

既然您已经对我们的聊天结构有了一个清晰的了解,那么让我们开始制作我们的聊天室模型。

Go inside your models folder and create the following ChatRoom.js. Add the following content to it:

进入您的models文件夹并创建以下ChatRoom.js 。 向其中添加以下内容:

import mongoose from "mongoose";
import { v4 as uuidv4 } from "uuid";

export const CHAT_ROOM_TYPES = {
  CONSUMER_TO_CONSUMER: "consumer-to-consumer",
  CONSUMER_TO_SUPPORT: "consumer-to-support",
};

const chatRoomSchema = new mongoose.Schema(
  {
    _id: {
      type: String,
      default: () => uuidv4().replace(/\-/g, ""),
    },
    userIds: Array,
    type: String,
    chatInitiator: String,
  },
  {
    timestamps: true,
    collection: "chatrooms",
  }
);

chatRoomSchema.statics.initiateChat = async function (
	userIds, type, chatInitiator
) {
  try {
    const availableRoom = await this.findOne({
      userIds: {
        $size: userIds.length,
        $all: [...userIds],
      },
      type,
    });
    if (availableRoom) {
      return {
        isNew: false,
        message: 'retrieving an old chat room',
        chatRoomId: availableRoom._doc._id,
        type: availableRoom._doc.type,
      };
    }

    const newRoom = await this.create({ userIds, type, chatInitiator });
    return {
      isNew: true,
      message: 'creating a new chatroom',
      chatRoomId: newRoom._doc._id,
      type: newRoom._doc.type,
    };
  } catch (error) {
    console.log('error on start chat method', error);
    throw error;
  }
}

export default mongoose.model("ChatRoom", chatRoomSchema);

We have three things going on here:

我们在这里进行三件事:

  • We have a const for CHAT_ROOM_TYPES which has only two types

    我们有一个CHAT_ROOM_TYPES常量,只有两种类型

  • We define our ChatRoom schema

    我们定义我们的ChatRoom模式
  • We add a static method to initiate chat

    我们添加了一个静态方法来发起聊天

在用户之间发起聊天(/房间/发起[POST请求]) (Initiate a chat between users (/room/initiate [POST request]))

Let's discuss our static method defined in models/ChatRoom.js called initiateChat:

让我们来讨论在定义我们的静态方法models/ChatRoom.jsinitiateChat

chatRoomSchema.statics.initiateChat = async function (userIds, type, chatInitiator) {
  try {
    const availableRoom = await this.findOne({
      userIds: {
        $size: userIds.length,
        $all: [...userIds],
      },
      type,
    });
    if (availableRoom) {
      return {
        isNew: false,
        message: 'retrieving an old chat room',
        chatRoomId: availableRoom._doc._id,
        type: availableRoom._doc.type,
      };
    }

    const newRoom = await this.create({ userIds, type, chatInitiator });
    return {
      isNew: true,
      message: 'creating a new chatroom',
      chatRoomId: newRoom._doc._id,
      type: newRoom._doc.type,
    };
  } catch (error) {
    console.log('error on start chat method', error);
    throw error;
  }
}

This function takes in three parameters:

此函数接受三个参数:

  • userIds (array of users)

    userIds(用户数组)
  • type (type of chatroom)

    类型(聊天室类型)
  • chatInitiator (the user who created the chat room)

    chatInitiator(创建聊天室的用户)

Next we are doing two things here: either returning an existing chatroom document or creating a new one.

接下来,我们在这里做两件事:要么返回现有的聊天室文档,要么创建一个新的文档。

Let's break this one down:

让我们分解一下:

const availableRoom = await this.findOne({
  userIds: {
    $size: userIds.length,
    $all: [...userIds],
  },
  type,
});
if (availableRoom) {
  return {
    isNew: false,
    message: 'retrieving an old chat room',
    chatRoomId: availableRoom._doc._id,
    type: availableRoom._doc.type,
  };
}

First using the this.findOne() API in mongoose, we find all the chatrooms where the following criteria is met:

首先在猫鼬中使用this.findOne() API,我们找到满足以下条件的所有聊天室:

userIds: { $size: userIds.length, $all: [...userIds] },
type: type,

You can read more on the $size operator here, and more on the $all operator here.

您可以在此处阅读有关$ size运算符的更多信息 ,并在此处了解有关$ all运算符的更多信息

We're checking to find a chatroom document where an item exists in our chatrooms collection where

我们正在检查以查找聊天室文档,其中该聊天室文档在我们的聊天室集合中存在某项

  1. the userIds are the same as the one we are passing to this function (irrespective of the user ids order), and

    userIds与我们传递给该函数的userIds相同(与用户ID顺序无关),并且

  2. the length of the userIds is the same as that my userIds.length that we are passing through the function.

    该长度userIds是一样的,我的userIds.length ,我们正在经历的功能。

Also we're checking that the chat room type should be the same.

另外,我们正在检查聊天室类型是否应该相同。

If something like this is found, we simply return the existing chatroom.

如果找到类似的内容,我们只需返回现有的聊天室即可。

Otherwise we create a new chat room and return it by doing this:

否则,我们将创建一个新的聊天室并通过执行以下操作将其返回:

const newRoom = await this.create({ userIds, type, chatInitiator });
return {
  isNew: true,
  message: 'creating a new chatroom',
  chatRoomId: newRoom._doc._id,
  type: newRoom._doc.type,
};

Create a new room and return the response.

创建一个新房间并返回响应。

We also have an isNew key where, if it's retrieving an old chatroom, we set it to false otherwise true.

我们还有一个isNew键,如果要检索旧的聊天室,则将其设置为false否则为true

Next for your route created in routes/chatRoom.js called post('/initiate', chatRoom.initiate) go to its appropriate controller in controllers/chatRoom.js and add the following in the initiate method:

接下来,在routes/chatRoom.js创建的名为post('/initiate', chatRoom.initiate) routes/chatRoom.js post('/initiate', chatRoom.initiate)转到其在controllers/chatRoom.js合适的控制器,并在initiate方法中添加以下内容:

initiate: async (req, res) => {
  try {
    const validation = makeValidation(types => ({
      payload: req.body,
      checks: {
        userIds: { 
          type: types.array, 
          options: { unique: true, empty: false, stringOnly: true } 
        },
        type: { type: types.enum, options: { enum: CHAT_ROOM_TYPES } },
      }
    }));
    if (!validation.success) return res.status(400).json({ ...validation });

    const { userIds, type } = req.body;
    const { userId: chatInitiator } = req;
    const allUserIds = [...userIds, chatInitiator];
    const chatRoom = await ChatRoomModel.initiateChat(allUserIds, type, chatInitiator);
    return res.status(200).json({ success: true, chatRoom });
  } catch (error) {
    return res.status(500).json({ success: false, error: error })
  }
},

We are using the make-validation library here to validate the user's request. For the initiate API, we expect the user to send an array of users and also define the type of the chat-room that is being created.

我们在这里使用make-validation库来验证用户的请求。 对于启动API,我们希望用户发送一组users ,并定义正在创建的chat-room的类型。

Once the validation passes, then:

验证通过后,即可:

const { userIds, type } = req.body;
const { userId: chatInitiator } = req;
const allUserIds = [...userIds, chatInitiator];
const chatRoom = await ChatRoomModel.initiateChat(allUserIds, type, chatInitiator);
return res.status(200).json({ success: true, chatRoom });

One thing to notice here is userIds, type is coming from req.body while userId that is being aliased as chatInitiatorId is coming from req thanks to our decode middleware.

这里要注意的一件事是req.body userIds, type来自req.body而别名为chatInitiatorId userId来自req这要归功于我们的decode中间件。

If you remember, we attached app.use("/room", decode, chatRoomRouter); in our server/index.js file. This means this route /room/initiate is authenticated. So const { userId: chatInitiator } = req; is the id of the current user logged in.

如果您还记得,我们附加了app.use("/room", decode, chatRoomRouter); 在我们的server/index.js文件中。 这意味着该路由/room/initiate已通过身份验证。 因此const { userId: chatInitiator } = req; 是当前登录用户的ID。

We simply call our initiateChat method from ChatRoomModel and pass it allUserIds, type, chatInitiator. Whatever result comes we simply pass it to the user.

我们只需拨打我们的initiateChat从方法ChatRoomModel并把它传递allUserIds, type, chatInitiator 。 无论结果如何,我们只要将其传递给用户即可。

Let's run this and see if it works (here is a video of me doing it):

让我们运行它,看看它是否有效(这是我做的一个视频):

在聊天室中创建一条消息(/:roomId / message)[POST请求] (Create a message in chat room (/:roomId/message) [POST request])

Let's create a message for the chat room we just created with pikachu.

让我们为刚刚使用pikachu创建的聊天室创建一条消息。

But before we create a message we need to create a model for our chatmessages. So let's do that first. In your models folder create a new file called ChatMessage.js and add the following content to it:

但是在创建消息之前,我们需要为chatmessages消息创建模型。 所以让我们先做。 在您的models文件夹中创建一个名为ChatMessage.js的新文件, ChatMessage.js其中添加以下内容:

import mongoose from "mongoose";
import { v4 as uuidv4 } from "uuid";

const MESSAGE_TYPES = {
  TYPE_TEXT: "text",
};

const readByRecipientSchema = new mongoose.Schema(
  {
    _id: false,
    readByUserId: String,
    readAt: {
      type: Date,
      default: Date.now(),
    },
  },
  {
    timestamps: false,
  }
);

const chatMessageSchema = new mongoose.Schema(
  {
    _id: {
      type: String,
      default: () => uuidv4().replace(/\-/g, ""),
    },
    chatRoomId: String,
    message: mongoose.Schema.Types.Mixed,
    type: {
      type: String,
      default: () => MESSAGE_TYPES.TYPE_TEXT,
    },
    postedByUser: String,
    readByRecipients: [readByRecipientSchema],
  },
  {
    timestamps: true,
    collection: "chatmessages",
  }
);

chatMessageSchema.statics.createPostInChatRoom = async function (chatRoomId, message, postedByUser) {
  try {
    const post = await this.create({
      chatRoomId,
      message,
      postedByUser,
      readByRecipients: { readByUserId: postedByUser }
    });
    const aggregate = await this.aggregate([
      // get post where _id = post._id
      { $match: { _id: post._id } },
      // do a join on another table called users, and 
      // get me a user whose _id = postedByUser
      {
        $lookup: {
          from: 'users',
          localField: 'postedByUser',
          foreignField: '_id',
          as: 'postedByUser',
        }
      },
      { $unwind: '$postedByUser' },
      // do a join on another table called chatrooms, and 
      // get me a chatroom whose _id = chatRoomId
      {
        $lookup: {
          from: 'chatrooms',
          localField: 'chatRoomId',
          foreignField: '_id',
          as: 'chatRoomInfo',
        }
      },
      { $unwind: '$chatRoomInfo' },
      { $unwind: '$chatRoomInfo.userIds' },
      // do a join on another table called users, and 
      // get me a user whose _id = userIds
      {
        $lookup: {
          from: 'users',
          localField: 'chatRoomInfo.userIds',
          foreignField: '_id',
          as: 'chatRoomInfo.userProfile',
        }
      },
      { $unwind: '$chatRoomInfo.userProfile' },
      // group data
      {
        $group: {
          _id: '$chatRoomInfo._id',
          postId: { $last: '$_id' },
          chatRoomId: { $last: '$chatRoomInfo._id' },
          message: { $last: '$message' },
          type: { $last: '$type' },
          postedByUser: { $last: '$postedByUser' },
          readByRecipients: { $last: '$readByRecipients' },
          chatRoomInfo: { $addToSet: '$chatRoomInfo.userProfile' },
          createdAt: { $last: '$createdAt' },
          updatedAt: { $last: '$updatedAt' },
        }
      }
    ]);
    return aggregate[0];
  } catch (error) {
    throw error;
  }
}

export default mongoose.model("ChatMessage", chatMessageSchema);

There are a couple of things happening here:

这里发生了几件事:

  • We have a MESSAGE_TYPES object which has only one type called text

    我们有一个MESSAGE_TYPES对象,该对象只有一种称为text类型

  • We are defining our schema for chatmessage and readByRecipient

    我们正在为chatmessagereadByRecipient定义架构

  • Then we are writing our static method for createPostInChatRoom

    然后,我们为createPostInChatRoom编写静态方法

I know this is a lot of content, but just bear with me. Let's just write the controller for the route that creates this message.

我知道这是很多内容,但请多多包涵。 让我们只为创建此消息的路由编写控制器。

For the route defined in our routes/chatRoom.js API called .post('/:roomId/message', chatRoom.postMessage) let's go to its controller in controllers/chatRoom.js and define it:

对于在我们的routes/chatRoom.js API中定义的routes/chatRoom.js该路由名为.post('/:roomId/message', chatRoom.postMessage)让我们转到controllers/chatRoom.js中的controllers/chatRoom.js并对其进行定义:

postMessage: async (req, res) => {
  try {
    const { roomId } = req.params;
    const validation = makeValidation(types => ({
      payload: req.body,
      checks: {
        messageText: { type: types.string },
      }
    }));
    if (!validation.success) return res.status(400).json({ ...validation });

    const messagePayload = {
      messageText: req.body.messageText,
    };
    const currentLoggedUser = req.userId;
    const post = await ChatMessageModel.createPostInChatRoom(roomId, messagePayload, currentLoggedUser);
    global.io.sockets.in(roomId).emit('new message', { message: post });
    return res.status(200).json({ success: true, post });
  } catch (error) {
    return res.status(500).json({ success: false, error: error })
  }
},

Cool, let's discuss what we are doing here:

很酷,让我们讨论一下我们在做什么:

Operators discussed in this video are:

该视频中讨论的操作员是:

通过ID查看聊天室的会话[获取请求] (See conversation for a chat room by it's id [Get request])

Now that we have

现在我们有了

  • Created a chat room

    创建了一个聊天室
  • Are able to add messages in that chat room

    能够在该聊天室中添加消息

Let's see the entire conversation for that chat as well (with pagination).

让我们同时查看该聊天的整个对话(分页)。

For your route .get('/:roomId', chatRoom.getConversationByRoomId) in routes/chatRoom.js open its controller in the file controllers/chatRoom.js and add the following content to the chat room:

为了您的路线.get('/:roomId', chatRoom.getConversationByRoomId)routes/chatRoom.js在文件打开它的控制器, controllers/chatRoom.js ,并添加以下内容到聊天室:

getConversationByRoomId: async (req, res) => {
  try {
    const { roomId } = req.params;
    const room = await ChatRoomModel.getChatRoomByRoomId(roomId)
    if (!room) {
      return res.status(400).json({
        success: false,
        message: 'No room exists for this id',
      })
    }
    const users = await UserModel.getUserByIds(room.userIds);
    const options = {
      page: parseInt(req.query.page) || 0,
      limit: parseInt(req.query.limit) || 10,
    };
    const conversation = await ChatMessageModel.getConversationByRoomId(roomId, options);
    return res.status(200).json({
      success: true,
      conversation,
      users,
    });
  } catch (error) {
    return res.status(500).json({ success: false, error });
  }
},

Next let's create a new static method in our ChatRoomModel file called getChatRoomByRoomId in models/ChatRoom.js:

接下来,让我们在models/ChatRoom.js中的ChatRoomModel文件中创建一个名为getChatRoomByRoomId的新静态方法:

chatRoomSchema.statics.getChatRoomByRoomId = async function (roomId) {
  try {
    const room = await this.findOne({ _id: roomId });
    return room;
  } catch (error) {
    throw error;
  }
}

Very straightforward – we are getting the room by roomId here.

非常简单-我们在这里通过roomId获取房间。

Next in our UserModel, create a static method called getUserByIds in the file models/User.js:

接下来,在UserModel ,在文件models/User.js创建一个名为getUserByIds的静态方法:

userSchema.statics.getUserByIds = async function (ids) {
  try {
    const users = await this.find({ _id: { $in: ids } });
    return users;
  } catch (error) {
    throw error;
  }
}

The operator used here is $in – I'll talk about this in a bit.

这里使用的运算符是$ in-我将稍作讨论。

And then at last, go to your ChatMessage model in models/ChatMessage.js and write a new static method called getConversationByRoomId:

最后,转到models/ChatMessage.js ChatMessage模型,并编写一个名为getConversationByRoomId的新静态方法:

chatMessageSchema.statics.getConversationByRoomId = async function (chatRoomId, options = {}) {
  try {
    return this.aggregate([
      { $match: { chatRoomId } },
      { $sort: { createdAt: -1 } },
      // do a join on another table called users, and 
      // get me a user whose _id = postedByUser
      {
        $lookup: {
          from: 'users',
          localField: 'postedByUser',
          foreignField: '_id',
          as: 'postedByUser',
        }
      },
      { $unwind: "$postedByUser" },
      // apply pagination
      { $skip: options.page * options.limit },
      { $limit: options.limit },
      { $sort: { createdAt: 1 } },
    ]);
  } catch (error) {
    throw error;
  }
}

Let's discuss all that we have done so far:

让我们讨论到目前为止我们已经做的所有事情:

All the source code is available here.

所有源代码都可在此处获得

将整个对话标记为已读(功能类似于WhatsApp) (Mark an entire conversation as read (feature similar to WhatsApp))

Once the other person is logged in and they view a conversation for a room id, we need to mark that conversation as read from their side.

对方登录后,他们查看了一个房间ID的对话后,我们需要将该对话标记为从对方那边读过。

To do this, in your routes/chatRoom.js for the route

为此,请在您的routes/chatRoom.js找到该路由

put('/:roomId/mark-read', chatRoom.markConversationReadByRoomId)

go to its appropriate controller in controllers/chatRoom.js and add the following content in the markConversationReadByRoomId controller.

请转到controllers/chatRoom.js相应的控制器,然后在markConversationReadByRoomId控制器中添加以下内容。

markConversationReadByRoomId: async (req, res) => {
  try {
    const { roomId } = req.params;
    const room = await ChatRoomModel.getChatRoomByRoomId(roomId)
    if (!room) {
      return res.status(400).json({
        success: false,
        message: 'No room exists for this id',
      })
    }

    const currentLoggedUser = req.userId;
    const result = await ChatMessageModel.markMessageRead(roomId, currentLoggedUser);
    return res.status(200).json({ success: true, data: result });
  } catch (error) {
    console.log(error);
    return res.status(500).json({ success: false, error });
  }
},

All we are doing here is first checking if the room exists or not. If it does, we proceed further. We take in the req.user.id as currentLoggedUser and pass it to the following function:

我们在这里要做的就是首先检查房间是否存在。 如果是这样,我们将继续进行。 我们将req.user.id作为currentLoggedUser并将其传递给以下函数:

ChatMessageModel.markMessageRead(roomId, currentLoggedUser);

Which in our ChatMessage model is defined like this:

在我们的ChatMessage模型中,哪个定义如下:

chatMessageSchema.statics.markMessageRead = async function (chatRoomId, currentUserOnlineId) {
  try {
    return this.updateMany(
      {
        chatRoomId,
        'readByRecipients.readByUserId': { $ne: currentUserOnlineId }
      },
      {
        $addToSet: {
          readByRecipients: { readByUserId: currentUserOnlineId }
        }
      },
      {
        multi: true
      }
    );
  } catch (error) {
    throw error;
  }
}

A possible use case is that the user might not have read the last 15 messages once they open up a specific room conversation. They should all be marked as read. So we're using the this.updateMany function by mongoose.

一个可能的用例是,一旦打开特定的会议室对话,用户可能就没有阅读最近的15条消息。 它们都应标记为已读。 因此,我们使用猫鼬的this.updateMany函数。

The query itself is defined in 2 steps:

查询本身分为两个步骤:

  • Find

  • Update

    更新资料

And there can be multiple statements be updated.

并且可以有多个语句被更新。

To find a section, do this:

要查找部分,请执行以下操作:

{
  chatRoomId,
  'readByRecipients.readByUserId': { $ne: currentUserOnlineId }
},

This says I want to find all the message posts in the chatmessages collection where chatRoomId matches and readByRecipients array does not. The userId that I am passing to this function is currentUserOnlineId.

这表示我想在chatmessages集合中找到所有与chatRoomId匹配而readByRecipients数组不匹配的消息。 我要传递给此函数的userIdcurrentUserOnlineId

Once it has all those documents where the criteria matches, it's then time to update them:

一旦所有这些文档都符合条件,就可以更新它们了:

{
  $addToSet: {
    readByRecipients: { readByUserId: currentUserOnlineId }
  }
},

$addToSet will just push a new entry to the readByRecipients array. This is like Array.push but for mongo.

$addToSet只会将一个新条目推送到readByRecipients数组。 这就像Array.push但适用于mongo。

Next we want to tell mongoose to not just update the first record it finds, but also to update all the records where the condition matches. So doing this:

接下来,我们要告诉mongoose不仅要更新它找到的第一条记录,还要更新条件匹配的所有记录。 这样做:

{
  multi: true
}

And that is all – we return the data as is.

仅此而已–我们将按原样返回数据。

Let's run this API.

让我们运行此API。

Start up the server:

启动服务器:

npm start;

Open your postman and create a new PUT request to test this route ocalhost:3000/room/<room=id-here>/mark-read:

打开邮递员并创建一个新的PUT请求以测试此路线ocalhost:3000/room/<room=id-here>/mark-read

奖金部分 (Bonus Section)

  • How to delete a chat room and all its related messages

    如何删除聊天室及其所有相关消息
  • How to delete a message by its message id

    如何通过消息ID删除消息

And we are done! Wow that was a lot of learning today.

我们完成了! 哇,今天有很多东西要学习。

You can find the source code of this tutorial here.

您可以在此处找到本教程的源代码。

Reach out to me on twitter with your feedback – I would love to hear if you have any suggestions for improvements: twitter.com/adeelibr

在Twitter上与我联系,提供您的反馈意见-如果您有任何改进建议,我很想听听: twitter.com/adeelibr

If you liked to this article, please do give the github repository a star and subscribe to my youtube channel.

如果您喜欢本文,请给github存储库加一个星号,然后订阅我的youtube频道

翻译自: https://www.freecodecamp.org/news/create-a-professional-node-express/

nodejs入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值