在NestJS/TypeORM中检测重复值异常时code属性的TS类型
问题:NestJS + TypeORM + Postgres 唯一字段重复错误的TypeScript强类型处理
场景背景
我使用NestJS搭配TypeORM和Postgres数据库,已经为实体的username字段设置了唯一约束,实体代码如下:
@Entity() export class DataEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) username: string; // 其他属性... }
当前实现问题
向数据库添加数据时,需要在检测到重复username时返回错误提示。目前将catch中的ex设为any类型时代码可正常运行,但希望使用正确的TypeScript强类型。ex.code属性确实存在,但找不到包含该属性的对应类型。理想写法是if (ex instanceof WhateverError && ex.code === '23505'),但不清楚对应的错误类型。当前服务代码如下:
async createThing(data: dataDto): Promise<DataEntity> { try { // 数据库操作... } catch (ex) { console.log(ex); if (ex.code === '23505') { // 23505是Postgres检测到唯一约束冲突的错误码 this.logger.error(`Username '${username}' already exists!`); throw new ConflictException(`Username '${username}' already exists!`); } else { this.logger.error(`Failed to create data! Data: ${JSON.stringify(createOrgDto)}`); throw new InternalServerErrorException(); } } }
错误日志详情
控制台输出的错误信息(已修改部分敏感内容):
QueryFailedError: duplicate key value violates unique constraint "UQ_e288cba8a890c81abc9dbac6b33" at PostgresQueryRunner.query (C:\Users\chris\myproj\src\driver\postgres\PostgresQueryRunner.ts:299:19) at processTicksAndRejections (node:internal/process/task_queues:95:5) at InsertQueryBuilder.execute (C:\Users\chris\myproj\src\query-builder\InsertQueryBuilder.ts:163:33) at SubjectExecutor.executeInsertOperations (C:\Users\chris\myproj\src\persistence\SubjectExecutor.ts:434:42) at SubjectExecutor.execute (C:\Users\chris\myproj\src\persistence\SubjectExecutor.ts:137:9) at EntityPersistExecutor.execute (C:\Users\chris\myproj\src\persistence\EntityPersistExecutor.ts:182:21) at DataService.createData (C:\Users\chris\myproj\src\data\data.service.ts:43:7) { query: 'INSERT INTO "Data"("id", "username", "password", "name", "logoUrl", "type", "superAdmin") VALUES (DEFAULT, $1, $2, $3, DEFAULT, DEFAULT, DEFAULT) RETURNING "id", "logoUrl", "type", "superAdmin"', parameters: [ 'foo', '<REDACTED_PASSWORD>', 'FooCorp' ], driverError: error: duplicate key value violates unique constraint "UQ_e288cba8a890c81abc9dbac6b33" at C:\Users\chris\myproj\node_modules\pg\lib\client.js:526:17 at processTicksAndRejections (node:internal/process/task_queues:95:5) at PostgresQueryRunner.query (C:\Users\chris\myproj\src\driver\postgres\PostgresQueryRunner.ts:249:25) at InsertQueryBuilder.execute (C:\Users\chris\myproj\src\query-builder\InsertQueryBuilder.ts:163:33) at SubjectExecutor.executeInsertOperations (C:\Users\chris\myproj\src\persistence\SubjectExecutor.ts:434:42) at SubjectExecutor.execute (C:\Users\chris\myproj\src\persistence\SubjectExecutor.ts:137:9) at EntityPersistExecutor.execute (C:\Users\chris\myproj\src\persistence\EntityPersistExecutor.ts:182:21) at DataService.createData (C:\Users\chris\myproj\src\data\data.service.ts:43:7) { length: 249, severity: 'ERROR', code: '23505', detail: 'Key (username)=(foo) already exists.', hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: undefined, schema: 'public', table: 'Data', column: undefined, dataType: undefined, constraint: 'UQ_e288cba8a890c81abc9dbac6b33', file: 'nbtinsert.c', line: '666', routine: '_bt_check_unique' }, length: 249, severity: 'ERROR', code: '23505', detail: 'Key (username)=(foo) already exists.', hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: undefined, schema: 'public', table: 'Data', column: undefined, dataType: undefined, constraint: 'UQ_e288cba8a890c81abc9dbac6b33', file: 'nbtinsert.c', line: '666', routine: '_bt_check_unique' }
疑问
我已经尝试使用QueryFailedError类型,但它并没有code属性。难道只能强制使用any类型吗?
解决方案
1. 自定义类型守卫实现强类型判断
TypeORM的QueryFailedError本身未暴露code属性,但它包含的driverError字段是Postgres驱动抛出的错误对象,自带code属性。可以通过自定义类型守卫来实现强类型校验:
import { QueryFailedError } from 'typeorm'; import { PostgresError } from 'pg'; // 判断是否为Postgres唯一约束冲突错误 function isUniqueConstraintError(error: unknown): error is QueryFailedError & { driverError: PostgresError } { return ( error instanceof QueryFailedError && typeof (error as any).driverError === 'object' && (error as any).driverError.code === '23505' ); }
在catch块中使用该守卫:
async createThing(data: dataDto): Promise<DataEntity> { try { // 执行数据库操作 return await this.dataRepository.save(data); } catch (ex) { if (isUniqueConstraintError(ex)) { this.logger.error(`Username '${data.username}' already exists!`); throw new ConflictException(`Username '${data.username}' already exists!`); } else { this.logger.error(`Failed to create data! Data: ${JSON.stringify(data)}`); throw new InternalServerErrorException(); } } }
2. 扩展TypeORM的QueryFailedError类型声明
如果不想写类型守卫,可以通过扩展模块类型的方式,给QueryFailedError添加code属性的类型定义。在项目中新建typeorm.d.ts文件:
import { QueryFailedError } from 'typeorm'; declare module 'typeorm' { interface QueryFailedError { code: string; driverError: { code: string; detail: string; // 可按需添加其他需要的属性 }; } }
添加后TypeScript会自动识别QueryFailedError上的code属性,无需额外类型转换。
3. 直接结合pg库的错误类型判断
Postgres驱动的错误类型在pg包的PostgresError中,可以直接结合它进行判断:
import { QueryFailedError } from 'typeorm'; import { PostgresError } from 'pg'; // 在catch块中 async createThing(data: dataDto): Promise<DataEntity> { try { // 数据库操作 } catch (ex) { if (ex instanceof QueryFailedError && (ex.driverError as PostgresError).code === '23505') { this.logger.error(`Username '${data.username}' already exists!`); throw new ConflictException(`Username '${data.username}' already exists!`); } else { // 其他错误处理 throw new InternalServerErrorException(); } } }
内容的提问来源于stack exchange,提问作者Chris Barr
相关产品推荐
相关产品推荐

