Routing-controllers: Routing-Controllers + class-validator bug?

Created on 17 Mar 2018  路  39Comments  路  Source: typestack/routing-controllers

From 11000100111000 in typestack/class-validator#128

When using the automatic validation from routing-controllers for some reason the email "patch@patch" is valid when we are using these validators and the "patch" validator group

@IsEmail({
        allow_display_name: false,
        allow_utf8_local_part: true,
        require_tld: true
    }, {
        groups: ["post", "patch", "login"]
    })
    @IsOptional({
        groups: ["patch"]
    })
    public email;

Doing it the manual way like so works however

let user = new UserValidation();
user.email = "patch@patch";
validate(user, {groups: ["patch"]}).then(console.log);
fix

Most helpful comment

I think I might be seeing similar issue too. Tried running a version of the auto validation based on the example in the README:

@JsonController()
class UserController {
    @Post('/login')
    login(@Body({validate: true}) user: User) {
        console.log('this is a valid body');
        return 'success!!!';
    }
}

class User {
    @IsEmail()
    email: string;

    @MinLength(6)
    password: string;
}

createExpressServer({
    controllers: [UserController]
}).listen(3000);

No matter what I send in the POST request I get a success back. Not sure if I've missed something in the above code but for the life of me I can't get it to validate and throw an error.

Validating manually through the class-validator does work though.

All 39 comments

Having same issue. Validation isn't running implicitly in the controller.

I think I might be seeing similar issue too. Tried running a version of the auto validation based on the example in the README:

@JsonController()
class UserController {
    @Post('/login')
    login(@Body({validate: true}) user: User) {
        console.log('this is a valid body');
        return 'success!!!';
    }
}

class User {
    @IsEmail()
    email: string;

    @MinLength(6)
    password: string;
}

createExpressServer({
    controllers: [UserController]
}).listen(3000);

No matter what I send in the POST request I get a success back. Not sure if I've missed something in the above code but for the life of me I can't get it to validate and throw an error.

Validating manually through the class-validator does work though.

The problem might not be in routing-controllers directly but with transformation of plain object to class instance using class-transformer.

Exactly. I have similar issue. Parameters of my actions are custom classes (not interfaces) annotated with attributes like @MinLength ... The problem is that parameters that I received are pure literal objects and not instances of my classes. So problem seems to be class-transformer.

I force options (that normally default to true) during creation of express server :

createExpressServer({
  controllers: [ MyController ],
  classTransformer: true,
  validation: true
});

Same for me.
So I have typeorm model


Group.ts

import { IsNotEmpty, IsUppercase, MaxLength } from 'class-validator';
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { SubGroup } from './SubGroup';

@Entity({
    name: 'pss_groups',
})
export class Group {
    @PrimaryGeneratedColumn()
    public id: number;

    @Column({
        length: 1,
        name: 'name',
    })
    @Index({ unique: true })
    @IsNotEmpty()
    @MaxLength(1)
    @IsUppercase()
    public name: string;

    @Column({
        name: 'description',
    })
    public description: string;

    @OneToMany(type => SubGroup, subGroup => subGroup.group)
    public subGroups: SubGroup[];
}

And here's my controller


GroupController.ts

import { Body, HttpCode, JsonController, Post } from 'routing-controllers';
import { Service } from 'typedi';
import { Group } from '../models/Group';
import { GroupService } from '../services/GroupService';

@JsonController('/groups')
export class GroupController {
    constructor(@Service() private groupService: GroupService) {
    }

    @HttpCode(201)
    @Post('')
    public async createGroup(@Body({ required: true, validate: true }) group: Group): Promise<Group> {
        return this.groupService.createGroup(group);
    }
}

In result action is not validating data at all, even if I include validate function and use it on group value, the result will be an empty array.

My app initialization


app.ts

const expressApp = express();

expressApp.use(json({ limit: '50mb' }));
expressApp.use(urlencoded({ extended: true }));

useExpressServer(expressApp, {
    cors: true,
    routePrefix: env.app.routePrefix,
    defaultErrorHandler: false,
    classTransformer: true,
    validation: true,
    controllers: env.app.dirs.controllers,
    middlewares: env.app.dirs.middlewares,
});

