发布于 2026-01-06 5 阅读
0

Building REST API in Nodejs / MongoDB /Passport /JWT

使用 Node.js/MongoDB/Passport/JWT 构建 REST API

使用 Node.js / MongoDB / Passport / JWT 构建 REST API

如果你之前不了解 Node 和 JS,可以参加Wes Bos 的这门课程,它能帮助你快速入门。

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。 立即开始学习 →

在本课中,我们将开始使用NodeJSMongoDB数据库构建 REST API 的旅程。如果您之前没有 NodeJS 和 MongoDB 的经验,那么本课将非常适合您。

为什么要学习这篇教程?

我刚开始学习编程的时候,一直在寻找解决问题的方法,也确实找到了。但问题是,我不明白为什么有些代码有时有效,有时无效。我只能复制别人的源代码,却不明白其中的原理。

本教程将帮助您了解所有可以使用的样板代码,并让您理解其中的每个部分。

我们将制作什么?

我们将创建一个与 Medium 网站非常相似的网站,并采用 REST 标准。我们还将使用以下功能:

  • 身份验证本地 + JWT
  • 用户可以创建帖子
  • 用户可以删除自己的帖子并进行更新。
  • 用户可以关注其他用户的帖子。
  • 用户收到他关注的用户发布的帖子通知
  • 用户可以点赞帖子
  • 用户可以看到他喜欢过的所有职位列表。

听起来很有趣,对吧?让我们来看看我们将使用哪些工具来制作这款精彩的应用程序。

该应用程序的技术栈

我们将使用 JavaScript、ES6 和 ES7,并使用 Babel 和 Webpack v2 编译源代码。您还应该熟悉 JavaScript 的 Promise 和异步操作。

数据库方面,我们将使用 MongoDB。

所有代码都在Github上,链接在此。

设置工具

在本系列的第一部分中,我们将使用以下工具搭建我们的环境:

  • 编辑器配置
  • 表达
  • 埃斯林特
  • 巴别塔
  • Webpack 2

读完这篇文章,我们就能搭建并运行一个简单的 Express 服务器了。让我们开始吧!

为你的项目创建一个新目录。我把它命名为makenodejsrestapi”。我将使用yarn包来安装我的工具。在该目录下,我们首先创建一个名为“.gitignore”的新文件,并添加以下内容:

node_modules/

现在,我们将运行以下命令来初始化我们的项目:

yarn init

系统会询问您各种问题,我直接按回车键,让 yarn 使用默认值。命令执行完毕后,您会在项目目录中看到一个名为 _package.json_ 的新文件,其内容如下:

{ 
 “name”: “makenodejsrestapi”, 
 “version”: “1.0.0”, 
 “main”: “index.js”, 
 “license”: “MIT” 
}

此文件仅包含我们项目的元数据。接下来,我们将开始在项目中添加 Express。请运行以下命令:

yarn add express

如果初始状态下找不到该包,yarn需要一些时间才能找到它,但最终肯定会找到。命令运行完毕后,我们的package.json 文件将更新为以下内容:

接下来,我们在项目中创建一个名为 src 的新目录,并在其中创建一个名为 index.js 的新文件。将以下内容放入该文件中:

import express from 'express';
 const app = express();
 const PORT = process.env.PORT || 3000;
 app.listen(PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(Server running on port: $ {
             PORT
         }-- - Running on $ {
             process.env.NODE\_ENV
         }-- - Make something great!)
     }
 });

请注意,如果环境变量中未设置端口,我们将使用端口 3000。现在,我们将在 package.json 文件中添加一个“script”,以便在使用 Babel 运行时使用开发环境。以下是修改后的文件:

现在,使用 yarn 运行以下命令安装 cross-env:

yarn add cross-env

这是更新后的 package.json 文件:

{
     "name": "makenodejsrestapi",
     "version": "1.0.0",
     "main": "index.js",
     "license": "MIT",
     "scripts": {
         "dev": "NODE\_ENV=development node src/index.js"
     },
     "dependencies": {
         "cross-env": "^5.1.3",
         "express": "^4.16.2"
     }
 }

现在我们可以使用以下命令添加 Babel 依赖项:

yarn add -D babel-preset-env babel-plugin-transform-object-rest-spread

运行命令后,您可以创建一个名为 .babelrc 的文件,并在其中提供有关应用程序的环境和插件信息。接下来我们将这样做:

{
     "presets": [
         ["env", {
             "targets": {
                 "node": "current"
             }
         }]
     ],
     "plugins": [
         ["transform-object-rest-spread", {
             "useBuiltIns": true
         }]
     ]
 }

transform-object-rest-spread 插件用于转换对象解构赋值的剩余属性。现在,我们也将使用 webpack 2:

yarn add -D webpack babel-core babel-loader webpack-node-externals

最后,我们将配置 webpack,因为我们也在上面添加了它的依赖项:

const nodeExternals = require('webpack-node-externals');
 const path = require('path');
 module.exports = {
     target: 'node',
     externals: [nodeExternals()],
     entry: {
         'index': './src/index.js'
     },
     output: {
         path: path.join(\_\_dirname, 'dist'),
         filename: '[name].bundle.js',
         libraryTarget: 'commonjs2',
     },
     module: {
         rules: [{
             test: /\.js$/,
             exclude: /node\_modules/,
             use: 'babel-loader'
         }]
     }
 }

现在,我们也运行 package.json 脚本:

"scripts": { "dev:build": "webpack -w", "dev": "cross-env NODE\_ENV=development node dist/index.bundle.js" }

最后,我们可以运行我们的应用程序了:

为了更直观地查看,以下是运行构建后的输出结果:

请注意,上面我们运行了两条命令:

  • 第一条命令只是构建了应用程序并准备了 Babel 构建。
  • 第二条命令实际执行请求,您可以在控制台中看到输出结果。

现在,我们终于要安装 ES Lint 了:

yarn add -D eslint eslint-config-equimper

现在,创建一个名为“.eslintrc”的新文件,并添加以下内容:

{ “extends” : “equimper” }

完成此操作后,如果您未遵循正确的 ES 标准,将会收到警告。当您的项目需要严格遵循规范时,此工具非常有用。

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。立即开始学习 →

