Express 项目结构
讨论 Express 项目从基础分层到模块化结构的演进路径,重点说明为什么拆 Controller、Service、Repository 和全局中间件。
[!info] related notes
- 所属 MOC: Node.js MOC, 后端开发 MOC, Node.js 后端面试 MOC
- 相关概念: express, Node.js Web 框架选型:Express、Koa 与 NestJS, 后端测试
- 相关实践: Express 实现登录, nodejs后端api测试搭建集成测试框架
Express 项目结构
一句话定义
Express 项目结构本质上是在回答一个问题:
这套服务应该把“接请求、写业务、访问数据、做横切能力”分别放在哪里,才能在项目变大后仍然可维护。
为什么 Express 项目结构经常会失控
因为 Express 本身很轻。
它很适合快速起服务,但也意味着:
- 框架不会强制你分层
- 路由里很容易直接塞业务逻辑
- 业务一多,
app.js和 route handler 就会迅速膨胀
所以 Express 项目结构不是“目录美观问题”,而是长期维护成本问题。
常见演进路径
可以先把 Express 项目的结构演进记成三步:
- 基础分层
- 服务层解耦
- 模块化组织
第一层:基础分层
适合:
- 小型项目
- 原型验证
- 路由和业务都还不复杂
典型结构:
src/
├── routes/
├── controllers/
├── models/
├── middlewares/
└── app.js
这里的核心分工是:
routes:声明 HTTP 入口controllers:解析参数、调用下层、返回响应models:数据模型或直接数据库访问middlewares:日志、鉴权、异常等横切逻辑
这种结构可以起步,但当业务变复杂后,controller 很容易变成“大杂烩”。
第二层:服务层解耦
适合:
- 已经有明显业务逻辑
- 控制器开始变厚
- 不同接口之间开始共享业务流程
典型结构:
src/
├── api/
│ ├── routes/
│ ├── controllers/
│ └── middlewares/
├── services/
├── repositories/
└── models/
这一步最重要的变化不是多了几个文件夹,而是多了两条边界:
controller不再承担主要业务逻辑repository不再把数据库访问散落到各处
换句话说:
- Controller 负责“收请求和发响应”
- Service 负责“业务规则和流程编排”
- Repository 负责“查库、写库、事务落点”
第三层:模块化结构
适合:
- 中大型项目
- 多模块团队协作
- 认证、用户、订单、文件、通知等子域已经明显分开
一种更稳的组织方式通常是按模块聚合:
src/
├── config/
├── modules/
│ ├── auth/
│ │ ├── auth.routes.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ └── auth.repository.ts
│ ├── users/
│ └── products/
├── middlewares/
├── shared/
└── app.ts
这种结构的优势是:
- 单个模块内的修改更集中
- 团队协作边界更清晰
- 更容易和测试、文档、DTO、权限体系配套
一个更推荐的默认心智模型
对于大多数 Node.js / Express 后端项目,一个足够稳的默认结构通常是:
src/
├── app.ts
├── config/
├── modules/
│ └── <module>/
│ ├── <module>.routes.ts
│ ├── <module>.controller.ts
│ ├── <module>.service.ts
│ ├── <module>.repository.ts
│ ├── dto/
│ └── tests/
├── middlewares/
├── shared/
│ ├── errors/
│ ├── logger/
│ ├── auth/
│ └── utils/
└── infrastructure/
这套结构的关键不是命名本身,而是几条稳定分工:
app.ts:组装应用,不写业务modules/*:按业务能力聚合middlewares/:放全局横切逻辑shared/:放跨模块复用但不直接承载业务的能力infrastructure/:数据库、缓存、外部服务客户端等基础设施
哪些内容不该放错层
不要把业务规则塞进 route handler
例如:
- 扣库存
- 订单状态流转
- 权限组合判断
- 跨表事务
这些都更应该进 service。
不要把数据库细节散落进 controller
如果 controller 里到处都是 SQL / ORM 细节,后续改事务、改索引、改存储方式都会很痛苦。
不要把所有工具函数都塞进 utils
utils 很容易成为新的垃圾桶。
更稳的是区分:
- 真正通用的小工具
- 认证、日志、错误、配置这类明确能力
测试结构怎么跟着走
如果你已经把代码拆成 Controller / Service / Repository,测试层通常也会自然分层:
- Service 测业务规则
- API / integration test 测接口行为
- 少量 repository / DB integration test 测关键持久化边界
所以结构设计和测试设计其实是一起演进的。
最适合面试时怎么讲
如果面试官问“Express 项目怎么组织”,一个稳的回答是:
Express 本身很轻,所以我通常不会让路由直接承担业务逻辑。小项目至少会分 route、controller、service;项目再大一点会加 repository 和按模块聚合。这样做的目的不是为了目录好看,而是把请求入口、业务规则、数据访问和横切能力拆开,后续做测试、鉴权、事务和团队协作都更稳。
边界与易混淆点
- 不是目录越深越高级,关键是职责边界是否稳定
- 小项目不必一开始就上很重的分层
- 但一旦 controller 开始变厚,就该尽快补 service / repository 边界