Search code examples
typescriptnestjsmonorepoclass-validator

How to use DTOs classes from another package for validation in NestJS?


I'm trying to use NestJS's ValidationPipe to validate DTOs that are passed to my controllers. In my DTOs I use TypeScript decorators inside ES 6 classes, as recommended by the official docs.

While this works well when the DTOs live in the same NPM package as the rest of the Nest application, I run into trouble in my setup:

I have a NPM workspace monorepo with a package that is shared between the frontend and backend packages. The DTOs live in this shared package. That way I have a single source of truth for my DTOs and I can use them in the NestJS server and the frontend code without duplicating anything.

The problem is that the TypeScript decorators are lost when I build the shared repository. Neither the generated .js nor the .d.ts file contain the decorators anymore.

Example:

dto.ts in @myapp/shared:

export class ListDTO {
  @IsString()
  name!: string;
}

TypeScript transpiles it to this:

dto.js:

export class ListDTO {
}

dto.d.ts:

export declare class ListDTO {
    name: string;
}

If I use ListDTO in my NestJS app, I get this warning when enabling debugging in ValidationPipe and the validation always succeeds, even for invalid data:

No validation metadata found. No validation will be  performed. There are multiple possible reasons:
        - There may be multiple class-validator versions installed. You will need to flatten your dependencies to fix the issue.
        - This validation runs before any file with validation decorator was parsed by NodeJS.

I think the reason is that the decorators are lost in the transpilation step of the shared package. Is there any way to work around this and keep the single source of truth in the shared package?


Solution

  • After watching a video on how how NestJS uses reflection and metadata, I figured out that the TypeScript compiler includes metadata for reflection on decorators in a special block at the beginning of the JS file. NestJS and class-validator use this metadata when reflecting over a class. It's not included by default.

    The solution thus is to include this metadata in the transpilation step of the shared package. You need to change tsconfig.json this way:

    {
        "compilerOptions": {
            "emitDecoratorMetadata": true
        }
    }