接下来我们将添加什么?

在本节中,我们将设置此应用程序后端所需的更多工具:

  • 添加猫鼬、身体解析器、摩根、压缩、头盔
  • 设置配置文件夹
  • 设置常量

添加 Mongoose

要将 mongoose 和其他提到的模块添加到您的应用程序中,请运行以下命令:

yarn add mongoose body-parser compression helmet && yarn add -D morgan

需要注意的是,我们指定的模块顺序,将按照相同的顺序下载。

为了确保我们理解一致,以下是我的 package.json 文件内容:

现在,我们将使用以下命令再次编译我们的项目:

yarn dev

请确保项目仍在运行。现在,在 src 文件夹内新建一个 config 文件夹,并在其中创建一个名为 constants.js 的文件,内容如下:

const devConfig = {};
const testConfig = {};
const prodConfig = {};
const defaultConfig = {

PORT: process.env.PORT || 3000,
};

function envConfig(env) {
     switch (env) {
         case 'development':
             return devConfig;
         case 'test':
             return testConfig;
         default:
             return prodConfig;
     }
 }

 //Take defaultConfig and make it a single object 
 //So, we have concatenated two objects into one 
 export default { ...defaultConfig,
     ...envConfig(process.env.NODE\_ENV),
 };

现在,回到 index.js 文件,我们将添加对该常量文件的依赖,并将对 PORT 的引用更改为使用此文件,如下所示:

import express from 'express';
import constants from './config/constants';
const app = express();
app.listen(constants.PORT, err => {
    if (err) {
        throw err;
    } else {
        console.log(`Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great.!`)
    }
});

现在,在 config 文件夹中创建一个名为 database.js 的新文件,并添加以下内容:

import mongoose from 'mongoose';
 import constants from './constants';

 //Removes the warning with promises 
 mongoose.Promise = global.Promise;

 //Connect the db with the url provided 
 try {
     mongoose.connect(constants.MONGO\_URL)
 } catch (err) {
     mongoose.createConnection(constants.MONGO\_URL)
 }
 mongoose.connection.once('open', () => console.log('MongoDB Running')).on('error', e => {
     throw e;
 })

我们还修改了 constants.js 文件中的 mongoose 连接配置,如下所示:

const devConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-dev', }; 
 const testConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-test', }; 
 const prodConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-prod', };

这样可以确保我们在使用不同配置文件和环境运行应用程序时,使用的数据库是不同的。您可以继续再次运行此应用程序。

当数据库在该端口上运行后,即可成功开始使用您的应用程序。

中间件设计

现在,我们将开始制作应用程序的中间件。

在 config 文件夹中,新建一个名为middleware.js 的文件,并添加以下内容:

import morgan from 'morgan';
 import bodyParser from 'body-parser';
 import compression from 'compression';
 import helmet from 'helmet';
 import {
     isPrimitive
 } from 'util';
 const isDev = process.env.NODE\_ENV === 'development';
 const isProd = process.env.NODE\_ENV === 'production';
 export default app => {
     if (isProd) {
         app.use(compression());
         app.use(helmet());
     }
     app.use(bodyParser.json());
     app.use(bodyParser.urlencoded({
         extended: true
     }));
     if (isDev) {
         app.use(morgan('dev'));
     }
 };

要使用此配置,还需要将导入语句添加到索引文件中,例如:

import express from 'express';
 import constants from './config/constants';
 import './config/database';
 import middlewareConfig from './config/middleware';
 const app = express(); //passing the app instance to middlewareConfig 

 middlewareConfig(app);
 app.listen(constants.PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(`Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great.!`)
     }
 });

现在,运行你的应用程序,它应该会在 3000 端口上处理一个 GET 请求!

注册用户

在本节中,我们将使用上一课中搭建的 MongoDB 环境,并以此为基础构建一个允许用户注册的应用程序。为了方便您理解,我们最新的 package.json 文件内容如下:

在本节中,我们将继续推进,实现用户注册功能。我们还将创建用户模型,以便将数据保存到数据库中。

完成本节后,您的文件结构至少应如下所示:

跟着课程一步步来,看看结果如何!

定义模型

我们将首先创建 User 模型。为此,请在 src > modules > users 目录下创建一个名为 user.model.js 的新文件,并添加以下内容:

import mongoose, {
     Schema
 } from 'mongoose';
 import validator from 'validator';
 import {
     passwordReg
 } from './user.validations';
 const UserSchema = new Schema({
     email: {
         type: String,
         unique: true,
         required: [true, 'Email is required!'],
         trim: true,
         validate: {
             validator(email) {
                 return validator.isEmail(email);
             },
             message: '{VALUE} is not a valid email!',
         },
     },
     firstName: {
         type: String,
         required: [true, 'FirstName is required!'],
         trim: true,
     },
     lastName: {
         type: String,
         required: [true, 'LastName is required!'],
         trim: true,
     },
     userName: {
         type: String,
         required: [true, 'UserName is required!'],
         trim: true,
         unique: true,
     },
     password: {
         type: String,
         required: [true, 'Password is required!'],
         trim: true,
         minlength: [6, 'Password need to be longer!'],
         validate: {
             validator(password) {
                 return passwordReg.test(password);
             },
             message: '{VALUE} is not a valid password!',
         },
     },
 });
 export default mongoose.model('User', UserSchema);

我们刚刚定义了用户模型的架构,其中包含各种属性,例如:

  • 为用户定义的属性
  • 此外,还提供了关于其类型属性、唯一性以及如何验证这些数据的元信息。
  • 请注意,我们还提供了一个验证函数。这使得向 MongoDB 集合中插入数据变得非常简单。

定义控制器

现在,我们将通过在 Controller 定义中使用 User 模型来启用它。在 src > modules > users 目录下创建一个名为 user.controllers.js 的新文件,并添加以下内容:

import User from './user.model';
 export async function signUp(req, res) {
     try {
         const user = await User.create(req.body);
         return res.status(201).json(user);
     } catch (e) {
         return res.status(500).json(e);
     }
 }

