写点什么

Prisma 不能优雅的支持 DTO,试试 Vona ORM 吧

作者:
  • 2025-08-08
    河南
  • 本文字数:2616 字

    阅读完需:约 9 分钟

Prisma不能优雅的支持DTO,试试Vona ORM吧

在 Nodejs 生态中,Prisma 是一个非常流行的 ORM 库,支持 Typescript,提供了非常友好的类型推断能力。但是,Prisma 却不能优雅的支持 DTO。在与其他后端框架整合时,DTO 是进行参数验证、生成 Swagger 元数据的关键节点。如果不能像推断类型一样自动推断出 DTO,那么,我们就仍然需要手工创建 DTO。随着业务的增长,复杂的表间关系会让手工补充 DTO 的工作日益繁重。


而 Vona ORM 就提供了非常便利的工具,使我们可以非常直观的动态推断出 DTO,就像推断类型一样,从而解放我们的双手,显著提升生产力。甚至可以说,能够自动推断 DTO,为 Nodejs 后端框架打开了一扇窗。


Vona 本身就是一款更直观的 Nodejs 框架,如果大家第一次接触,可以先参考这篇文章:你认为Vonajs提供的这些特性会比Nestjs更好用吗?


限于篇幅,这里不展开讲解 Vona ORM 所有的知识点,而是以目录树为例,演示如何查询一棵目录树,以及如何动态生成 DTO,并最终生成 Swagger 元数据。

1. 创建 Entity

在 VSCode 中,可以通过右键菜单Vona Create/Entity创建 Entity 的代码骨架:


@Entity('demoStudentCategory')export class EntityCategory extends EntityBase {  @Api.field()  name: string;
@Api.field(v.optional()) categoryIdParent?: TableIdentity;}
复制代码


  • 行 2: 继承自 EntityBase,就会自动提供 5 个基础字段:id、createdAt、updatedAt、deleted、iid

  • iid:是实例 Id,通过多实例的机制支持多租户系统的开发

  • 行 4、7: 定义两个业务字段:name、categoryIdParent

  • @Api.field:通过此装饰器定义的信息,可同时应用于参数验证和 Swagger 元数据

  • v.optional:声明为可选字段

  • 更多信息,参见:Vona Entity

2. 创建 Model

在 VSCode 中,可以通过右键菜单Vona Create/Model创建 Model 的代码骨架:


import { EntityCategory } from '../entity/category.ts';
@Model({ entity: EntityCategory })export class ModelCategory extends BeanModelBase<EntityCategory> {}
复制代码


  • 行 3: entity:指定 Model 所对应的 Entity

  • 行 4: 继承自 BeanModelBase,从而拥有大量操作数据库的方法,如:CRUD、聚合、分组,等等

3. 创建树形结构

如果要创建一棵目录树,本质就是建立 Model 引用自身的递归结构。Vona ORM 同样支持 4 种关系:1对11对多多对1多对多。那么,在这里,我们就需要采用1对多来创建目录的自身引用关系。


import { EntityCategory } from '../entity/category.ts';
@Model({ entity: EntityCategory,+ relations: {+ children: $relation.hasMany(() => ModelCategory, 'categoryIdParent', {+ autoload: true,+ columns: ['id', 'name'],+ }),+ },})export class ModelCategory extends BeanModelBase<EntityCategory> {}
复制代码


  • 行 5: relations:可以定义多个关系

  • 行 6: children:定义一个 1 对多的关系

  • $relation.hasMany:

  • 参数 1: 引用自身 ModelCategory

  • 参数 2: 设置关联外键 categoryIdParent

  • 参数 3: 关联选项

  • autoload:是否自动加载关联数据

  • columns:控制关联数据的字段列表

4. 创建 Controller

在 VSCode 中,可以通过右键菜单Vona Create/Controller创建 Controller 的代码骨架:


@Controller()export class ControllerCategory extends BeanBase {}
复制代码


接下来我们创建一个 Api,用于获取目录树:


export class ControllerCategory extends BeanBase {  @Web.get('getCategoryTree')  async getCategoryTree() {  }}
复制代码


  • 行 2: 通过 @Web.get 定义一个 api,path 为 getCategoryTree

5. 查询目录树

一般而言,我们还需要创建一个 Service,从而实现以下调用链:Controller->Service->Model->操作数据库。为了简化起见,在这里,我们直接在 Controller 中调用 Model 方法:


export class ControllerCategory extends BeanBase {  @Web.get('getCategoryTree')  async getCategoryTree() {    const tree = await this.scope.model.category.select({      columns: ['id', 'name'],    });    return tree;  }}
复制代码


  • 行 4: 通过this.scope取得 Category Model,然后调用 select 方法

  • columns:指定要查询的字段列表


由于前面我们设置 children 关系为autoload: true,因此,查询结果tree就是一棵完整的目录树。下面我们看一下tree的类型推断效果:




6. 自动推断 DTO

现在我们自动推断 DTO,并且设为 API 的返回数据的类型:


export class ControllerCategory extends BeanBase {  @Web.get('getCategoryTree')+ @Api.body(v.array(v.object($Dto.get(() => ModelCategory, { columns: ['id', 'name'] }))))  async getCategoryTree() {    const tree = await this.scope.model.category.select({      columns: ['id', 'name'],    });    return tree;  }}
复制代码


  • 行 3: 通过 @Api.body 定义 API 返回数据的类型:

  • v.array:定义数组类型

  • v.object:定义对象类型

  • $Dto.get:动态推断 DTO

  • 参数 1:指定目标 Model

  • 参数 2:指定选项

  • columns:指定要提取的字段列表


同样,由于前面我们设置 children 关系为autoload: true,因此,$Dto.get生成的 DTO 就是一棵完整的目录树。下面我们看一下 API 的 Swagger 效果:





从示意图中,我们可以清晰的看到,这棵树引用的 children 类型是名称为demo-student.entity.category_2c7d642ee581efa300341e343180fbb0ecdc785d的动态 Entity 的数组,从而形成一种递归的引用关系。

7. 封装 DTO

虽然我们已经实现了预期的目标,但是 Vona ORM 提供的能力还没有结束。我们可以创建一个新的 DTO,将前面的代码$Dto.get(() => ModelCategory, { columns: ['id', 'name'] })封装起来,从而用于其他地方:


在 VSCode 中,可以通过右键菜单Vona Create/Dto创建 DTO 的代码骨架:


@Dto()export class DtoCategoryTree {}
复制代码


然后我们通过继承机制来封装 DTO:


@Dto()export class DtoCategoryTree + extends $Dto.get(() => ModelCategory, { columns: ['id', 'name'] }) {}
复制代码


现在,我们再使用新创建的 DTO 来改造前面的 API 代码:


export class ControllerCategory extends BeanBase {  @Web.get('getCategoryTree')+ @Api.body(v.array(v.object(DtoCategoryTree)))+ async getCategoryTree(): Promise<DtoCategoryTree[]>{    const tree = await this.scope.model.category.select({      columns: ['id', 'name'],    });    return tree;  }}
复制代码


  • 行 3: 直接传入DtoCategoryTree

  • 行 4: 返回类型为Promise<DtoCategoryTree[]>

结语

Vonajs 已开源:https://github.com/vonajs/vona


Vonajs 作者正在 B 站直播撰写技术文档,工作日每晚 8:30,欢迎围观:濮水代码直播间

用户头像

关注

还未添加个人签名 2019-02-03 加入

还未添加个人简介

评论

发布
暂无评论
Prisma不能优雅的支持DTO,试试Vona ORM吧_node.js_风_InfoQ写作社区