CRUD
本文档介绍如何在 Midway 中使用 @midwayjs/crud。
@midwayjs/crud 不是一个“只能自动生成接口”的脚手架,它本质上提供的是一套面向资源的标准 CRUD 能力:
- 统一的增删改查 service 抽象
- 统一的分页 / 排序 / 过滤 / 搜索协议
- 可选的 REST 路由快捷生成
- 和现有 validation / swagger / web 路由体系的接入
如果你经常在不同模块里重复写下面这些代码:
findAndCountsaveupdatedelete- 手动解析
page、limit、sort - 为每个资源写一套几乎一样的 Controller
那这个组件就是用来把这些重复劳动收起来的。
相关信息:
| 描述 | |
|---|---|
| 可用于标准项目 | ✅ |
| 可用于 Serverless | ❌ |
| 可用于一体化 | ✅ |
| 包含独立主框架 | ❌ |
| 包含独立日志 | ❌ |
这个组件能做什么
先用一句话概括:
先提供一个可复用的 CRUD service,再按需把它暴露成 HTTP 接口。
这意味着它支持两种使用方式:
-
只把它当数据访问层能力来用
适合你的业务逻辑比较复杂,不想让组件替你生成接口。 -
把它当接口快捷层来用
适合资源型接口很多,希望快速拿到统一的 REST API。
所以它解决的是“资源型接口的重复代码”问题,但不会替代你的业务 service。
复杂业务依然应该写在你自己的 service 里,例如:
- 下单时要检查库存、优惠券、支付状态
- 创建用户时要同步多个系统
- 删除资源前要做权限和状态机校验
这些逻辑应该继续在你的业务 service 中完成,CRUD 组件只是提供一个稳定的资源操作基座。
核心概念
在开始之前,先理解这三个层次。
1. CrudService<T>
这是最核心的抽象,定义了统一的资源操作接口。
list(query)
findOne(id)
create(data)
update(id, data)
delete(id)
你可以把它理解成“一个标准化的资源仓储服务接口”。
2. 数据库适配层
Midway CRUD 目前提供了 4 个官方适配基类:
TypeOrmCrudService<T>MikroCrudService<T>SequelizeCrudService<T>MongooseCrudService<T>
它们都实现了相同的 CRUD 核心接口,但底层分别接不同的数据访问组件。
3. HTTP 暴露层
这是可选的。
如果你希望快速生成路由,可以使用:
- 类式:
@Crud() - 函数式:
defineCrudRoutes()
这两种方式都只是把同一个 CrudService 暴露成 HTTP 接口,不会生成第二套独立逻辑。
什么时候适合用
适合:
- 大量资源型接口,结构相似
- 列表查询都需要统一分页、排序、过滤
- 想减少重复的 Repository / Model 调用代码
- 想让不同模块的 API 风格保持一致
不适合:
- 主要是复杂工作流,不是标准资源接口
- 一个接口需要跨多个聚合、多个事务、多个外部系统
- 你希望所有查询语义都完全自由,不接受统一约束
如果一个模块的核心不是“资源管理”,而是“复杂业务流程”,那更适合直接写普通 Controller + Service,而不是强行套 CRUD。
安装依赖
先安装 CRUD 组件本身:
$ npm i @midwayjs/crud@4 --save
然后根据你使用的数据库组件安装对应依赖。
# TypeORM
$ npm i @midwayjs/typeorm@4 typeorm --save
# MikroORM
$ npm i @midwayjs/mikro@4 @mikro-orm/core --save
# Sequelize
$ npm i @midwayjs/sequelize@4 sequelize sequelize-typescript --save
# Mongoose
$ npm i @midwayjs/mongoose@4 mongoose --save
或者在 package.json 中增加如下依赖后,重新安装。
{
"dependencies": {
"@midwayjs/crud": "^4.0.0",
"@midwayjs/typeorm": "^4.0.0",
"@midwayjs/mikro": "^4.0.0",
"@midwayjs/sequelize": "^4.0.0",
"@midwayjs/mongoose": "^4.0.0",
"typeorm": "^0.3.26",
"@mikro-orm/core": "^6.4.5",
"sequelize": "^6.37.5",
"sequelize-typescript": "^2.1.6",
"mongoose": "^8.9.5"
}
}
启用组件
在 src/configuration.ts 中引入组件。
下面以 koa + typeorm + crud 为例:
import { Configuration } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as typeorm from '@midwayjs/typeorm';
import * as crud from '@midwayjs/crud';
@Configuration({
imports: [
koa,
typeorm,
crud,
],
})
export class MainConfiguration {}
如果你使用其他数据库组件,只需要把对应组件加到 imports 中即可。
入门:先从 service-only 开始
对于新手 来说,最容易理解的方式不是先生成路由,而是先把它当一个“可复用的数据服务”。
这也是推荐的理解顺序。
为什么推荐先学 service-only
因为这样你能先理解:
- 组件真正提供的核心是什么
- 业务代码应该写在哪里
- 路由层只是一个可选外壳
如果一上来就只看 @Crud(),很容易误以为这只是一个自动生成接口的装饰器。
最小 TypeORM 示例
先定义一个资源 service:
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { TypeOrmCrudService } from '@midwayjs/crud/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from '../entity/user';
@Provide()
export class UserCrudService extends TypeOrmCrudService<UserEntity> {
@InjectEntityModel(UserEntity)
repo: Repository<UserEntity>;
}
然后在业务 service 里直接组合它:
import { Provide, Inject } from '@midwayjs/core';
@Provide()
export class UserService {
@Inject()
userCrudService: UserCrudService;
async listUsers() {
return this.userCrudService.list({
page: 1,
limit: 20,
sort: [],
filters: [],
});
}
async createUser(input: CreateUserDTO) {
return this.userCrudService.create(input);
}
}
这时候:
- 你已经获得统一的 CRUD 能力
- 但不会自动生成任何 HTTP 路由
- 业务仍然由你自己的
UserService负责组织
这也是最适合复杂业务场景的用法。
其他数据库适配
如果你的项目不是 TypeORM,也可以使用其他官方适配。
MikroORM
import { Provide } from '@midwayjs/core';
import { InjectRepository } from '@midwayjs/mikro';
import { MikroCrudService } from '@midwayjs/crud/mikro';
@Provide()
export class UserCrudService extends MikroCrudService<UserEntity> {
@InjectRepository(UserEntity)
repo;
}
Sequelize
import { Provide } from '@midwayjs/core';
import { InjectRepository } from '@midwayjs/sequelize';
import { SequelizeCrudService } from '@midwayjs/crud/sequelize';
import { Repository } from 'sequelize-typescript';
@Provide()
export class UserCrudService extends SequelizeCrudService<UserModel> {
@InjectRepository(UserModel)
repo: Repository<UserModel>;
}