我们刚刚定义了一个 signUp 函数,它以请求对象和响应对象作为参数,并使用我们上面定义的 User 模型创建了它。

我们还返回了相应的响应以及他们的代码,以便用户在交易成功时收到通知。

定义应用程序路由

我们将为应用程序定义路由,以便指定用户必须访问哪些路径才能访问我们开发的应用程序。在 src > modules > users 目录下创建一个名为 user.routes.js 的新文件,并添加以下内容:

import {
     Router
 } from 'express';
 import \* as userController from './user.controllers';
 const routes = new Router();
 routes.post('/signup', userController.signUp);
 export default routes;

请注意,这样做行不通。我们必须在 modules 文件夹内定义模块 index.js,并添加以下内容:

import userRoutes from './users/user.routes';
 export default app => {
     app.use('/api/v1/users', userRoutes);
 };

现在我们可以运行我们的应用程序了,这将是我们应用程序的第一个实际版本。现在我们只需要对根目录下的 index.js 文件进行最后的修改:

以下是更新后的内容:

/\* eslint-disable no-console \*/
 import express from 'express';
 import constants from './config/constants';
 import './config/database';
 import middlewaresConfig from './config/middlewares';
 import apiRoutes from './modules';
 const app = express();
 middlewaresConfig(app);
 app.get('/', (req, res) => {
     res.send('Hello world!');
 });
 apiRoutes(app);
 app.listen(constants.PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(` Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great `);
     }
 });

现在运行该应用时,我们仍然可以看到应用正在运行:

Postman 非常适合 API 测试,学习这门课程可以帮助你了解它的用途。

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。立即开始学习 →

使用 MongoDB 和 Postman

接下来我们将使用两种必要的工具:

  1. Robomongo:点击此处下载。它是一款非常棒的工具,可以用来可视化 MongoDB 数据并进行查询。而且它是免费的!它适用于所有操作系统平台。
  2. Postman:点击此处下载。它是一款用于调用 API 并获取响应的工具。它拥有强大的可视化功能,还可以保存请求格式,从而节省大量时间。再次强调,它是免费的!Postman 支持所有操作系统平台。

打开 Robomongo 并连接到本地 MongoDB 实例后,您可以看到数据库已经存在:

我们已经准备好了一个由我们的应用程序生成的收藏集,它看起来会像这样:

目前这里是空的,因为我们还没有创建任何数据。我们很快就会创建!

尝试用户注册

现在我们打开 Postman。我们将使用以下 URL 调用 API:

http://localhost:3000/api/v1/users/signup

在 Postman 中,它看起来会像这样:

在调用此 API 之前,我们将尝试一个 Hello World 版本。看看此 API 会发生什么:

现在,我们回到注册 API。在成功调用之前,我们将尝试提供无效值,看看会遇到什么错误。如果邮箱地址无效,结果如下:

现在,我们再用正确的数据试试。开始吧!

故事还没结束。我们现在还可以看到数据正在被插入到 MongoDB 数据库中:

出色的!

添加更多验证

虽然我们已经在 User 模型中添加了一些验证,但如果我们也想在另一个文件中保留这些验证呢?为此,请在 src > modules > users 目录下创建一个名为 user.validations.js 的新文件,并添加以下内容:

import Joi from 'joi';
 export const passwordReg = /(?=.\*\d)(?=.\*[a-z])(?=.\*[A-Z]).{6,}/;
 export default {
     signup: {
         email: Joi.string().email().required(),
         password: Joi.string().regex(passwordReg).required(),
         firstName: Joi.string().required(),
         lastName: Joi.string().required(),
         userName: Joi.string().required(),
     },
 };

接下来,在路由文件中也添加此验证:

import {
       Router
   } from 'express';
   import validate from 'express-validation';
   import \* as userController from './user.controllers';
   import userValidation from './user.validations';
   const routes = new Router();
   routes.post('/signup', validate(userValidation.signup), userController.signUp);
   export default routes;

请注意:

  • 我们添加了从 Express 导入验证的功能。
  • 我们还添加了一个新的用户注册验证函数。

现在当我们尝试使用相同的凭据注册用户时,会收到错误提示:

这其实不是验证的问题,而是因为我们试图插入另一个使用相同邮箱的用户。我们换个方法试试:

现在,我们可以修正这个问题,并在 MongoDB 数据库中看到数据:

太棒了!我们成功地为项目添加了强大的验证功能。

密码加密和用户登录

我们将更多地与用户互动。在上一节课中,我们成功保存了一个新用户。但这种方法的主要问题是,用户的密码以明文形式保存。这将是我们现在要在应用程序中做出的改进之一。

为了确保您不会错过任何重要信息,我们最新的 package.json 文件内容如下:

在本课中,我们将继续推进,实现用户密码加密功能。除此之外,我们还将进行以下更改:

  • 在 webpack 构建中添加rimraf和 clean dist
  • 对用户密码进行加密
  • 利用护照制定本地战略
  • 允许用户登录

添加 rimraf 依赖项

我们将首先使用以下命令在项目中添加rimraf依赖项:

yarn add -D rimraf

要重新构建您的项目,请运行以下命令:

yarn

现在,让我们把 rimraf 也添加到 package.json 文件中:

“scripts”: {
 “clean”: “rimraf dist”,
 “dev:build”: “yarn run clean && webpack -w”,
 “dev”: “cross-env NODE\_ENV=development nodemon dist/index.bundle.js”
}

现在,运行以下命令:

yarn dev:build

运行此命令后,dist 文件夹将被刷新,并在构建过程完成后恢复:

用于加密密码的库

现在,我们将向项目中添加一个库,以便在将用户密码保存到数据库之前对其进行加密。这样,即使数据库遭到黑客攻击,我们也能确保密码安全。

运行以下命令:

yarn add bcrypt-nodejs

这样,该库就会添加到我们的项目中。

修改模型

现在,我们需要修改模型,以便在收到明文密码请求时,能够设置一个加密后的密码。请在 user.model.js 文件中添加以下代码。

UserSchema.pre('save', function(next) {
     if (this.isModified('password')) {
         this.password = this.\_hashPassword(this.password);
     }
     return next();
 });
 UserSchema.methods = {
     \_hashPassword(password) {
         return hashSync(password);
     },
     authenticateUser(password) {
         return compareSync(password, this.password);
     },
 };

