Zod + Nestjs (вместо сlass-validator)
-
Очень удобно внедрять проверку данных запроса декларативно, в контроллерах. Так и учит нас официальная документация nestjs, и для этого нам необходима библиотека class-validator
Но многим не нравится обмазываться декораторами, особенно когда их много решение кажется очень сомнительным:
class CreateCompanyDto implements Dto { @IsString({message: 'Must be text format'}) @MinLength(2, { message: "Must have at least 2 characters" }) @MaxLength(20, { message: "Can't be longer than 20 characters" }) @IsDefined({ message: 'Must specify a receiver' }) public name!: string; @MaxLength(253, { message: "Can't be longer than 253 characters" }) @IsFQDN({}, {message: 'Must be a valid domain name'}) @IsDefined({ message: 'Must specify a domain' }) public domain!: string; @MaxLength(30, { message: "Can't be longer than 30 characters" }) @IsString({message: 'Must be text format'}) @IsDefined({ message: 'Must specify a company size' }) public size!: string; @IsPhoneNumber(null, {message: 'Must be a valid phone number'}) @IsDefined({ message: 'Must specify a phone number' }) public contact!: string; }
Zod - это удобная альтернатива библиотеке проверки классов для проверки тела запроса.
Пример кода
Итак поехали…
Создаем схему
import { z } from "zod"; export const createProductShema = z.object({ title: z.string(), text: z.string(), active: z.boolean(), }); type CreateProductDtoType = z.infer<typeof createProductShema>; // или вместо типа можем использовать интерфейс export interface CreateProductDto extends CreateProductDtoType {}
z.infer() позволит нам получить тип для данных, которые будем валидировать
далее создаем Pipe
@Injectable() export class ZodPipe implements PipeTransform { constructor(private readonly schema: any) {} transform(value: any, metadata: ArgumentMetadata) { this.schema.parse(value); return value; } }
и обмазываем наш контролер ранее созданным пайпом
@Controller() export class AppController { constructor(private readonly productUseCaseService: ProductUseCaseService) {} @Post() createProduct(@Body(new ZodPipe(schema)) body: CreateProductDto) { return this.productUseCaseService.create(body); } }
Ну и для того, чтобы мы отлавливали ошибки валидации, и формировали ответ запроса как нам нужно
напишем фильтр и подключим его@Catch(ZodError) export class ZodFilter<T extends ZodError> implements ExceptionFilter { catch(exception: T, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = 400; response.status(status).json({ errors: exception.errors, message: exception.message, statusCode: status, }); } }
async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new ZodFilter()); await app.listen(3000); }
Такая схема кажется гибче, и вообще zod закрывает проблемные места typescript связанные с отсутствием проверки типов в runtime
-
жаль сейчас нет библиотеки поддерживаемой с zod для Nest,
естьnestjs-zod
, но автор на нее забил -
@Manul поэтому и приходится велосипедить)