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

使用Node、Express、PostgreSQL的Web应用如何遵循MVC架构

架构调整建议

现有结构的问题

你当前的结构不符合标准MVC规范,核心问题是queries.js混合了模型层和控制器层的职责:既实现了数据库查询的模型逻辑,又包含了处理HTTP响应、渲染视图的控制器逻辑,还耦合了req/res这类HTTP专属对象。当项目规模扩大后,数据库逻辑、业务逻辑、HTTP处理逻辑揉在一起,确实会出现维护难、迭代效率低的问题。

调整后的标准MVC结构

你需要做的核心拆分就是把数据库操作和HTTP处理逻辑完全分开,拆分后各层职责明确:

  • 视图层:你现有.ejs模板不用动,仅负责数据渲染
  • 控制器层:仅处理HTTP请求、参数校验、调用模型层获取数据、决定返回响应/渲染视图
  • 模型层:仅封装数据库的增删改查操作,绝对不能依赖任何HTTP相关对象(req/res),只接收参数、返回处理后的数据
  • 配置层:抽离公共配置(比如数据库连接实例),避免重复初始化

推荐目录结构

项目根目录
├── app.js          # 仅注册中间件、路由,不写业务逻辑
├── config/
│   └── db.js       # 数据库连接配置
├── controllers/    # 控制器层
│   └── orderController.js
├── models/         # 模型层
│   └── Order.js
└── views/          # 你现有视图文件不用动

代码实现示例

  1. 抽离数据库配置到config/db.js
const { Pool } = require('pg')
const pool = new Pool({
  user: process.env.USER,
  host: process.env.HOST,
  database: process.env.DB,
  password: process.env.PASSWORD,
  port: process.env.PORT,
})
module.exports = pool
  1. 实现模型层models/Order.js,仅做数据操作
const pool = require('../config/db')

// 获取有效订单
const getActiveOrders = async () => {
  const queryOrders = 'SELECT * FROM orders WHERE complete=0 ORDER BY date_rec DESC;'
  const results = await pool.query(queryOrders)
  return results.rows
}

// 新增订单(示例方法)
const createOrder = async (orderData) => {
  const { customer, amount, date_rec } = orderData
  const insertQuery = `
    INSERT INTO orders (customer, amount, date_rec, complete)
    VALUES ($1, $2, $3, 0)
    RETURNING *
  `
  const results = await pool.query(insertQuery, [customer, amount, date_rec])
  return results.rows[0]
}

module.exports = {
  getActiveOrders,
  createOrder
}
  1. 实现控制器层controllers/orderController.js,仅处理HTTP逻辑
const OrderModel = require('../models/Order')

const getActiveOrders = async (request, response) => {
  try {
    const ordersObj = await OrderModel.getActiveOrders()
    response.render('pages/activeorders', { ordersObj })
  } catch (error) {
    console.error(error)
    response.status(500).render('pages/error', { msg: '订单获取失败' })
  }
}

// 新增订单的控制器逻辑(示例)
const addOrder = async (request, response) => {
  try {
    // 这里可加参数校验逻辑
    await OrderModel.createOrder(request.body)
    response.redirect('/activeorders')
  } catch (error) {
    console.error(error)
    response.status(400).render('pages/error', { msg: '订单提交失败,请检查输入' })
  }
}

module.exports = {
  getActiveOrders,
  addOrder
}
  1. 调整app.js的路由注册逻辑
const orderController = require('./controllers/orderController')

app.get('/activeorders', orderController.getActiveOrders)
// 新增订单提交路由(示例)
app.post('/orders', orderController.addOrder)

插入数据的实现逻辑

就是上面示例的流程:

  1. 前端表单提交数据到对应的POST路由
  2. 控制器拿到req.body的表单数据,做参数合法性校验
  3. 控制器把校验后的数据传给对应模型的插入方法
  4. 模型层用参数绑定的方式执行SQL插入,返回结果给控制器
  5. 控制器根据插入结果决定跳转成功页/返回错误提示

调整后的收益

  • 可复用性强:模型层的方法可以在多个控制器里复用,比如后续要做API接口给移动端调用,直接复用同一个getActiveOrders方法即可,不用重复写SQL
  • 易测试:测试模型方法不需要启动Express服务,直接传参调用就能验证返回值正确性
  • 易扩展:后续要换数据库、加缓存、修改查询逻辑,只需要改对应模型文件,控制器和视图完全不用动
  • 协作成本低:新开发者可以快速按照职责找到对应逻辑,不会出现改查询影响HTTP响应的问题

内容的提问来源于stack exchange,提问作者NicLovin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 10:57:01