在上述代码中,`this` 指的是请求中指定的当前用户。此外,`authenticateUser` 函数会在我们尝试登录时立即调用,用户会传递一个明文密码。我们会对该密码进行哈希处理,之后才会将其与数据库中的值进行比较。

现在,我们尝试发送一个新的请求,看看是否有效。以下是我的请求:

当我运行此请求时,我们得到的响应如下:

现在我们来查看一下数据库,也会看到类似的情况:

现在,我们将为我们的应用程序提供一个登录 API。

使用 Passport 登录

我们将使用名为Passport 的库。您也可以自由使用任何其他身份验证库,例如 Facebook、Google 等。

接下来,我们需要向项目中添加两个库。让我们运行以下命令来完成此操作:

yarn 添加 passport passport-local

完成上述步骤后,我们在src文件夹内创建一个名为 services 的新文件夹。我们将在 services 文件夹内创建一个名为 auth.services.js 的新文件,并添加以下内容:

import passport from 'passport';
 import LocalStrategy from 'passport-local';
 import User from '../modules/users/user.model';
 const localOpts = {
     usernameField: 'email',
 };
 const localStrategy = new LocalStrategy(localOpts, async (email, password, done) => {
     try {
         const user = await User.findOne({
             email
         });
         if (!user) {
             return done(null, false);
         } else if (!user.authenticateUser(password)) {
             return done(null, false);
         }
         return done(null, user);
     } catch (e) {
         return done(e, false);
     }
 });
 passport.use(localStrategy);
 export const authLocal = passport.authenticate('local', {
     session: false
 });

在这里,我们尝试了一种本地策略,该策略本质上是异步的,数据(用户的电子邮件地址和密码)会发送到 Passport 库。然后,该库将验证用户身份并返回响应。

我们还会添加 Passport 作为中间件。以下是修改后的文件:

import morgan from 'morgan';
 import bodyParser from 'body-parser';
 import compression from 'compression';
 import helmet from 'helmet';
 import passport from 'passport';

 const isDev = process.env.NODE\_ENV === 'development';
 const isProd = process.env.NODE\_ENV === 'production';

 export default app => {
     if (isProd) {
         app.use(compression());
         app.use(helmet());
     }
     app.use(bodyParser.json());

     app.use(bodyParser.urlencoded({
         extended: true
     }));
     app.use(passport.initialize());

     if (isDev) {
         app.use(morgan('dev'));
     }
 };

在这里,我们也使用我们的应用程序实例初始化了 Passport 库。

向控制器添加登录信息

现在是时候在控制器层添加登录功能了。请将以下函数添加到控制器中:

export function login(req, res, next) {
 res.status(200).json(req.user);
 return next();
}

请注意,这是我们最终的 Controller 文件的样子:

提供登录路线

我们还需要提供登录 API 的路由。我们将修改 user.routes.js 文件。请将以下路由及其导入语句添加到该文件中:

import {
 authLocal
} from ‘../../services/auth.services’;

routes.post(‘/login’, authLocal, userController.login);

以下是我们最终文件的样子:

尝试登录功能

现在我们将使用之前创建的凭据尝试以下 POST API:

http://localhost:3000/api/v1/users/login

如果凭证正确,则会发生以下情况:

这真是太棒了!我们不仅成功登录了现有用户,还通过加密保护了他的密码。

添加 JWT 身份验证

目前,我们能够在应用程序中注册新用户:

我们还允许用户登录我们的应用程序:

在了解本文将要制作的内容之前,让我们先来看看当前的 _package.json_file 文件是什么样子:

在本节中,我们将添加以下功能:

  • 我们将实现 JWT 身份验证并添加一个密钥密码
  • 添加新的 passport-jwt 库
  • 添加 JSON Web Token 库
  • 仅以 JSON 格式发送必需字段作为响应。

JSON Web Token 如何存储数据?

当我们提供要加密的数据以及秘密密码时,这些数据将被加密以形成 JWT 令牌的各个部分,例如:

如上所示,一个令牌可以包含用户的身份信息以及与该用户相关的其他数据。

添加 JWT 密钥

接下来,我们打开 _constants.js_file 文件,并在开发配置中添加一个 JWT 密钥:

const devConfig = {
 MONGO\_URL: ‘mongodb://localhost/makeanodejsapi-dev’,
 JWT\_SECRET: ‘thisisasecret’,
};

接下来,我们将使用以下命令安装两个库:

yarn add jsonwebtoken passport-jwt

现在,转到身份验证服务文件和 JWT 服务文件,并在该文件中添加以下代码行:

import { Strategy as JWTStrategy, ExtractJwt } from ‘passport-jwt’;

import User from ‘../modules/users/user.model’;
import constants from ‘../config/constants’;

接下来,让 Passport 使用指定的策略:

// Jwt strategy
 const jwtOpts = {
   jwtFromRequest: ExtractJwt.fromAuthHeader('authorization'),
   secretOrKey: constants.JWT\_SECRET,
 };

 const jwtStrategy = new JWTStrategy(jwtOpts, async (payload, done) => {
   try {
     //Identify user by ID
     const user = await User.findById(payload.\_id);

     if (!user) {
       return done(null, false);
     }
     return done(null, user);
   } catch (e) {
     return done(e, false);
   }
 });

 passport.use(localStrategy);
 passport.use(jwtStrategy);

 export const authLocal = passport.authenticate('local', { session: false });
 export const authJwt = passport.authenticate('jwt', { session: false });

为了测试这种方法是否有效,我们现在将在路由JS文件中使用私有路由。最终的文件内容如下所示:

import userRoutes from ‘./users/user.routes’;
import { authJwt } from ‘../services/auth.services’;

export default app => {
 app.use(‘/api/v1/users’, userRoutes);
 app.get(‘/hello’, authJwt, (req, res) => {
 res.send(‘This is a private route!!!!’);
 });
};

验证 JWT

我们来试试看,验证一下 JWT 现在是否能在 Postman 中正常工作:

