-
-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dependency injection with class-validator #528
Comments
What is the implementation of your |
This is only a code sample to illustrate my issue but the brandService is Nest component like
So my main goal is the use a @component inside a validator class |
I believe nest couldn't find the BrandValidator you are using.. Maybe do you need to add @component also to the BrandValidator to make it injectable and registered to the nest di? With typeDI module I guess this would work but in nest it can be another work around. |
@jmaicaaan As said in the initial request, i've also tried that ( to add the BrandValidator as a @component ), but its not working, i guess mainly because class-validator don't use Nest DI |
It's impossible to inject dependencies into |
So is there a better way of doing it? i have similar issue where i need to validate if the user email is existing in the database and i want to use the class-validator, is there any workaround? |
The only "workaround" that i have found is very dirty ATM : i've added typedi to my project and i manually register @components that will be use by class-validator
According to @kamilmysliwiec 's answer, don't think we have a better way to do that unfortunately |
@fmeynard i found a solution, i think its new feature of class-validator called "useContainer", main.ts
my constaint: entityExists.constraint.ts
My dto file:
And last step is to add the constraint to your desired module
pretty simple, 1 line makes it all work :) |
Great job @roeehershko! async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
useContainer(app, { fallback: true });
await app.listen(3000);
}
bootstrap(); 🔥 🔥 🔥 🔥 |
Hello Nestjs entrepreneurs, in core version : 5.0.0-beta.6 I implement
Not work and get this error I would like do dependency injection on a custom validator like this
|
@yamid-ny Could you share your repository? I'd love to reproduce this issue somewhere. |
Yes @kamilmysliwiec https://github.com/yamid-ny/dependency-injection-on-custom-validator thanks !! http://localhost:3000/auth/register // when register same email user
|
Hi guys, I found a solution for that, you can check my repo: https://github.com/neoteric-eu/nestjs-auth (btw, yesterday I was making a tech-workshops for developers in Gdańsk about how to proper handle authorisation and authentication with nest 😄 ), so, yeah @kamilmysliwiec I spread the love for Nest.js around the world 😸 @yamid-ny the solution for that in particular is done in two steps, first one: this.app = await NestFactory.create(AppModule, {
logger: new AppLogger('Nest')
});
useContainer(this.app, {fallbackOnErrors: true}); The Second thing: import {ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';
import {UserService} from './user.service';
import {Injectable} from '@nestjs/common';
@ValidatorConstraint({ name: 'isUserAlreadyExist', async: true })
@Injectable()
export class IsUserAlreadyExist implements ValidatorConstraintInterface {
constructor(protected readonly userService: UserService) {}
async validate(text: string) {
const user = await this.userService.findOne({
email: text
});
return !user;
}
}
Like so: import {Module} from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { UserController } from './user.controller';
import { userProviders } from './user.providers';
import { UserService } from './user.service';
import {IsUserAlreadyExist} from './user.validator';
@Module({
controllers: [UserController],
providers: [...userProviders, IsUserAlreadyExist, UserService],
imports: [DatabaseModule],
exports: [UserService]
})
export class UserModule {
} Then when I try to do {"name": "cojack", "email": "[email protected]", "password": "qwer1234", "roles": ["user"]} I have got following response: {"statusCode":422,"error":"Unprocessable Entity","message":[{"property":"email","children":[],"constraints":{"isUserAlreadyExist":"User already exists"}}]} Hell yeah! @fmeynard @jmaicaaan @hershko765 @roeehershko - you also guys might be interested how to solve this problem. Regards ;) |
and
behaves different.
while container tries to get Validator and MetadataStorage classes. In the second case there are no errors and everything works like a charm. Can you explain why? |
Hello Community !!! |
@kamilmysliwiec I am also interested in why Anyways, just wanted to tell you that this project is amazing! Keep up the great work! |
@ullalaaron I've explain it in my comment. |
@cojack where exactly? |
In version 5.1.0, everything works if i write In bootstrap function: const app = await NestFactory.create(ApplicationModule);
useContainer(app.select(ApplicationModule), { fallbackOnErrors: true });
await app.listen(config.api.port); Validator function: @ValidatorConstraint({ name: 'isUserAlreadyExist', async: true })
@Injectable()
export class IsUserAlreadyExist {
constructor(
@Inject('UserService') private readonly userService: UserService,
) {}
async validate(text: string) {
const user = await this.userService.findOneByEmail(text);
return !user;
}
defaultMessage(args: ValidationArguments) {
return 'User with this email already exists.';
}
} User entity: @Entity()
export default class User {
@PrimaryGeneratedColumn('uuid') id: string;
@IsNotEmpty()
@MaxLength(100)
@Validate(IsUserAlreadyExist)
@IsEmail()
@Column({
type: 'varchar',
length: 100,
primary: true,
})
email: string;
} |
I use the sample provided and works fine, when i change the service I have the following problem
|
@andreanta I have the same problem, did you find any solutions ? |
To answer to @andreanta, when using typeorm forFeature, the validation container is initialized before the nest container. The workaround i found, is to create a method on your implementation of the ValidatorConstraintInterface to set the service before the validation happens in the constructor of any module. @Module({
imports: [TypeOrmModule.forFeature([MyEntity])]
})
export class AppModule {
constructor(
private myRepository: Repository<MyEntity>
) {
const t = getFromContainer(MyConstraint);
t.setRepository(this.myRepository);
}
} @ValidatorConstraint({ name: 'MyConstraint' })
export class MyConstraint implements ValidatorConstraintInterface {
private myRepository: Repository<MyEntity>;
defaultMessage?(validationArguments?: ValidationArguments): string {
return 'Validation fail';
}
validate(value: any, args: ValidationArguments) {
// your validation code
// here you can now use this.myRepository.find(...)
}
setRepository(myRepository: Repository<MyEntity>) {
this.myRepository = myRepository;
}
} |
@Rmannn and @andreanta check my comment, it's already explained how it should be configured to use di from class-validator #528 (comment) |
@cojack Thank you, I saw your comment, but, as I said, when using typeorm forFeature() in a module, |
I also have the same problem when using |
Got the same problem when using TypeOrmModule.forFeature([Entity]) it shows "TypeError: Cannot read property 'findOneByEmail' of undefined" from calling injected UserService method |
@ullalaaron I was wrong, I didn't explain that:
And I'm also curious why it works like this, @kamilmysliwiec could you? @cdiaz and @Vergil0327 my solution is working for concept described here https://docs.nestjs.com/recipes/sql-typeorm I know that TypeOrmModule do a lot of out of the box, but I really like this approach, a bit more to write by hand, but is fine for me. |
bump |
Also I wonder if it possible to |
A proper Nest solution to this is surely needed. The Nest docs talk about and reference class-validator a lot. As a person learning the framework you assume that it's integrated, or able to be integrated, fully. In any case, here's a fairly simple workaround I'm using that works due to the fact that class-validator doesn't create constraints until they are needed... In main.ts export the application instance... export let app: INestApplication;
async function bootstrap() {
app = await NestFactory.create(AppModule);
await app.listen(3000);
} Then, manually inject what you need In your validation constraint... @ValidatorConstraint({ async: true })
class NotExistsConstraint implements ValidatorConstraintInterface {
private entityManager: EntityManager;
constructor() {
this.entityManager = app.get(EntityManager);
}
async validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> {
...
} I'm fairly new to Nestjs and I assume this configuration is safe to use, since it currently works. |
I was working on this issue, and figured out a workaround that works relatively well if you want to use request context stuff in custom validators for example. It requires creating an additional interceptor and pipe. It is a little hacky though, and haven't completely tested it, but it appears to work: context.interceptor.ts
and strip-context.pipe.ts
And an interface to use in validators:
And in the actual ValidatorConstraint class that can be used to create decorators you can reference those values using the validationArguments:
Hope this helps some of you who need access to request stuff in ValidationOptions, let me know if you have questions EDIT: I forgot to mention that you need to add the interceptor and pipe to your EDIT2: Also discovered that pipes are applied on controller parameter decorators as well, so I modified the strip-context pipe a bit to not break those |
@kamilmysliwiec as per @evilive3000 's comment
|
I've the same problem, i think we need a clean solution for that |
What about unit test? ` useContainer(app, { fallbackOnErrors: true }); No luck on this |
@cdiaz @Vergil0327 @matzmz, for now, I'm injecting the service with moduleRef:
If I try to inject the UserService directly a circular import error comes:
My UserService inject a repository with the InjectRepository decorator. Maybe this decorator has a check for circular dependencies? |
@javialon26 your solution isn't working for me, the @kamilmysliwiec any updates on this ? It seems the bug is related to the |
This is working for me, hope it can be of use for anyone. Basically I created a separate module for the validator constraints and imported the feature for using in the validator. main.ts - Application start point (calls import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import { useContainer } from "class-validator";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
forbidUnknownValues: true,
validationError: { target: false }
})
);
useContainer(app.select(AppModule), { fallbackOnErrors: true });
await app.listen(3000);
}
bootstrap(); validator.module.ts - Nest.js module for injecting import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Agencia } from "./cliente/agencia.entity";
import { AgenciaExists } from "./cliente/agencia-exists";
@Module({
imports: [TypeOrmModule.forFeature([Agencia])],
providers: [AgenciaExists]
})
export class ValidatorModule {} app.module.ts - Main module importing the import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { ValidatorModule } from "./validator.module";
import { ClienteModule } from "./cliente/cliente.module";
@Module({
imports: [TypeOrmModule.forRoot(), ValidatorModule, ClienteModule]
})
export class AppModule {} agencia-exists.ts - My custom validator that checks an import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments
} from "class-validator";
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { Agencia } from "./agencia.entity";
@ValidatorConstraint({ async: true })
@Injectable()
export class AgenciaExists implements ValidatorConstraintInterface {
constructor(
@InjectRepository(Agencia) private repository: Repository<Agencia>
) {}
async validate(id: number, args: ValidationArguments): Promise<boolean> {
const entity = await this.repository.findOne(id);
return entity !== undefined;
}
} cliente-input.ts - A DTO used for request body in import { Validate } from "class-validator";
import { AgenciaExists } from "./agencia-exists";
export class ClienteInput {
@Validate(AgenciaExists, { message: "Agência com id $value não existe." })
agencia: number;
} cliente-controller.ts - Controller of entity import { Controller, Post, Body } from "@nestjs/common";
import { ClienteInput } from "./cliente.input";
import { ClienteService } from "./cliente.service";
@Controller("clientes")
export class ClienteController {
constructor(private service: ClienteService) {}
@Post()
async create(@Body() input: ClienteInput): Promise<void> {
// Cast DTO to entity and save...
}
} |
Thanks @julianomqs, are you using the Although I get an Can you confirm you have the same behavior ? |
Hello @frco9 Sorry I forgot to put where the validator was used. I updated my previous comment. No, I'm not using the validator in an entity, but in a DTO used for input (request body) for POST and PATCH methods of my controller. The entity |
That's clearer to me many thanks @julianomqs ! |
@kamilmysliwiec this should be added to docs |
Just FYI, I'm not using TypeORM, instead I'm using mongoose, so, I have a service that connects to the DB schema, to achieve that in the ValidatorConstraint it's pretty the same way as the steps that @julianomqs posted before. 1. Set up service containermain.ts import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { ValidationPipe } from '@nestjs/common';
import { useContainer } from 'class-validator';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
app.useGlobalPipes(new ValidationPipe({
forbidUnknownValues: true,
validationError: {
target: false,
}
}));
useContainer(app.select(AppModule), {fallbackOnErrors: true});
const port = process.env.port || 3333;
await app.listen(port, () => {
console.log('Listening at http://localhost:' + port + '/' + globalPrefix);
});
} 2. Create my validatorunique.ts (custom validation) import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments
} from "class-validator";
import { UserService } from '@alp-api/db/services';
import { Injectable } from '@nestjs/common';
@ValidatorConstraint({ async: true })
@Injectable()
export class EmailIsUnique implements ValidatorConstraintInterface {
constructor(private userService: UserService){}
validate(email: any, args: ValidationArguments) {
return this.userService.findByEmail(email).then(user => !user);
}
} 3. Set up custom validation into db.module (validator module)Instead of creating a new module for custom validations, I used my already created db module to set my validation into the providers db.module.ts import { Module, Global } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
// Schemas
import { UserSchema } from './schemas';
import { UserService } from './services';
// DB Validators
import { EmailIsUnique } from './validators/unique';
@Global()
@Module({
imports: [
MongooseModule.forFeature([
{ name: 'User', schema: UserSchema }
])
],
providers: [
UserService,
EmailIsUnique
],
exports: [
MongooseModule,
UserService,
]
})
export class DbModule {} NoteSurely, my db.module.ts is imported into my app.module.ts |
A generic validator without a global pipe is-unique.validator.ts
validator.module.ts
user.entity.ts
Import ValidatorsModule in app.module.ts |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
I'm submitting a...
Current behavior
As mentioned in the documention NestJS is supposed to works well very with class validator, however i'm not successful trying to implement a custom validation class ( https://github.com/typestack/class-validator#custom-validation-classes ) or decorator.
This usecase is more than usefull to implement complex validation logic that require injecting services
As its a pretty standart usecase, i guess that there is a way to do it, but i'm not able to find it! could be a great addition to the online documentation imo !
Expected behavior
I've tried to add my Validator class as a component but not working.
In the class-validator documentation they mentioned that we can register a DI, is this doable with Nest ?
Minimal reproduction of the problem with instructions
Should lead to something like
[Nest] 1736 - 2018-3-26 12:16:38 [ExceptionsHandler] Cannot read property 'all' of undefined
TypeError: Cannot read property 'all' of undefined
The text was updated successfully, but these errors were encountered: