我正在使用 Nest.js 和 MySQL 构建一个 API。出于敏捷和 DRY 原则,我正在创建一个面向对象的结构,为给定的实体(来自 TypeORM)设置所有基本的 CRUD 端点。主要目标是避免为不同的实体编写相同的常见方法。
为了实现这一目标,我使用了 TypeScript Generics 策略。我仍然需要为每个实体创建所有的通用文件(.controller.ts、.service.ts、.module.ts、.entity.ts),但我不需要编写其方法。相反,我只需要扩展两个类:RestController 和 RestService。这些类已经实现了共同的方法,但我必须传递一些 T 类型作为参数,以便 TypeORM 可以将正确的存储库注入到 Service 中。
问题在于:当我在父类 (RestController) 中使用 @UsePipes 装饰器时,它没有被调用,但是当我在子类中 (SubcategoriesController) 重写 RestController 的 create 方法时,它可以正常工作。
rest.controller.ts:
您可以看到父类接受3种类型的参数:
- T:实体 - C:CreateDTO,可选 - U:UpdateDTO,可选
上面的代码完美地创建了端点,但是在create中它没有验证payload,这与ValidationPipe的预期不同。 如果我覆盖SubcategoriesController中的create方法并在其中添加UsePipes,它会起作用!
我认为这可能是关于Nest生命周期的错误,可能不支持在抽象类中使用管道。
有人有什么想法吗? P.S. 没有编译错误、警告或运行时异常。
为了实现这一目标,我使用了 TypeScript Generics 策略。我仍然需要为每个实体创建所有的通用文件(.controller.ts、.service.ts、.module.ts、.entity.ts),但我不需要编写其方法。相反,我只需要扩展两个类:RestController 和 RestService。这些类已经实现了共同的方法,但我必须传递一些 T 类型作为参数,以便 TypeORM 可以将正确的存储库注入到 Service 中。
问题在于:当我在父类 (RestController) 中使用 @UsePipes 装饰器时,它没有被调用,但是当我在子类中 (SubcategoriesController) 重写 RestController 的 create 方法时,它可以正常工作。
rest.controller.ts:
import { Get, Post, Body, Param, Put, Delete, UsePipes, ValidationPipe } from '@nestjs/common';
import { RestService } from './rest.service';
import { ObjectLiteral } from 'typeorm';
export abstract class RestController<T, C = T, U = T> {
constructor(protected service: RestService<T, C, U>) {}
@Get()
async index(): Promise<T[]> {
return this.service.getAll();
}
@Post('create')
@UsePipes(ValidationPipe) //HERE!
async create(@Body() data: C): Promise<T> {
return this.service.create(data as C);
}
}
rest.service.ts:
import { Repository, UpdateResult, DeleteResult, Entity, DeepPartial } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
export interface RestClass<T, C = T, U = T> {
// Properties
repository: Repository<T>;
// Default Methods
getAll(): Promise<T[]>;
create(model: T | C | U): Promise<T>;
}
export class RestService<T, C = T, U = T> implements RestClass<T, C, U> {
constructor(
public repository: Repository<T>,
) {}
getAll = async () => {
return await this.repository.find({relations:: this.repository.metadata.ownRelations.map(r => r.propertyName)});
}
create = async (model: C) => {
return await this.repository.save(model as C);
}
}
这里是我如何设置真实实体端点,扩展上述类:
subcategories.controller.ts:
import { Controller, Get, Post, UsePipes, ValidationPipe, Body } from '@nestjs/common';
import { SubcategoriesService } from './subcategories.service';
import { Subcategory } from './subcategory.entity';
import { RestController } from '../rest.controller';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';
//NOTE THE TYPE PARAMS IN <>
@Controller('subcategories')
export class SubcategoriesController extends RestController<Subcategory, CreateSubcategoryDTO> {
constructor(public service: SubcategoriesService) {
super(service);
}
}
subcategories.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Subcategory } from './subcategory.entity';
import { Repository } from 'typeorm';
import { RestService } from '../rest.service';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';
//NOTE THE TYPE PARAMS IN <>
@Injectable()
export class SubcategoriesService extends RestService<Subcategory, CreateSubcategoryDTO> {
constructor(
@InjectRepository(Subcategory) repository: Repository<Subcategory>,
) {
super(repository);
}
}
createSubcategory.dto.ts
import { IsString, Length, IsInt } from 'class-validator';
export class CreateSubcategoryDTO {
@IsString()
@Length(5, 60)
name: string;
@IsString()
@Length(0, 140)
summary: string;
@Length(0, 140)
icon: string;
@IsInt()
category: number;
}
您可以看到父类接受3种类型的参数:
- T:实体 - C:CreateDTO,可选 - U:UpdateDTO,可选
上面的代码完美地创建了端点,但是在create中它没有验证payload,这与ValidationPipe的预期不同。 如果我覆盖SubcategoriesController中的create方法并在其中添加UsePipes,它会起作用!
我认为这可能是关于Nest生命周期的错误,可能不支持在抽象类中使用管道。
有人有什么想法吗? P.S. 没有编译错误、警告或运行时异常。