如何以DRY方式将Kysely与NestJS管道验证集成
维持DDL唯一数据源的NestJS DTO验证方案
针对你用Kysely+NestJS,以DDL为唯一数据源但无法直接用kysely-codegen生成带class-validator注解类的问题,以下几个DRY方案可以解决:
方案1:自定义kysely-codegen模板生成带验证注解的类
kysely-codegen支持通过--template参数指定自定义EJS模板,你可以修改默认模板,让它直接生成带有class-validator注解的类(而非接口),模板会根据DDL里的字段约束(非空、数据类型、长度等)自动映射对应的验证装饰器。
步骤:
- 复制kysely-codegen的默认模板(可从其源码中找到
default-template.ejs) - 修改模板结构,将接口定义改为类定义,并添加验证注解逻辑
- 运行kysely-codegen时指定自定义模板:
kysely-codegen --template ./path/to/your-custom-template.ejs
示例模板片段:
import { IsString, IsInt, IsNotEmpty, IsOptional } from 'class-validator'; export class <%= entity.name %>Dto { <% entity.columns.forEach(column => { %> <% if (column.notNull) { %>@IsNotEmpty()<% } else { %>@IsOptional()<% } %> <% if (column.dataType === 'varchar') { %>@IsString()<% } %> <% if (column.dataType === 'int') { %>@IsInt()<% } %> <%= column.name %>: <%= column.tsType %>; <% }) %> }
每次更新DDL后重新生成,类会自动带上对应验证注解,完全基于DDL约束,无需手动同步。
方案2:用ts-morph二次生成验证类
如果不想修改kysely-codegen的模板,可写一个Node.js脚本,用ts-morph解析kysely-codegen生成的接口文件,自动生成带class-validator注解的DTO类。
步骤:
- 安装依赖:
npm install ts-morph --save-dev - 编写生成脚本(比如
generate-dtos.ts):import { Project, InterfaceDeclaration } from 'ts-morph'; const project = new Project(); // 读取kysely生成的接口文件 const sourceFile = project.addSourceFileAtPath('./src/db/types.ts'); // 遍历所有接口生成对应DTO sourceFile.getInterfaces().forEach(iface => { const dtoFile = project.createSourceFile(`./src/dtos/${iface.getName()}Dto.ts`, '', { overwrite: true }); // 导入class-validator装饰器 dtoFile.addImportDeclaration({ namedImports: ['IsString', 'IsInt', 'IsNotEmpty', 'IsOptional'], moduleSpecifier: 'class-validator', }); // 创建DTO类 const dtoClass = dtoFile.addClass({ name: `${iface.getName()}Dto` }); // 给类属性添加验证注解 iface.getProperties().forEach(prop => { const decorators = []; // 处理非空/可选约束 decorators.push(prop.isOptional() ? '@IsOptional()' : '@IsNotEmpty()'); // 处理数据类型 const propType = prop.getType().getText(); if (propType === 'string') decorators.push('@IsString()'); if (propType === 'number') decorators.push('@IsInt()'); dtoClass.addProperty({ name: prop.getName(), type: propType, decorators: decorators.map(d => ({ name: d })), }); }); }); project.save(); - 将脚本加入npm脚本,和kysely-codegen联动:
"scripts": { "db:generate": "kysely-codegen && ts-node ./generate-dtos.ts" }
每次更新DDL后运行npm run db:generate,就能自动生成接口和对应的验证DTO类,全程无需手动维护。
方案3:基于Kysely元数据的动态验证(运行时)
如果不想生成任何类,可利用Kysely的数据库元数据,在NestJS自定义管道中实现动态验证,直接基于DDL约束校验请求体。
示例自定义管道:
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; import { db } from './db/connection'; // 你的Kysely实例 @Injectable() export class DbSchemaValidationPipe implements PipeTransform { constructor(private readonly tableName: string) {} async transform(value: any) { const tableSchema = await db.introspection.getTable(this.tableName); if (!tableSchema) throw new BadRequestException(`表 ${this.tableName} 不存在`); const errors: string[] = []; for (const column of tableSchema.columns) { const fieldValue = value[column.name]; // 校验非空约束 if (column.notNull && (fieldValue === undefined || fieldValue === null)) { errors.push(`${column.name} 为必填项`); } // 校验数据类型 if (fieldValue !== undefined) { if (column.dataType.includes('varchar') && typeof fieldValue !== 'string') { errors.push(`${column.name} 必须为字符串类型`); } else if (column.dataType.includes('int') && !Number.isInteger(fieldValue)) { errors.push(`${column.name} 必须为整数`); } } } if (errors.length) throw new BadRequestException(errors); return value; } }
控制器中使用:
@Post() async create(@Body(new DbSchemaValidationPipe('users')) user: Users) { // 业务逻辑处理 }
这个方案完全依赖DDL元数据,无需生成DTO,但验证逻辑需要自行扩展,且属于运行时验证,性能略低于编译时生成的类。
内容的提问来源于stack exchange,提问作者Marcus Junius Brutus
相关产品推荐
相关产品推荐