Any thoughts?

Any News about this issue?
I noticed that if using @Controller works, if using @JsonController not works.

The issue is caused because the version of class-validator that you have installed does not match the version of class-validator that routing-controllers is using. (see issue #424 and typestack/class-validator#166)

For a temporary fix you can run the following commands:

npm uninstall routing-controllers class-validator
npm install --save [email protected] [email protected]

Edit: This will not work if you are using class-transformer. If you are using class-transformer read below for a solution

I've tried your solution, still same issue, @Body is not validated.

@Andrew5569 Nope, doesn't work...

I created an example: https://github.com/Andrew5569/rc-body-validation

This is working for me

Yup, that works @Andrew5569 but I have basically the exact same setup but with controller and the model in a separate file and it just doesn't want to work.

@Andrew5569
Temporary fix works.
Cheers!

So I have done few test and this is what I found:

library | t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 | t9 | t10 |
-----------|------|------|------|------|-----|------|----|----|---|----|
RC version | 0.7.6 | 0.7.6 | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 |
CV version | 0.7.3 | 0.8.0-4 | 0.7.3 | 0.8.x | 0.9.1 | 0.9.1 | 0.8.1 | not in package.json | 0.8.5 | 0.9.0 |
CT version | 0.1.9 | 0.1.9 | 0.1.9 | 0.1.9 | not in package.json | 0.1.9 | 0.1.9 | not in package.json | 0.1.9 | 0.1.9 |
Working ? | :white_check_mark: | :x:
normal as RC requires CV ^0.7.0| :x:
normal as RC requires CV ^0.8.1| :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :white_check_mark: | :x: |

So it seems to be a mix of class-validation / routing-controller changes breaking the validation, as the newer version of class-transformer works with routing-controller 0.7.6 and class-validator 0.7.3

@pleerock @NoNameProvided would you have any chance looking at this issue as it makes the stack (routing controller, class validator, class transfomers) non useable with latest version?

Thanks again 馃憤

@nolazybits I don't think you will have a fix soon, looking at latest commit date. I forked it and just updated package.json versions to latest and it seems to work. This is the modified version you can use from npm until official release: https://www.npmjs.com/package/@mardari/routing-controllers

thanks @MardariG I will give it a go 馃憤
@pleerock if you need help to maintain those projects I am happy to help, at least to review the existing PR etc, and try to merge and publish.

@MardariG Im sorry but your changes doesnt work on my project.

Sample:

import {  Body, Post, Controller } from '@mardari/routing-controllers';
import { MinLength, IsDefined, IsNotEmpty } from '../../../node_modules/class-validator';

export class User {
    @IsNotEmpty({message: 'is empty'})
    @IsDefined({message: 'is not defined'})
    @MinLength(5, { message: 'FIRST NAME '})
    public firstName: string;
    public lastName: string;

    public getName(): string {
        return this.lastName + ' ' + this.firstName;
    }
}

// tslint:disable-next-line:max-classes-per-file
@Controller('/user')
export class UserController {

    @Post()
    public post(@Body({required: true, validate: true}) user: User): number {
        console.log('saving user ' + user.getName());

        return 1;
    }

}

accepts

{
    "firstName": "kond",
    "lastName": "dysput"
}

@konraddysput
Not sure what can be the cause as what I did was just updating package.json from original repo to latest versions.
So I have a sample just like yours, and it works:

import {IsEmail, IsNotEmpty, MinLength} from 'class-validator';

export class Auth {
    @IsNotEmpty()
    @IsEmail()
    public email: string;
    @IsNotEmpty()
    @MinLength(8)
    public password: string;
}
import {Body, JsonController, Post} from '@mardari/routing-controllers';
import {Auth} from './requests/Auth';
import {AuthService} from '../../auth/AuthService';
import {AuthResponse} from './responses/AuthResponse';

@JsonController('/auth')
export class AuthController {

    constructor(
        private authService: AuthService
    ) {
    }

    @Post()
    public authorize(@Body() auth: Auth): Promise<AuthResponse> {
        return this.authService.login(auth);
    }

}



md5-f1d279fc8d71dec5a901c7da6f435474



{
"name":"BadRequestError",
"message":"Invalid body, check 'errors' property for more info.",
"errors":[
{"target":{"email":"[email protected]","password":"admin"},
"value":"admin",
"property":"password",
"children":[],
"constraints":{"minLength":"password must be longer than or equal to 8 characters"}}
]
}

note: all classes are separate files, not sure if this can be problems;

I think the problem isn't in class-validation or routing-controllers. Can you provide your package.json? Maybe you use diffrent version of class-transform?

"dependencies": {
    "@mardari/routing-controllers": "^0.7.9",
    "@types/bluebird": "^3.5.18",
    "@types/body-parser": "^1.17.0",
    "@types/chalk": "^2.2.0",
    "@types/cors": "^2.8.1",
    "@types/dotenv": "^4.0.3",
    "@types/express": "^4.16.0",
    "@types/faker": "^4.1.2",
    "@types/helmet": "^0.0.39",
    "@types/lodash": "^4.14.116",
    "@types/morgan": "^1.7.35",
    "@types/multer": "^1.3.7",
    "@types/reflect-metadata": "0.1.0",
    "@types/request": "^2.47.1",
    "@types/serve-favicon": "^2.2.30",
    "@types/uuid": "^3.4.3",
    "@types/winston": "^2.3.7",
    "adm-zip": "^0.4.11",
    "bcrypt": "^3.0.0",
    "body-parser": "^1.18.3",
    "chalk": "^2.4.1",
    "class-validator": "0.9.1",
    "commander": "^2.17.1",
    "compression": "^1.7.3",
    "copyfiles": "^2.0.0",
    "cors": "^2.8.4",
    "dataloader": "^1.4.0",
    "dotenv": "^6.0.0",
    "ejs": "^2.6.1",
    "event-dispatch": "^0.4.1",
    "express": "^4.16.3",
    "express-basic-auth": "^1.1.5",
    "express-graphql": "^0.6.12",
    "express-status-monitor": "^1.1.5",
    "faker": "^4.1.0",
    "figlet": "^1.2.0",
    "glob": "^7.1.3",
    "graphql": "^0.13.2",
    "helmet": "^3.13.0",
    "html-pdf": "^2.2.0",
    "jsonfile": "^4.0.0",
    "jsonwebtoken": "^8.3.0",
    "lodash": "^4.17.10",
    "microframework-w3tec": "^0.6.3",
    "moment": "^2.22.2",
    "morgan": "^1.9.0",
    "multer": "^1.3.1",
    "nodemon": "^1.18.3",
    "npm": "^6.4.0",
    "nps": "^5.9.3",
    "nps-utils": "^1.7.0",
    "path": "^0.12.7",
    "pg": "^7.4.3",
    "reflect-metadata": "^0.1.12",
    "request": "^2.88.0",
    "serve-favicon": "^2.5.0",
    "swagger-ui-express": "^4.0.0",
    "ts-node": "^6.0.5",
    "tslint": "^5.11.0",
    "typedi": "^0.8.0",
    "typeorm": "^0.2.7",
    "typeorm-typedi-extensions": "^0.2.1",
    "typescript": "3.0.1",
    "uuid": "^3.3.2",
    "winston": "^2.4.3",
    "xml2js": "^0.4.19"
  },
  "devDependencies": {
    "@types/ejs": "^2.6.0",
    "@types/nock": "^9.3.0",
    "@types/node": "^10.9.2",
    "cross-env": "^5.2.0",
    "nock": "^9.6.1",
    "shelljs": "^0.8.2"
  }

Ok. Current version working well with:

    "class-transformer": "~0.1.6",
    "class-validator": "^0.7.0",

Validation start working on my side and working really well. Key part is to use class-validator from version 0.7.0 - 0.7.3.

Following example showing how my express changes works:
https://github.com/pleerock/routing-controllers-express-demo

import {ActionParameterHandler} from "routing-controllers/ActionParameterHandler";
import {validateOrReject as validate, ValidationError} from "class-validator";
import {BadRequestError} from "routing-controllers";

/**
 * issue https://github.com/typestack/routing-controllers/issues/384
 * a copy of https://github.com/typestack/routing-controllers/blob/4a56d176db77bc081dfcd3d8550e8433b5bc476e/src/ActionParameterHandler.ts#L179-L199
 * @param value
 * @param paramMetadata
 */
ActionParameterHandler.prototype["validateValue"] = function (this: any, value: any, paramMetadata: any) {
    const isValidationEnabled = (paramMetadata.validate instanceof Object || paramMetadata.validate === true)
        || (this.driver.enableValidation === true && paramMetadata.validate !== false);
    const shouldValidate = paramMetadata.targetType
        && (paramMetadata.targetType !== Object)
        && (value instanceof paramMetadata.targetType);

    if (isValidationEnabled && shouldValidate) {
        const options = paramMetadata.validate instanceof Object ? paramMetadata.validate : this.driver.validationOptions;
        return validate(value, options)
            .then(() => value)
            .catch((validationErrors: ValidationError[]) => {
                const error: any = new BadRequestError(`Invalid ${paramMetadata.type}, check 'errors' property for more info.`);
                error.errors = validationErrors;
                error.paramName = paramMetadata.name;
                throw error;
            });
    }

    return value;
};

Temporarily solved this problem. I don't know why it works.

What i did to fix it was to look in node_modules\routing-controllers\package.json, and copy the versions for class-validator and class-transformer (if you use that) over to my own package.json. And then download the dependencies again

where exactly?
I'm using class-transformer and it doesn't work.
what is the solution for that?

Thanks in advance :)

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

