在Node.js项目里接入MongoDB,写法和连接MySQL大体一致,核心区别在于用Mongoose这类ODM来定义模型、再用Express把它挂到HTTP接口上。下面按原文的落地顺序整理一条完整链路:启动数据库实例、安装依赖、建立连接、定义模型、写路由、控制器、服务层,最后在app.js中注册路由,并用一个用户登录接口做验证。
一、先用mongod指定数据目录启动实例
MongoDB不会自动建库文件目录,需要先给实例指定--dbpath。原文的做法是在项目所在文件夹里建一个db文件夹,然后进入mongodb安装目录下的bin目录,在cmd中执行:
- .\mongod.exe --dbpath=db文件夹所在目录
复制代码
--dbpath的值必须指向一个真实存在的目录,路径不对时实例起不来,后续mongoose连接自然也就全部失败。客户端连接串中使用的默认端口是27017,与这里启动的实例保持一致。
二、安装mongoose依赖
在项目根目录执行:
使用--save会把依赖写入package.json的dependencies,方便团队与部署环境复现依赖树。
三、创建数据库连接模块
在项目根目录新建db/index.js,作为自定义模块对外提供连接对象:
- const mongoose = require('mongoose')
- mongoose.connect('mongodb://127.0.0.1:27017/CompanySystem').then(res=>{
- console.log('数据库连接成功')
- }).catch(error=>{
- console.log(error,'数据库连接失败')
- })
复制代码
这里有几个参数含义需要说清:mongodb://是协议头,127.0.0.1:27017是实例地址与端口,CompanySystem是目标库名。mongoose.connect返回Promise,所以用then/catch分流成功与失败;失败分支把error直接打印出来,是排查连不上问题的第一现场。
连接模块写好后,要在bin目录下的www文件中引入它,让进程启动时就完成初始化,而不是等到第一次请求才连库。
四、定义用户模型
先创建用户实例模型。模型的作用是把集合结构声明出来,Mongoose据此生成对应的集合与操作方法:
- //导入mongoose模块
- const mongoose = require('mongoose')
- const Schema = mongoose.Schema
- //user模型 =》 user集合
- const userType = {
- username: String, //用户名
- password: String, //密码
- }
- //注册用户模型
- const userModel = mongoose.model('user',new Schema(userType))
- //导出
- module.exports = userModel
复制代码
userType是字段声明,username与password都声明为String类型;mongoose.model('user', schema)的第一个参数是模型名,Mongoose会据此映射到对应的集合。定义完成后可以用mongodb可视化工具确认库和集合是否已经生成。
五、创建登录路由
在routes文件夹下新建UserRouter.js,只负责把URL和控制器方法对应起来:
- //导入express模块
- const express = require('express')
- //创建路由
- const UserRouter = express.Router()
- //导入控制器
- const UserController = require('../controller/UserController.js')
- //创建登录接口
- UserRouter.post('/api/login',UserController.login)
- //导出路由
- module.exports = UserRouter
复制代码
express.Router()返回一个独立的路由实例,post('/api/login', handler)把POST请求绑定到控制器方法上,注意这里传的是方法引用而不是调用结果。
六、控制器层处理请求与响应
在controller文件夹下新建UserController.js,负责取参、调服务层、按结果组装响应:
- //导入服务层
- const UserService = require('../../services/admin/UserService.js')
- //创建控制层
- const UserController = {
- login: async (req,res)=>{
- //console.log(req.body)
- const result = await UserService.login(req.body)
- if( result.length === 0 ){
- res.send({
- code: 0,
- message: '用户名和密码不匹配'
- })
- }else{
- res.send({
- code: 1,
- message: '登录成功',
- data: {
- username: result[0].username,
- }
- })
- }
- }
- }
- //导出控制层
- module.exports = UserController
复制代码
逻辑很直白:req.body取到前端提交的用户名密码,交给服务层查询。因为find返回的是数组,所以用result.length判断是否命中;为空即账号密码组合不存在,返回code为0和提示语;命中则返回code为1,并从result[0]中取username回给前端。此处返回结构里刻意没有回传password字段。
七、服务层执行查询
在service文件夹下新建UserService.js,把数据访问单独收口:
- //导入用户模型
- const UserModel = require("../../models/UserMondel")
- //创建服务层
- const UserService = {
- login: async ({username,password}) => {
- return UserModel.find({username,password})
- }
- }
- //导出服务层
- module.exports = UserService
复制代码
login的参数用解构直接拿到username与password,find的条件对象等价于同时匹配两个字段,返回Promise,所以控制器里可以用await接收。这里要注意require的路径与文件名要和实际文件保持一致,路径写错会直接抛模块找不到的异常。
八、全局注册路由并启动测试
在app.js中注册并使用UserRouter:
- //导入UserRouter路由
- const UserRouter = require('../routes/UserRouter')
- //注册使用UserRouter路由
- app.use(UserRouter)
复制代码
原文此处省略了用express创建web服务器app的代码,实际项目中按既有骨架补上即可。注册完成后启动服务器,用POST方式请求/api/login,分别用一个错误组合与一个正确组合验证两个分支的返回值。
九、这套分层可以固定复用的五个步骤
只要用的是MongoDB,新增一个业务接口基本都走同样顺序:第一步定义实例模型,声明字段与集合;第二步创建路由,绑定URL与方法;第三步写控制层,决定拿到参数后做什么判断和响应;第四步写服务层,执行具体的数据库查询;第五步在app.js中全局注册使用该路由。分层之后,路由只管映射、控制器只管流程、服务层只管数据,改动单点不会牵连整条链路。
十、几个容易踩的细节
一是连接失败时优先看catch里打印的error,再回头核对mongod是否已用正确的--dbpath启动、端口是否对应。二是登录判断依赖find返回数组这一事实,如果改用findOne,返回的是单条文档或null,result.length判断就会失效,需要同步改成判空。三是原文的查询条件把用户名和密码一起放进find,密码以明文参与匹配,这种写法只适合入门演示,真实项目应改为先按用户名取用户、再对密码做哈希校验。四是跨目录require较多,controller引用services、service引用models,任何一处相对路径写错都会在启动或首次请求时立即报错。 |