惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
U
Unit 42
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
博客园 - 司徒正美
Vercel News
Vercel News
I
InfoQ
GbyAI
GbyAI
C
Check Point Blog
B
Blog RSS Feed
Martin Fowler
Martin Fowler
B
Blog
MyScale Blog
MyScale Blog
腾讯CDC
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 三生石上(FineUI控件)

Node.js

有深度使用 bunjs 的佬吗, rust 重写的 1.4 版本内存泄露解决了吗? - V2EX 告别 Django Admin!这个 NodeJS 全栈框架让你在 DTO 中直接配置 Table/Form 渲染 Bun 用 Rust 重写的 PR 已合并到主分支 TanStack 最新版本被投毒,病毒会盗密钥和报复性删除用户目录 Bun 从 Zig 到 Rust 的迁移已经实锤,下个版本可能成为最后一个 Zig 版本 bun 要从 zig 迁移 rust 了, 100 万刀被 claude 收购了,开始影响语言决策了 JS/Node 已经是新时代的 Java 了吧 Node.js 服务大部分时候连阿里云 Redis 是正常的,但是又时候会突然报 read ECONNRESET? 2026 年, node 写后端你用的 nestjs, fastify, honojs 还是其他? npmmirror 竟然有些包没有更新到最新版本 大家 typescript 下用的最多的是后端框架是哪个? 求助,大家 node.js 是怎么代理的 ai 时代, node.js 成为核心语言 NestJS + Swagger UI:非 200 状态码 Execute 时返回值不显示问题 深入 alova3 服务端能力:分布式 BFF 层到 API 网关的最佳实践 Anthropic 收购 Bun 关于 Node.js 中的事件循环问题。 Hoa - 一个极简 Web 框架 有没有 v 友遇到 windows10 丢失系统环境变量的问题 Esbuild 进程占用高 有没有推荐的 Nodejs 的 sass 多租户系统 2025 年 node 项目,乱成一锅粥的 typescript ESM import 写法该怎么选? [EvanNav 6.3.1] 如何删除加载页面,让 Nav 更符合你的需求⁉️ 删除 node_modules 文件夹非常耗时 Node.JS 作者 Ryan Dahl 的故事 Vona ORM 文档终于肝完了,欢迎拍砖 Node.js 官网更新了 adonisjs 有没有现成的注册登录库? 现在流行的 Node.js 做后台比传统的 Java .Net 有哪些优势? 现在大家开发 api 都用什么 node 框架?有没有想 rails 一样,功能齐全的框架?
Prisma 不能优雅的支持 DTO,可以试试 Vona ORM
zhennann · 2025-08-05 · via Node.js

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

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

限于篇幅,这里不展开讲解 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:声明为可选字段

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的类型推断效果:

1.png

2.png

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 效果:

3.png

4.png

5.png

从示意图中,我们可以清晰的看到,这棵树引用的 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[]>