I found that it is sufficient to declare the dependency to class-validator to the same version as in routing-controllers: "class-validator": "^0.8.1",. I had to remove my node_modules folder and the lock file and run npm install again.
After that here was no additional class-validator package in the node_modules folder for routing-controllers. That way the app and routing-controllers were using the exactly same version from the root nod_modules folder. No error anymore.

From 11000100111000 in typestack/class-validator#128

When using the automatic validation from routing-controllers for some reason the email "patch@patch" is valid when we are using these validators and the "patch" validator group

@IsEmail({
        allow_display_name: false,
        allow_utf8_local_part: true,
        require_tld: true
    }, {
        groups: ["post", "patch", "login"]
    })
    @IsOptional({
        groups: ["patch"]
    })
    public email;

Doing it the manual way like so works however

let user = new UserValidation();
user.email = "patch@patch";
validate(user, {groups: ["patch"]}).then(console.log);

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

Worked for me tks !!

I have similar issue and found that it could be caused by usage of useContainer for class-validator. Related issue is here: https://github.com/typestack/class-validator/issues/328.

I think the reason for the bug is that class-validator maintains an internal registry of the contracts that you decorate. If you install class-validator as a package dependency, you'll likely get the newer one where your contracts will be registered; but when routing-controllers runs it uses its internal class-validator (v0.8.5) that would have an empty registry and hence not validate anything.

