Node.js API开发中如何实现Model层与Controller层分离
Node/Express 实现 Model 与 Controller 解耦的实践方案
完全不需要写强耦合的代码,问题核心是没有正确处理异步操作的返回值,反而把HTTP层的res对象透传到了数据层,才导致职责混淆。只要遵循分层原则,配合Promise/async-await做异步流程控制,完全可以实现清晰的职责拆分。
分层的核心规则
各层严格只处理自己职责范围内的逻辑,绝对不跨层透传HTTP上下文对象(req/res):
- Model层:仅负责数据读写操作,不感知HTTP请求、响应逻辑,所有操作结果通过Promise返回
- Controller层:仅处理HTTP相关逻辑,包括参数校验、权限校验、调用Model/Service、构造响应格式、处理请求异常
- 路由层:仅做URL路径和Controller方法的映射,不写业务逻辑
- 入口文件:仅做服务初始化、中间件挂载、全局配置注册,不写业务逻辑
具体实现示例
1. 改造Model层(以User.js为例)
把原来接收res参数的方法改成返回Promise,所有数据库查询的结果、错误都通过Promise向外传递,不要在Model里写任何响应相关的代码。
如果用的是mysql2这类自带Promise支持的数据库驱动,可以直接用async/await写法:
// models/User.js class User { constructor(db) { this.db = db; } // 纯数据操作,只返回查询结果 async getUserList() { const [rows] = await this.db.query("SELECT UserID, UserName FROM User"); return rows; } } module.exports = User;
如果用的是旧版仅支持回调的数据库驱动,手动包装一层Promise即可:
getUserList() { return new Promise((resolve, reject) => { this.db.query("SELECT UserID, UserName FROM User", (err, result) => { if (err) return reject(err); resolve(result); }) }) }
2. 实现Controller层
Controller层接收路由传来的req/res对象,调用Model方法拿到数据后再处理响应,绝对不把req/res传到Model里:
// controllers/userController.js const User = require('../models/User'); async function getUserList(req, res) { try { // 从app上下文取提前初始化好的数据库连接 const userModel = new User(req.app.get('db')); // 等待异步查询结果返回,再在Controller里做响应 const userList = await userModel.getUserList(); res.json({ code: 0, data: userList }); } catch (err) { // 错误统一在Controller层处理,不用散在每个数据查询逻辑里 res.status(500).json({ code: 500, message: '获取用户列表失败' }) } } module.exports = { getUserList };
3. 简化路由与入口文件
入口文件和路由层只做初始化、转发逻辑,完全不堆砌业务代码:
// index.js 服务入口 const express = require('express'); const mysql = require('mysql2/promise'); const userController = require('./controllers/userController'); const app = express(); // 全局初始化数据库连接,挂载到app上下文供各层调用 const init = async () => { const db = await mysql.createConnection({ host: 'localhost', user: 'root', password: '', database: 'test' }); app.set('db', db); // 路由仅做路径和方法的映射 app.post('/user', userController.getUserList); app.listen(3000, () => { console.log('服务启动在3000端口'); }) } init();
进阶优化
当项目业务逻辑更复杂时,可以在Model和Controller之间新增Service层,进一步拆分职责:
- Model:只做单表基础CRUD,不带任何业务判断
- Service:负责组合多个Model的调用、处理业务规则、加工组装数据
- Controller:只处理HTTP协议相关逻辑,调用Service拿到最终结果后返回响应
另外通用的错误处理、响应格式封装可以抽成Express全局中间件,减少各Controller里的重复代码。
内容的提问来源于stack exchange,提问作者ChiefMo
相关产品推荐
相关产品推荐

