本教程是本系列中的第5部分。
- 第1部分:带Babel的最小Node.js设置
- 第2部分:如何在Node.js中设置Express.js
- 第3部分:如何在Node.js中用Express.js创建一个REST API
- 第4部分:在Express中用Mongoose设置MongoDB
Node + Express + MongoDB是一个强大的技术栈,用于后端应用程序提供CRUD操作。它为你提供了暴露API(Express路由)、添加业务逻辑(Express中间件和Express路由中的逻辑)以及使用数据库(MongoDB)的真实数据的一切。它是建立MERN(MongoDB、Express、React、Node)、MEAN(MongoDB、Express、Angular、Node)或MEVN(MongoDB、Express、Vue、Node)技术栈的理想选择。一切都会缺少的是用React、Angular、Vue或其他东西的前端应用程序。但这是另一节的内容了。
本节首先关注的是为我们的REST API连接MongoDB和Express。之前我们已经在Express.js应用程序中设置了MongoDB,并在数据库中加入了初始数据,但在Express中还没有将其用于RESTful API。现在,我们想确保每一个通过这个REST API进行的CRUD操作都是从/到MongoDB数据库中读取或写入的,而不是像以前那样在我们的Express路由中使用样本数据。这就是为什么我们需要通过Mongoose将我们的Express路由与MongoDB连接起来,以实现两个世界的融合。
在我们的src/index.js中,我们用 MongoDB 数据库设置并启动了 Express 应用程序,我们已经有一个 Express 中间件,它将模型作为上下文传递给我们所有的 Express 路由。在此之前,这些模型一直是样本数据。现在,我们正在使用连接我们和MongoDB数据库的Mongoose模型。由于文件夹/文件的数据结构与以前相同,所以在将模型作为上下文传递给Express路由时没有任何变化。
...
import models from './models';
const app = express();
...
app.use((req, res, next) => { req.context = { models, me: models.users[1], }; next();});
...
然而,我的用户(认证用户)可以从数据库的种子数据中检索出来。在models对象上不再有users 数组作为样本数据,因为models现在是我们与MongoDB数据库的接口。
...
import models from './models';
const app = express();
...
app.use(async (req, res, next) => { req.context = { models, me: await models.User.findByLogin('rwieruch'), }; next();});
...
即使我们还不知道认证的用户,因为我们没有从外部为其传递任何数据给REST API,我们只是取任何我们知道存在于我们数据库中的用户,因为之前的MongoDB数据库播种。findByLogin 方法在我们的模型上是可用的,因为我们之前已经把它实现为自定义方法,让它通过用户名或电子邮件来检索用户。
现在让我们深入了解一下我们的Express路由。我们有针对会话、用户和消息实体的路由。会话实体是第一位的。同样,我们可以使用模型的接口--由Mongoose提供的接口--来与数据库进行交互,而不是使用之前在模型上提供的样本数据。在src/routes/session.js中,修改以下几行代码。
import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => { const user = await req.context.models.User.findById( req.context.me.id, ); return res.send(user);});
export default router;
路由函数变成了一个异步函数,因为我们现在正在处理对MongoDB数据库的异步请求。我们用async/await来处理该函数的异步性。
由于我们之前通过上下文对象将模型方便地传递给了每个具有应用程序范围的Express中间件的Express路由,所以我们可以在这里利用它。我们之前从MongoDB数据库中任意抽取的认证用户,可以用来从数据库中检索当前会话用户。
让我们来处理src/routes/user.js文件中的用户路由,它提供了 RESTful API 端点,用于通过 id 获取用户或单个用户。这两个API请求都将导致对MongoDB数据库的读取操作。
import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => { const users = await req.context.models.User.find(); return res.send(users);});
router.get('/:userId', async (req, res) => { const user = await req.context.models.User.findById( req.params.userId, ); return res.send(user);});
export default router;
第一个获取用户列表的API端点并没有从请求中获得任何输入参数。但是第二个API端点可以访问用户标识符,以便从MongoDB数据库中读取正确的用户。
最后但并非最不重要的是src/routes/message.js文件中的消息路由。除了按标识符读取消息和单一消息外,我们还有用于创建消息和删除消息的API端点。这两种操作都应该导致对MongoDB数据库的写操作。
import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => { const messages = await req.context.models.Message.find(); return res.send(messages);});
router.get('/:messageId', async (req, res) => { const message = await req.context.models.Message.findById( req.params.messageId, ); return res.send(message);});
router.post('/', async (req, res) => { const message = await req.context.models.Message.create({ text: req.body.text, user: req.context.me.id, });
return res.send(message);});
router.delete('/:messageId', async (req, res) => { const message = await req.context.models.Message.findById( req.params.messageId, );
if (message) { await message.remove(); }
return res.send(message);});
export default router;
用Mongoose完成删除数据库中的消息,有更短的方法。然而,通过这种方式,你要确保触发可以在模型中设置的数据库钩子。你已经在之前的src/models/user.js文件中设置了其中一个钩子,即删除钩子。
...
userSchema.pre('remove', function(next) { this.model('Message').deleteMany({ user: this._id }, next);});
...
每当一个用户被删除,这个钩子会确保所有属于这个用户的信息也被删除。这样你就不必在每次删除一个实体的操作时都要适当地清理数据库了。
基本上,这就是用Mongoose连接MongoDB和Express路线的方法。所有用Mongoose设置的模型都可以作为你的MongoDB数据库的接口。一旦用户点击了你的REST API,你就可以在Express路由中对你的MongoDB数据库进行读或写操作。
练习
- 确认你上一节的源代码。请注意,该项目不能在沙盒中正常运行,因为没有数据库。
- 确认你在上一节的改动。
- 检查替代PostgreSQL与Sequelize实现的源代码。
- 实验你的 REST API 与 cURL 操作。
本教程是本系列2的第1部分。
- 第2部分:如何处理Express中的错误