A proposed solution to routing-controllers:
add class-validator as an optional-peer-dependency and force consumers that want to validate to add it themselves.

A proposed solution to people who declare contracts in their api project:
don't add class-validator to your project. By default if you add routing-controllers you'll also get class-validator automatically, so just use taht

A proposed solution to people who keep contracts in a separate package that gets installed to their api:
add class-validator as a dev-dependency in your contracts package. add routing-controllers to your api

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

Anyone have better solution than this ? so far this is the only working solution for me.

Dependencies are

    "body-parser": "^1.19.0",
    "class-transformer": "^0.2.3",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-winston": "^3.1.0",
    "express-ws": "^4.0.0",
    "install": "^0.12.2",
    "jwt-simple": "^0.5.5",
    "mongoose": "^5.4.8",
    "mongoose-autopopulate": "^0.9.1",
    "multer": "^1.4.1",
    "reflect-metadata": "^0.1.13",
    "routing-controllers": "^0.7.7",

"class-validator": "^0.9.1", uninstalled package

Any better solution? Only import class-validators from routing controllers works

I've published @gritcode/routing-controllers that includes this fix (ie: class-validator as a peer dependency).

To use it: npm i @gritcode/routing-controllers class-validator

I've sent a PR back to this abandoned lib, but doubt it'll ever be merged