现在我们需要在请求中添加一个只属于特定用户的 JWT 令牌。

我们将为 User 模型添加功能,使其在用户登录时也包含 JWT 令牌。因此,让我们向 User 模型 JS 文件添加更多库:

import jwt from ‘jsonwebtoken’;
import constants from ‘../../config/constants’;

现在我们可以解密令牌并获取用户信息。

创建 JWT 令牌

我们还需要创建一个为用户生成令牌的方法。现在就来添加这个方法:

UserSchema.methods = {

     createToken() {
       return jwt.sign(
         {
           \_id: this.\_id,
         },
         constants.JWT\_SECRET,
       );
     },
     toJSON() {
       return {
         \_id: this.\_id,
         userName: this.userName,
         token: `JWT ${this.createToken()}`,
       };
     },
   };

使用 toJSON() 方法也很重要。我们在令牌前面附加了 JWT,因为 Passport 库使用它来识别 JWT 令牌。

现在,我们再尝试登录用户:

这次我们甚至还收到了 JWT 令牌作为响应。该令牌将包含用户 ID 和用户名。现在我们有了一个可用的 JWT 示例!

让我们复制 JWT 值,现在尝试使用私有路由:

通过用户和对象关联发布帖子

接下来,我们就可以在应用程序中注册新用户了:

我们还允许用户登录我们的应用程序:

在了解本文将要制作的内容之前,让我们先来看看当前的package.json文件是什么样子:

在本节中,我们将添加以下功能:

  • 我们将为帖子创建一个新的资源。现在,用户也可以创建帖子了。
  • 将用户设为帖子作者
  • 解决我们在之前的帖子中提出的一些问题

创建帖子模型

就像我们之前对 User 模型所做的那样,Post 模型也需要进行同样的操作,例如创建一个新文件夹。在本课程结束时,您的项目中将包含以下新文件夹和文件:

我们将首先创建 Post 模型。我们还会添加所需的验证。接下来,我们再添加一个用于 Mongoose 唯一性验证的库:

yarn add mongoose-unique-validator

我们还将添加一个新的 Slug 库。为此,请使用以下命令安装它:

纱线添加蛞蝓

如果你想知道什么是 slugify,简单来说,就是文章的 URL 应该看起来像文章标题。这样看起来美观,而且文章内容也能在 URL 中一览无余,这是一个很好的做法。

现在,我们也可以添加这个库了。我们的模型将如下所示:

import mongoose, { Schema } from 'mongoose';
   import slug from 'slug';
   import uniqueValidator from 'mongoose-unique-validator';

   const PostSchema = new Schema({
     title: {
       type: String,
       trim: true,
       required: [true, 'Title is required!'],
       minlength: [3, 'Title need to be longer!'],
       unique: true,
     },
     text: {
       type: String,
       trim: true,
       required: [true, 'Text is required!'],
       minlength: [10, 'Text need to be longer!'],
     },
     slug: {
       type: String,
       trim: true,
       lowercase: true,
     },
     user: {
       type: Schema.Types.ObjectId,
       ref: 'User',
     },
     favoriteCount: {
       type: Number,
       default: 0,
     },
   }, { timestamps: true });

   PostSchema.plugin(uniqueValidator, {
     message: '{VALUE} already taken!',
   });

   PostSchema.pre('validate', function (next) {
     this.\_slugify();

     next();
   });

   PostSchema.methods = {
     \_slugify() {
       this.slug = slug(this.title);
     },
   };

   PostSchema.statics = {
     createPost(args, user) {
       return this.create({
         ...args,
         user,
       });
     },
   };

   export default mongoose.model('Post', PostSchema);

我们在上述模型中进行了以下操作:

  • Post 模型定义的字段
  • 针对每个字段添加了验证
  • 为整个 Post 对象添加了验证
  • 我们根据帖子标题对其进行 slug 化处理,并将该值保存下来。

在上面的代码中,接下来我们将在控制器中添加 createPost 方法。

创建帖子控制器

现在我们需要一个控制器,以便用户能够实际执行与帖子相关的操作。

根据上面所示的目录结构,在 post 模块中定义一个新的文件 post.controller.js,并添加以下内容:

import Post from './post.model';

 export async function createPost(req, res) {
   try {
     const post = await Post.createPost(req.body, req.user.\_id);
     return res.status(201).json(post);
   } catch (e) {
     return res.status(400).json(e);
   }
 }

当遇到错误或成功创建新帖子时,我们会返回相应的响应。

创建帖子路由

现在让我们在应用程序中的 posts 模块下的 post.route.js 文件中创建指向 Post 控制器的路由,内容如下:

import { Router } from 'express';

 import \* as postController from './post.controllers';
 import { authJwt } from '../../services/auth.services';

 const routes = new Router();

 routes.post(
   '/',
   authJwt,
 );

 export default routes;

我们也要修改 index.js 文件来实现这个功能。最终内容如下:

import userRoutes from ‘./users/user.routes’;
import postRoutes from ‘./posts/post.routes’;

export default app => {
 app.use(‘/api/v1/users’, userRoutes);
 app.use(‘/api/v1/posts’, postRoutes);
};

验证帖子 API

现在我们将尝试使用 POST API 创建一个新帖子。

首先,尝试登录用户,以便获取 JWT 令牌,然后通过此 URL 调用创建帖子 API:

http://localhost:3000/api/v1/posts

以下是我们尝试的方法以及得到的反馈:

我们已经填充了日期和别名字段。其中也包含用户 ID。让我们也看看 MongoDB 中的这篇文章:

如果我们再次调用此 API 创建帖子,将会失败,因为标题已被占用:

这意味着我们的验证也运行正常。

将标题设为强制性

我们可以实施更多验证措施,例如将帖子标题设为必填项。

让我们在 posts 模块中创建一个名为 post.validations.js 的新文件,并添加以下内容:

import Joi from 'joi';

   export const passwordReg = /(?=.\*\d)(?=.\*[a-z])(?=.\*[A-Z]).{6,}/;
   export default {
     signup: {
       body: {
         email: Joi.string().email().required(),
         password: Joi.string().regex(passwordReg).required(),
         firstName: Joi.string().required(),
         lastName: Joi.string().required(),
         userName: Joi.string().required(),
       },
     },
   };

