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

推荐订阅源

The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
S
Schneier on Security
Forbes - Security
Forbes - Security
Cisco Talos Blog
Cisco Talos Blog
月光博客
月光博客
T
Threat Research - Cisco Blogs
I
InfoQ
量子位
NISL@THU
NISL@THU
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
P
Privacy & Cybersecurity Law Blog
The Register - Security
The Register - Security
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
AWS News Blog
AWS News Blog
T
Troy Hunt's Blog
M
MIT News - Artificial intelligence
B
Blog
T
Tor Project blog
有赞技术团队
有赞技术团队
Hacker News: Ask HN
Hacker News: Ask HN
Y
Y Combinator Blog
L
LangChain Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LINUX DO - 热门话题
Schneier on Security
Schneier on Security
Cloudbric
Cloudbric
H
Hacker News: Front Page
C
CERT Recently Published Vulnerability Notes
Google DeepMind News
Google DeepMind News
V
V2EX
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
宝玉的分享
宝玉的分享
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
Recorded Future
Recorded Future
Simon Willison's Weblog
Simon Willison's Weblog
J
Java Code Geeks
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
美团技术团队

Node.js

告别 Django Admin!这个 NodeJS 全栈框架让你在 DTO 中直接配置 Table/Form 渲染 Bun 用 Rust 重写的 PR 已合并到主分支 TanStack 最新版本被投毒,病毒会盗密钥和报复性删除用户目录 Bun 从 Zig 到 Rust 的迁移已经实锤,下个版本可能成为最后一个 Zig 版本 bun 要从 zig 迁移 rust 了, 100 万刀被 claude 收购了,开始影响语言决策了 - V2EX JS/Node 已经是新时代的 Java 了吧 Node.js 服务大部分时候连阿里云 Redis 是正常的,但是又时候会突然报 read ECONNRESET? - V2EX 2026 年, node 写后端你用的 nestjs, fastify, honojs 还是其他? npmmirror 竟然有些包没有更新到最新版本 大家 typescript 下用的最多的是后端框架是哪个? 求助,大家 node.js 是怎么代理的 ai 时代, node.js 成为核心语言 NestJS + Swagger UI:非 200 状态码 Execute 时返回值不显示问题 深入 alova3 服务端能力:分布式 BFF 层到 API 网关的最佳实践 - V2EX Anthropic 收购 Bun 关于 Node.js 中的事件循环问题。 - V2EX 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 studio 更好性能的类 studio 数据管理 node 怎么获取剪切板中复制的文件信息 写了个 js 小工具库, 希望大家给点建议 NodeJS 阿里云服务器,经常遇到整个服务器卡死,系统盘读操作被占满 不用继承就能给类加事件系统?这波原型链操作我给满分 做了一个函数式、带类型、超顺手的微型事件库,已发布到 npm 告别 Node.js 工具链地狱: Bun 如何让 MCP Server 开发效率翻 3 倍 - V2EX redis 集群模式支持批量操作库 mget/mset 😂😂浪费了 3 天时间尝试在新项目中使用 prisma,最后还是决定回到 typeorm - V2EX 吐槽一下 bun node.js 有什么比较好用的微信 sdk - V2EX nodejs 后端,怎么比较好的生成接口文档?(排除 nest.js) - V2EX Cherry Studio 只用 bun.exe 怎么绕过去? 把 node.js 程序打包成可执行文件,不过只有 10M - V2EX 前端包管理工具调研 - V2EX 请问下 JavaScript 的 CustomEvent 和自己手搓的发布订阅哪个更快效率更好? 同样是运行 npm run build 打包程序 cursor 比 hbx 慢 1000 倍 从零开始开发一个 MCP Server! 现在还有人 eggjs 吗? - V2EX TypeScript7.0 用 go 重写, 10 倍快,看了两遍确定是真的... - V2EX 做了几个扩展,顺便整理了一下开源了一个浏览器扩展开发模版 prisma 运行 migrate 命令之后,总会把其他不相关的表给删除 问个页面跳转访问方案 求助! v 友们求推荐靠谱的 node.js 学习及面试网站和视频 大家正使用哪个 node 版本,会保持用最新版吗? 请教大家一个在 hono.js 中使用 ts 的类型兼容性问题
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

现在我们自动推断 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[]>