@adenhertog thank you very much for your solution. Maybe you could create a smaller PR with just that change and it would be merged. I'll be happy to poke them :)

I've published @gritcode/routing-controllers that includes this fix (ie: class-validator as a peer dependency).

To use it: npm i @gritcode/routing-controllers class-validator

I've sent a PR back to this abandoned lib, but doubt it'll ever be merged

@adenhertog I've merged your changes in the next branch and will soon package and publish to NPM as a release candidate for testing (0.8.1-alpha.1)

that's great @jotamorais, thanks for reviving this lib!

that's great @jotamorais, thanks for reviving this lib!

Published to NPM, the pre-release including your changes (tagged as 0.8.1-alpha.2 off of next branch).

Please, update your package.json and try it out before I merge into master and publish v0.8.1 to NPM.

I will close this issue as we made class-transformer and class-validator peer dependencies in the last release (8.0.0) and it seems stable so far.
I've recently released 0.8.1-alpha.2 and it also fixes a related issue to include the current request context when resolving controller from IoC (ref #497)

Hi there!
Thanks for the fix, but I'm confused.
I have your last version on my project: 0.8.1 and I'm trying to validate a Generic class as @Body request, on a @JsonController defined POST controller for entity Employee.

controller method

@Post('/')
public async post(
    @Body({ required: true, validate: true })
    body: EntityPostRequest<Employee | Employee[]>,
)

EntityPostRequest definition

export class EntityPostRequest<T extends BaseCoreEntity | BaseCoreEntity[]> {
  @ValidateNested()
  data: T;
}

BaseCoreEntity

export class BaseCoreEntity extends BaseEntity {
  public id: number;
}

Employee entity

@Entity()
export class Employee extends CoreEntity {
  public static QUERY_MAPPINGS_TYPES = ['id', 'departmentId', 'age'];
  @IsOptional()
  @IsPositive()
  @PrimaryGeneratedColumn('increment')
  public id: number;
  @MaxLength(30)
  @Column({ type: 'varchar', length: 30, nullable: false })
  public name: string;
  @MaxLength(30)
  @Column({ type: 'varchar', length: 30, nullable: false })
  public lastname: string;
  @IsPositive()
  @Max(120)
  @Column('int')
  public age?: number;
  @ManyToOne(
    () => Department,
    (department) => department.employees,
  )
  @JoinColumn()
  public department: Department;
  @IsPositive()
  @Column()
  @RelationId((employee: Employee) => employee.department)
  public departmentId: number;
  @IsOptional()
  @CreateDateColumn()
  public created?: Date;

  constructor(id?, name?, lastname?, age?, created?, department?: Department) {
    super();
    this.id = id;
    this.name = name;
    this.lastname = lastname;
    this.age = age;
    this.created = created;
    this.department = department;
  }
}

I don't know what the hell I'm doing worong, but for example, I can't even get 'age' key of Employee to get validates. I'm inserting ages of values higher than 120 and It doesn't thhrow an error, actually it inserts the entity into the database. So no validation exists.

Can anyone guide me?

Hi @juanitopons,

if I'm right, the class-validator doesn't work with generics or unions, because it isn't possible to detect their types during the gathering metadata process of decorators.

Therefore I would suggest you to simply split your code into two endpoints for a single record and for an array of records.

if you want to have a single endpoint, then you need to change body structure to be for example:

{
  @ValidateNested()
  item?: Employee,

  @ValidateNested()
  items?: Array<Employee>
}

then it should work as you want.

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

impzero picture impzero  路  4Comments

szabobar picture szabobar  路  4Comments

AleskiWeb picture AleskiWeb  路  4Comments

posabsolute picture posabsolute  路  4Comments

dcbartlett picture dcbartlett  路  3Comments