使用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/ # 你现有视图文件不用动
代码实现示例
- 抽离数据库配置到
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
- 实现模型层
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 }
- 实现控制器层
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 }
- 调整
app.js的路由注册逻辑
const orderController = require('./controllers/orderController') app.get('/activeorders', orderController.getActiveOrders) // 新增订单提交路由(示例) app.post('/orders', orderController.addOrder)
插入数据的实现逻辑
就是上面示例的流程:
- 前端表单提交数据到对应的POST路由
- 控制器拿到
req.body的表单数据,做参数合法性校验 - 控制器把校验后的数据传给对应模型的插入方法
- 模型层用参数绑定的方式执行SQL插入,返回结果给控制器
- 控制器根据插入结果决定跳转成功页/返回错误提示
调整后的收益
- 可复用性强:模型层的方法可以在多个控制器里复用,比如后续要做API接口给移动端调用,直接复用同一个
getActiveOrders方法即可,不用重复写SQL - 易测试:测试模型方法不需要启动Express服务,直接传参调用就能验证返回值正确性
- 易扩展:后续要换数据库、加缓存、修改查询逻辑,只需要改对应模型文件,控制器和视图完全不用动
- 协作成本低:新开发者可以快速按照职责找到对应逻辑,不会出现改查询影响HTTP响应的问题
内容的提问来源于stack exchange,提问作者NicLovin
相关产品推荐
相关产品推荐

