You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:24:29