Express 项目结构

讨论 Express 项目从基础分层到模块化结构的演进路径,重点说明为什么拆 Controller、Service、Repository 和全局中间件。

#type / concept #status / growing #tech / dev / backend #resource / express #resource / nodejs

[!info] related notes

Express 项目结构

一句话定义

Express 项目结构本质上是在回答一个问题:

这套服务应该把“接请求、写业务、访问数据、做横切能力”分别放在哪里,才能在项目变大后仍然可维护。

为什么 Express 项目结构经常会失控

因为 Express 本身很轻。

它很适合快速起服务,但也意味着:

  • 框架不会强制你分层
  • 路由里很容易直接塞业务逻辑
  • 业务一多,app.js 和 route handler 就会迅速膨胀

所以 Express 项目结构不是“目录美观问题”,而是长期维护成本问题。

常见演进路径

可以先把 Express 项目的结构演进记成三步:

  1. 基础分层
  2. 服务层解耦
  3. 模块化组织

第一层:基础分层

适合:

  • 小型项目
  • 原型验证
  • 路由和业务都还不复杂

典型结构:

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 边界
创建于 2025/1/1 更新于 2026/5/27