我们还需要修改路由文件,以包含此验证。以下是修改后的文件:

import { Router } from 'express';
 import validate from 'express-validation';
 import \* as postController from './post.controllers';
 import { authJwt } from '../../services/auth.services';
 import postValidation from './post.validations';

 const routes = new Router();
 routes.post(
   '/',
   authJwt,
   validate(postValidation.createPost),
   postController.createPost,
 );

 export default routes;

我们已从上面使用的 authJwtobject 中获取到用户 ID。现在我们收到的消息是:

我们将尽快修改回复方式,使其更加得体。

通过 ID 获取数据并填充另一个对象。

接下来,我们就可以在应用程序中注册新用户了:

我们还允许用户登录我们的应用程序:

我们还创建了一篇与用户相关的帖子:

在本节中,我们将添加以下功能:

  • 我们将通过其 ID 获取帖子。
  • 我们还将创建控制器和路由
  • 我们将向您展示如何在帖子中填充用户信息。
  • 我们将使用的其他库

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。立即开始学习 →

将 HTTP 状态库添加到控制器

要添加此库,请运行以下命令:

yarn add http-status

现在,我们也可以在用户控制器中使用这个库了。首先,让我们导入这个库:

import HTTPStatus from 'http-status';

接下来,我们将不再使用控制器中原有的状态码(例如 200 等),而是修改此库提供的状态码,如下所示:

export async function signUp(req, res) {
     try {
       const user = await User.create(req.body);
       return res.status(HTTPStatus.CREATED).json(user.toAuthJSON());
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

   export function login(req, res, next) {
       res.status(HTTPStatus.OK).json(req.user.toAuthJSON());
     return next();
   }

我们将在帖子控制器中执行相同的操作:

import HTTPStatus from 'http-status';
   import Post from './post.model';

   export async function createPost(req, res) {
     try {
       const post = await Post.createPost(req.body, req.user.\_id);
         return res.status(HTTPStatus.CREATED).json(post);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

按 ID 获取帖子

我们将在帖子控制器中定义一个新函数,用于通过 ID 获取帖子:

export async function getPostById(req, res) {
   try {
     const post = await Post.findById(req.params.id);
     return res.status(HTTPStatus.OK).json(post);
   } catch (e) {
     return res.status(HTTPStatus.BAD\_REQUEST).json(e);
   }
 }

接下来,我们来定义这个函数的路由:

routes.get('/:id', postController.getPostById);

我们的 MongoDB 数据库中有以下帖子:

我们将通过我们的 API 获取此帖子:

这个响应的问题在于,我们得到了 MongoDB 中存在的所有字段。这不是我们想要的。让我们在 Post 模型中进行修改:

PostSchema.methods = {
   \_slugify() {
     this.slug = slug(this.title);
   },
   toJSON() {
     return {
       \_id: this.\_id,
       title: this.title,
       text: this.text,
       createdAt: this.createdAt,
       slug: this.slug,
       user: this.user,
       favoriteCount: this.favoriteCount,
     };
   },
 };

在模型中应用 toJSON() 函数后,我们现在会得到如下响应:

在 POST 响应中获取用户数据

仔细观察上面的JSON数据,我们会发现其中包含用户字段,该字段保存着用户的ID。但如果我们还想在同一个对象中保留用户的其他信息呢?

只需稍微修改 getPostById 函数,并将函数中的 post 常量修改如下:

const post = await Post.findById(req.params.id).populate('user');

我们刚刚添加了一个填充调用,现在的响应将是:

当我们填充用户对象时,toJSON 函数也能正常工作。但这里存在一个问题,因为我们还得到了上面提到的 token 字段,这不应该发生!

让我们修改用户模型来改进这一点:

UserSchema.methods = {
   \_hashPassword(password) {
     ...
   },
   authenticateUser(password) {
     ...
   },
   createToken() {
     ...
   },
   toAuthJSON() {
     ...
   },
   toJSON() {
     return {
       \_id: this.\_id,
       userName: this.userName,
     };
   },

我们修改了上面的 toJSON 方法,使 token 字段不包含在响应本身中。

问题依然存在。我们来看看尝试登录用户时会发生什么:

你看,这里也没有 token 字段。要解决这个问题,请转到用户控制器中的登录函数,并按如下方式修改:

export function login(req, res, next) {
 res.status(HTTPStatus.OK).json(req.user.toAuthJSON());
 return next();
}

现在,我已经使用了 toAuthJSON 函数本身。如果您现在尝试登录,应该可以像以前一样正常登录了!

从数据库中获取所有数据

接下来,我们就可以在应用程序中注册新用户了:

我们还允许用户登录我们的应用程序:

我们还创建了一篇与用户相关的帖子:

在本节中,我们将添加以下功能:

  • 完善帖子控制器,添加更多功能

扩展控制器

目前,我们的帖子控制器仅具备以下功能:

  • 创建帖子
  • 按 ID 获取帖子

现在,我们还将添加更多功能,首先我们将获取所有帖子列表。

获取所有帖子

让我们在帖子控制器中添加一个新方法来获取所有帖子,从而扩展其功能:

export async function getPostsList(req, res) {
     try {
       const posts = await Post.find().populate('user');
       return res.status(HTTPStatus.OK).json(posts);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

这里,我们返回了帖子。让我们修改路由文件,使用上面添加的这个函数:

routes.get('/', postController.getPostsList);

我们尚未在身份验证中添加此功能,以便即使未经身份验证的用户也能至少发布帖子。现在让我们试试这个 API:

目前数据库中有 11 篇文章,因此上述 API 测试没有问题。但是,如果文章数量超过 5 万篇会怎样呢?在这种情况下,我们将面临严重的性能问题。

分页功能来帮忙了

我们可以根据用户请求返回有限数量的文章。在文章模型中,我们可以提供分页参数,例如:

PostSchema.statics = {
     createPost(args, user) {
       ...
     },
     list({ skip = 0, limit = 5 } = {}) {
       return this.find()
         .sort({ createdAt: -1 })
         .skip(skip)
         .limit(limit)
         .populate('user');
     },
   };

列表函数最初只返回前 5 篇文章。如果跳过 5 篇文章,列表函数会返回 5 篇文章,但会先跳过前 5 篇文章。我们也要修改一下控制器:

export async function getPostsList(req, res) {
   const limit = parseInt(req.query.limit, 0);
   const skip = parseInt(req.query.skip, 0);
   try {
     const posts = await Post.list({ limit, skip });
     return res.status(HTTPStatus.OK).json(posts);
   } catch (e) {
     return res.status(HTTPStatus.BAD\_REQUEST).json(e);
   }
 }

现在,当我们提供这些值时,会得到这样的响应:

更新帖子并添加验证

接下来,我们就可以在应用程序中注册新用户了:

我们还允许用户登录我们的应用程序:

我们还创建了一篇与用户相关的帖子:

在本课中,我们将添加以下功能:

  • 我们将更新帖子,并确保更新帖子的用户是帖子的作者。
  • 创建验证字段

我们将在接下来的课程中添加更多关于帖子操作的内容。

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。立即开始学习 →

扩展控制器

目前,我们的帖子控制器仅具备以下功能:

  • 创建一个 pos
  • 按 ID 获取帖子
  • 获取所有帖子列表

现在,我们还将添加更多功能,首先是允许用户更新帖子。

更新帖子

让我们在帖子控制器中添加一个新方法来更新帖子,从而扩展其功能:

export async function updatePost(req, res) {
     try {
       const post = await Post.findById(req.params.id);
       if (!post.user.equals(req.user.\_id)) {
         return res.sendStatus(HTTPStatus.UNAUTHORIZED);
       }

       Object.keys(req.body).forEach(key => {
         post[key] = req.body[key];
       });

       return res.status(HTTPStatus.OK).json(await post.save());
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

这就是我们上面所做的:

  • 通过 JWT 令牌确认用户是否与 Post 对象中的用户相同
  • 如果用户不是同一人,则返回 UNAUTHORIZED 响应。
  • 如果用户相同,我们会获取请求中传递的每个键,并据此更新帖子。
  • 所有更新完成后,我们返回 OK 响应

让我们修改验证文件,使用上面添加的这个函数:

import Joi from 'joi';

   export default {
     createPost: {
       body: {
         title: Joi.string().min(3).required(),
         text: Joi.string().min(10).required(),
       },
     },
     updatePost: {
       body: {
         title: Joi.string().min(3),
         text: Joi.string().min(10),
       },
     },
   };

我们刚刚在 updatePost 函数中添加了字段长度至少为两个的验证。现在该编写路由文件了:

routes.patch(
   '/:id',
   authJwt,
   validate(postValidation.updatePost),
   postController.updatePost,
 );

更新帖子

现在工作已经完成,我们将验证上述工作。让我们使用 Postman 发送一个 PATCH 请求,如下所示:

太棒了,成功了!连文章的别名都更新了。请确保我们在 Post 模型中添加了这个方法:

PostSchema.pre(‘validate’, function (next) {
 this.\_slugify();
 next();
});

接下来,也请尝试对帖子文本进行同样的操作。

授权用户删除帖子

目前,我们能够在应用程序中注册新用户:

我们还允许用户登录我们的应用程序:

我们成功创建了一篇与用户相关的帖子:

在本课中,我们将添加以下功能:

  • 我们将允许作者删除帖子。
  • 授权功能
  • 添加一个名为“漂亮”的工具

扩展控制器

目前,我们的帖子控制器仅具备以下功能:

  • 创建帖子
  • 按 ID 获取帖子
  • 获取所有帖子列表
  • 更新帖子

现在,我们还将添加更多功能,首先是允许用户删除帖子。

删除帖子

让我们在帖子控制器中添加一个新方法来删除帖子,从而扩展其功能:

export async function deletePost(req, res) {
     try {
         const post = await Post.findById(req.params.id);

       if (!post.user.equals(req.user.\_id)) {
         return res.sendStatus(HTTPStatus.UNAUTHORIZED);
       }

       await post.remove();
       return res.sendStatus(HTTPStatus.OK);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

这就是我们上面所做的:

  • 通过 JWT 令牌确认用户是否与 Post 对象中的用户相同
  • 如果用户不是同一人,我们将返回 UNAUTHORIZED 响应。
  • 如果用户是同一人,我们将删除该帖子。
  • 一旦帖子被删除,我们就返回 OK 响应。

现在该处理路由文件了:

routes.delete('/:id', authJwt, postController.deletePost);

删除帖子

现在工作已经完成,我们将验证上述工作。让我们使用 Postman 发送一个 DELETE 请求,如下所示:

现在,您可以使用类似以下的查询来验证此帖子是否既不在“获取所有帖子”API 中,也不在 MongoDB 中:

添加 Prettier 库

我们可以使用以下 yarn 命令添加 prettier 库:

yarn add -D 更漂亮

完成上述步骤后,以下是我的更新后的 package.json 文件:

{
     "name": "makeanodejsrestapi",
     ...,
     "scripts": {
       ...,
       "prettier": "prettier --single-quote --print-width 80 --trailing-comma all --write 'src/\*\*/\*.js'"
     },
     "dependencies": {
       ...
     },
     "devDependencies": {
       ...,
       "prettier": "^1.3.1",
       ...
     }
   }

我们仅展示了所做的更改。我们还将使用以下命令添加一个 ES 代码检查库:

yarn add -D eslint-config-prettie

现在,我们将创建一个名为 .eslintrc 的新文件,并添加以下注释:

{
 “extends”: [
 “equimper”,
 “prettier”
 ]
}

如果您忘记添加分号或缩进,只需运行以下命令,它们就会自动添加:

纱线更漂亮

是不是很神奇?:) 这也显示了哪些文件被修改了:

我们将继续使用此命令和库,因为它确实简化了我们的工作!

一门高级培训课程,教你如何使用 Node.js、Express、MongoDB 等技术构建应用程序。立即开始学习 →

收藏帖子并管理帖子统计信息

接下来,我们就可以在应用程序中注册新用户了:

我们还允许用户登录我们的应用程序:

我们成功创建了一篇与用户相关的帖子:

在本节中,我们将添加以下功能:

  • 用户在验证身份后可以收藏帖子,收藏也会增加 favoriteCount 计数器变量的值。
  • 修改用户和帖子模型以适应此需求。
  • 在 Post 中添加递增/递减静态变量

修改用户模型

我们将添加一个新字段来存储用户收藏的帖子。为此,请编辑 _user.model.js_file 文件,并在密码字段之后添加一个新字段:

favorites: {
         posts: [{
           type: Schema.Types.ObjectId,
           ref: 'Post'
         }]
       }

我们还会添加一个函数来使用此字段:

UserSchema.methods = {
     \_hashPassword(password) {
       ...
     },
     authenticateUser(password) {
       ...
     },
     createToken() {
       ...
     },
     toAuthJSON() {
       ...
     },
     toJSON() {
       ...
     },

     \_favorites: {
       async posts(postId) {
         if (this.favorites.posts.indexOf(postId) >= 0) {
           this.favorites.posts.remove(postId);
         } else {
           this.favorites.posts.push(postId);
         }
         return this.save();
       }
     }
   };

扩展后控制器

让我们在这里也添加一个函数,以使用我们在模型中定义的功能。首先在_post.controller.js_file中使用导入语句:

import User from ‘../users/user.model’;

接下来,我们调用 Usermodel 函数:

export async function favoritePost(req, res) {
     try {
       const user = await User.findById(req.user.\_id);
       await user.\_favorites.posts(req.params.id);
       return res.sendStatus(HTTPStatus.OK);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

最后,让我们修改 _post.routes.js_file 文件以访问此函数:

routes.post(‘/:id/favorite’, authJwt, postController.favoritePost);

现在是时候测试这条路线了。在 Postman 中,从数据库或“获取所有帖子”API 中选择一个帖子 ID 后,向目标 API 发送 GET 请求:

接下来,我们从 MongoDB 验证这是否有效:

我们只保留了对象 ID,因为这样可以避免数据重复。如果您再次调用相同的 API,您会发现一些奇怪的事情,用户模型中的收藏夹里的帖子 ID 已经被移除了!

我们还在 Post 模型中保留了 favoriteCount。现在让我们来实现它。我们将把这段逻辑添加到 Postmodel 类中:

PostSchema.statics = {
   createPost(args, user) {
     ...
   },
   list({ skip = 0, limit = 5 } = {}) {
     ...
   },

   incFavoriteCount(postId) {
     return this.findByIdAndUpdate(postId, { $inc: { favoriteCount: 1 } });
   },

   decFavoriteCount(postId) {
     return this.findByIdAndUpdate(postId, { $inc: { favoriteCount: -1 } });
   }
 };

incFavoriteCount 和 decFavoriteCount 方法首先使用 Mongo 的 findByIdAndUpdate 方法查找帖子 ID,然后使用 $inc 运算符,如果要增加收藏数,则加 1;如果要减少收藏数,则减 1。

现在我们也来修改 User 模型。首先添加以下导入语句:

import Post from '../posts/post.model';

然后,修改此处的 _favoritesmethod 功能:

\_favorites: {
       async posts(postId) {
         if (this.favorites.posts.indexOf(postId) >= 0) {
           this.favorites.posts.remove(postId);
           await Post.decFavoriteCount(postId);
         } else {
           this.favorites.posts.push(postId);
           await Post.incFavoriteCount(postId);
         }

         return this.save();
       }
     }

Now the User model issue we stated above will resolve and the favoriteCount in Post model will also work:

If you hit the same API again and again, the result won’t change. Excellent! We have working APIs where a user can favorite a post as well.

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Identifying if a Post is already a Favorite to User

the last section, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were able to create a post related to a user:

Update a post:

And delete a Post as well:

In this section, we will be adding the following functionalities:

  • We will send them if the current post is favorite to the user or not so that front-end can make decisions based on this fact
  • We will make a route modification and work on Controller functions as well

Extending route

We just need to make very few modifications in our_post.route.js_file:

routes.get(‘/:id’, authJwt, postController.getPostById);
routes.get(‘/’, authJwt, postController.getPostsList);

We just added authJwt in these two existing lines. Once this is done, if I try to get Post list without Authorization header, we will get an error:

Extending the User model

Now, we will add more information to the post JSON if it is favorable to the current Authorizeduser.

Move to the _user.model.js_file and add this function in _favorites:

isPostIsFavorite(postId) {
     if (this.favorites.posts.indexOf(postId) >= 0) {
       return true;
     }
    return false;
    }

Move to the _post.controller.js_file now and modify the getPostByIdfunction:

export async function getPostById(req, res) {
     try {
       const promise = await Promise.all([
         User.findById(req.user.\_id),
           Post.findById(req.params.id).populate('user')
       ]);

       const favorite = promise[0].\_favorites.isPostIsFavorite(req.params.id);
       const post = promise[1];

       return res.status(HTTPStatus.OK).json({
         ...post.toJSON(),
         favorite
       });
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Here, we just added a new field favorite which will be reflected in a Post API like this:

We will modify our getPostsListfunction as well to include a Promise and return the appropriate response:

export async function getPostsList(req, res) {
     const limit = parseInt(req.query.limit, 0);
     const skip = parseInt(req.query.skip, 0);
     try {
       const promise = await Promise.all([
         User.findById(req.user.\_id),
         Post.list({ limit, skip })
       ]);

       const posts = promise[1].reduce((arr, post) => {
         const favorite = promise[0].\_favorites.isPostIsFavorite(post.\_id);

         arr.push({
           ...post.toJSON(),
           favorite
         });

         return arr;
       }, []);

       return res.status(HTTPStatus.OK).json(posts);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Let’s run this now and get all posts:

Excellent.

Conclusion

your will learn a lot of Node and API knowledge from this post but has more and more topic that we should know eg.secrity, rate limit, best practice I hope you enjoy for this.

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Disclaimer

This post contains affiliate links to products. We may receive a commission for purchases made through these links.


文章来源:https://dev.to/kris/building-rest-api-in-nodejs-mongodb-passport-jwt-4hbj