[ ] Regression
[ ] Bug report
[ ] Feature request
[x ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
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 !
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 ?
@Controller('cars')
export class CarController {
@Get()
public test(@Res() res) {
const dto = new CarInsertDTO();
dto.brand = 'toyota';
validate(dto).then(errs => {
if (errs && errs.length > 0) {
throw new BadRequestException(errs);
}
res.send(200);
});
}
}
export class CarInsertDTO {
@IsString()
public name: string;
@Validate(BrandValidator, { message: 'Invalid brand'})
public readonly strategy;
}
@ValidatorConstraint()
export class BrandValidator implements ValidatorConstraintInterface {
constructor(private readonly brandService: BrandService) {}
validate(brandName: any, args: ValidationArguments) {
// can be a http service , database service or whatever
const brands: string[] = this.brandService.all();
return brands.indexOf(brandName) >= 0;
}
}
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
What is the implementation of your brandService
@fmeynard ? How is it imported?
@jmaicaaan
This is only a code sample to illustrate my issue but the brandService is Nest component like
@Component
export class BrandService {
public all() {
return ['toyota','ford']; // in a real world this can a remote call or a database call
}
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 @ValidatorConstraint
with Nest.
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?
@hershko765
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
import { Container } from 'typedi';
// ... others imports
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, validationError: { target: false } }));
const dataSourceModule = app.select(DataSourceModule);
Container.set(DataSourceManager, dataSourceModule.get(DataSourceManager));
Container.set(DataSourceService, dataSourceModule.get(DataSourceService));
await app.listen(config.get('app.port'));
}
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",
once you do it, class-validator will use nestJS di
main.ts
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
import {useContainer} from 'class-validator';
import {ValidationPipe} from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
const validationPipe = app
.select(ApplicationModule)
.get(ValidationPipe);
// This will cause class-validator to use the nestJS module resolution,
// the fallback option is to spare our selfs from importing all the class-validator modules to nestJS
useContainer(app.select(ApplicationModule), { fallback: true });
app.useGlobalPipes(validationPipe);
await app.listen(3000);
}
bootstrap();
my constaint: entityExists.constraint.ts
@ValidatorConstraint({ name: 'entityExists', async: false })
@Component()
export class EntityExists implements ValidatorConstraintInterface {
constructor(
@Inject('DbConnectionToken') private readonly connection: Connection,
) {}
async validate(text: string, validationArguments: ValidationArguments) {
const entity = await this.connection.getRepository(validationArguments.constraints[0]).findOneById(text);
return !! entity;
}
defaultMessage(args: ValidationArguments) {
console.log(args);
return 'Entity not exists';
}
}
My dto file:
...
export class CreateCustomerDto {
@Validate(EntityExists, [Customer])
customer_id: number;
....
}
And last step is to add the constraint to your desired module
@Module({
imports: [],
controllers: [
...
],
components: [
Validator,
ValidationPipe,
EntityExists,
...
],
})
export class ApplicationModule implements NestModule {
}
pretty simple, 1 line makes it all work :)
you can create your own "ValidationModule" add there all the constraints and stuff, i am just a random lazy guy
Great job @roeehershko!
Also, in v5.0.0 you should be able to use the following syntax:
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
import { useContainer } from 'class-validator';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
useContainer(app, { fallback: true });
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Not work and get this error
[Nest] 9880 - 2018-6-4 20:39:27 [ExceptionHandler] Nest cannot find given element (it does not exist in current context)
I would like do dependency injection on a custom validator like this
@ValidatorConstraint({ name: 'isUserAlreadyExist', async: false })
export class IsUserAlreadyExist implements ValidatorConstraintInterface {
constructor(private readonly usersService: UsersService) {}
async validate(text: string) {
const user = await this.usersService.findOne({
email: text,
});
return user ? true : false;
}
}
@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
{
"email": "[email protected]",
"password": "123456"
}
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 :smile: ), so, yeah @kamilmysliwiec I spread the love for Nest.js around the world :smile_cat:
@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 {fallbackOnErrors: true}
is required, because Nest throw Exception when DI doesn't have required class.
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;
}
}
IsUserAlreadyExist
have to be Injetable and registered in _nest_ module
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 POST /users
twice with payload as follow:
{"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 ;)
@kamilmysliwiec
useContainer(app, { fallbackOnErrors: true });
and
```
useContainer(app.select(AppModule), { fallbackOnErrors: true });
behaves different.
In first case I've got few errors spitted into the console like this
[Nest] 3442 - 2018-6-12 16:43:51 [ExceptionHandler] Nest cannot find given element (it does not exist in current context)
```
while container tries to get Validator and MetadataStorage classes.
But finally code works correctly, and I got all injected services as expected;
In the second case there are no errors and everything works like a charm.
Can you explain why?
Hello Community !!!
How can I validate (all / multiple) Schema fields required ?
For example, UserSchema has email, nickname; PostSchema has title ( ... 3 or more fields, 3 or more dependency injections ... )
Currenly I use IsUserAlreadyExist from user.validator, this only validate one field in one schema (email).
In a real project We need some like "@Unique" in class-validator ModelDto.
You know a solution ?
@kamilmysliwiec I am also interested in why useContainer(app.select(AppModule), { fallbackOnErrors: true });
works perfectly, while useContainer(app, { fallbackOnErrors: true });
throws
[Nest] 3442 - 2018-6-12 16:43:51 [ExceptionHandler] Nest cannot find given element (it does not exist in current context)
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
with @InjectRepository the validation
I have the following problem
"Nest can't resolve dependencies of the AnagraficaService (?). Please make sure that the argument at index [0] is available in the AnagraficaService context."
If i remove the Validate all is ok
@ApiModelProperty()
@IsNotEmpty()
@IsEmail()
/*@ValidateIf(o => o.id === undefined || o.id <= 0)
@Validate(IsEmailAlreadyExist, {
message: "Email gi脿 utilizzata"
})*/
@Column({ length: 50 })
Email: string;
@Injectable()
export class AnagraficaService extends BaseService<Anagrafica> {
constructor(
@InjectRepository(Anagrafica)
private readonly todoRepository: Repository<Anagrafica>,
) {
super(todoRepository);
}
}
@Module({
imports: [
TypeOrmModule.forFeature([Anagrafica]),
],
controllers: [
... ],
providers: [
AnagraficaService,
IsEmailAlreadyExist,
IsAnagraficaAlreadyExist,
]
})
export class AnagraficaModule {}
@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.
We can't inject the repository because he is not yet in the 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.
To get the constraint, we can use the getFromContainer function from the 'class-validator' module.
Then we can inject manually any service implementing a setter.
@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 https://github.com/nestjs/nest/issues/528#issuecomment-395338798
@cojack Thank you, I saw your comment, but, as I said, when using typeorm forFeature() in a module,
the validation container is initialized before the nest container and the service is injected before to be created. So the class-validator will fail to find the service to inject.
The useContainer is call to late, (only in our case) because my model requires the decorator that requires the constraint that requires the repository that is not ready...
Model -> validation decorator -> constraint -> repo.
A repository need the model to be initialized.
The pb occurs only because we are using a Repository i guess. it should works as you said if we use any others services out of the scope of dependencies of typeorm.
How is this issue closed? Are there any updates on this? As @Rmannn explained, @cojack's solution doesn't work when using TypeOrmModule.forFeature([Entity])
I also have the same problem when using forFeature()
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:
@kamilmysliwiec I am also interested in why useContainer(app.select(AppModule), { fallbackOnErrors: true }); works perfectly, while useContainer(app, { fallbackOnErrors: true }); throws
[Nest] 3442 - 2018-6-12 16:43:51 [ExceptionHandler] Nest cannot find given element (it does not exist in current context)
Anyways, just wanted to tell you that this project is amazing! Keep up the great work!
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 @Inject(REQUEST)
in custom constraint.
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.
Also I wonder if it possible to
@Inject(REQUEST)
in custom constraint.
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
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
/**
* Injects customerId and userId into the context, so that the ValidationPipe can use it.
*/
@Injectable()
export class ContextInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<any> {
const request = context.switchToHttp().getRequest()
if (request.body ) {
request.body.context = {
customerId: request.customer && request.customer.id,
userId: request.user && request.user.id,
}
}
return next.handle()
}
}
and strip-context.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
/**
* Strips the context that is injected to be used by the validation pipe
*/
@Injectable()
export class StripContextPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if ( value.context) {
const { context, ...rest } = value
return rest
}
return value
}
}
And an interface to use in validators:
import { ValidationArguments } from 'class-validator'
export interface ContextValidationArguments extends ValidationArguments {
object: {
context: {
customerId: number
userId: number
}
} & Object
}
And in the actual ValidatorConstraint class that can be used to create decorators you can reference those values using the validationArguments:
@ValidatorConstraint()
@Injectable()
export class VariableNotExistsConstraint implements ValidatorConstraintInterface {
defaultMessage(validationArguments?: ValidationArguments): string {
return ""
}
async validate(value: any, validationArguments?: ContextValidationArguments): Promise<boolean> {
const customerId = validationArguments.object.context.customerId
const userId = validationArguments.object.context.userId
return true
}
}
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 main.ts
file if you want them on every request (StripContextPipe after the ValidationPipe)
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
useContainer(app, { fallbackOnErrors: true });
and useContainer(app.select(AppModule), { fallbackOnErrors: true });
behave differently.
class-validator
tries to get the instance of given class using findInstanceByPrototypeOrToken
method of NestApplicationContext
. Possible cause of failure when using NestApplication
instance instead of NestApplicationContext
is the lack of tokenization that happens when NestApplicationContext
's this.moduleTokenFactory.create(module, scope);
is run.
Got the same problem when using TypeOrmModule.forFeature([Entity])
it shows "TypeError: Cannot read property 'findOneByEmail' of undefined" from calling injected UserService method
I've the same problem, i think we need a clean solution for that
What about unit test?
Would like to test ContraintClass with dependencies.
Is there any temporary solution to get/simulate iocContainer from TestingModule instance?
`
const module: TestingModule = await Test.createTestingModule(...);
const app: INestApplication = module.createNestApplication();
useContainer(app, { fallbackOnErrors: true });
`
No luck on this
@cdiaz @Vergil0327 @matzmz, for now, I'm injecting the service with moduleRef:
import { ModuleRef } from '@nestjs/core';
import { Injectable } from '@nestjs/common';
import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { UserService } from '../user.service';
@ValidatorConstraint({ name: 'isUserAlreadyExist', async: true })
@Injectable()
export class IsUserAlreadyExist implements ValidatorConstraintInterface {
private userService: UserService;
constructor(private readonly moduleRef: ModuleRef) {}
async validate(username: string) {
const user = await this.userService.findByUsername(username);
return !user;
}
defaultMessage() {
return 'user with this username already exists.';
}
onModuleInit() {
this.userService = this.moduleRef.get('UserService');
}
}
If I try to inject the UserService directly a circular import error comes:
Error: A circular dependency has been detected inside @InjectRepository(). Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Also, try to eliminate barrel files because they can lead to an unexpected behavior too.
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 userService
is undefined
when the validate
method is called. Have you done something else ?
@kamilmysliwiec any updates on this ? It seems the bug is related to the TypeOrmModule
from nestjs/typeorm as @cojack made this work without the nestjs/typeOrm Module.
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 useContainer
of class-validator
).
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 class-validator
validators that need dependency injection and imports a TypeOrm repository
for use inside the validator.
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 ValidatorModule
.
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 Agencia
entity exists in the database.
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 POST
and PATCH
methods in cliente.controller.ts
.
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 Cliente
. The entity Agencia
is a required related entity (foreign key).
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 @Validate
on the Cliente
entity ? It's working fine for me in that case.
Although I get an A circular dependency has been detected inside @InjectRepository().
if I try to @Validate
a property on the Agencia
entity itself (for example a slug field is unique)
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 Agencia
is a required foreign key of the entity Cliente
.
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.
main.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);
});
}
unique.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);
}
}
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 {}
Surely, my db.module.ts is imported into my app.module.ts
A generic validator without a global pipe
is-unique.validator.ts
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
ValidationOptions,
registerDecorator
} from "class-validator";
import { getManager } from "typeorm";
@ValidatorConstraint({ async: true })
export class isUniqueValidator implements ValidatorConstraintInterface {
validate(columnNameValue: any, args: ValidationArguments) {
const params = args.constraints[0];
return getManager()
.query(
`SELECT * FROM ${params.table} WHERE ${params.column} = '${columnNameValue}'`
)
.then(user => {
if (user[0]) return false;
return true;
});
}
}
export function IsUnique(params: {}, validationOptions?: ValidationOptions) {
return function(object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [params],
validator: isUniqueValidator
});
};
}
validator.module.ts
import { isUniqueValidator } from "./is-unique.validator";
import { Module } from "@nestjs/common";
@Module({
providers: [isUniqueValidator]
})
export class ValidatorsModule {}
user.entity.ts
import { IsUnique } from "./../validators/is-unique.validator";
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { IsOptional, IsString, IsNotEmpty, IsEmail } from "class-validator";
import { CrudValidationGroups } from "@nestjsx/crud";
const { CREATE, UPDATE } = CrudValidationGroups;
@Entity("users")
export class UserEntity {
@PrimaryGeneratedColumn()
id: string;
@IsOptional({ groups: [UPDATE] })
@IsNotEmpty({ groups: [CREATE] })
@IsEmail({}, { always: true })
@IsUnique(
{ table: "users", column: "email" },
{ message: "Email $value already exists", groups: [CREATE] }
)
@Column({ unique: true })
email: string;
@IsOptional({ groups: [UPDATE] })
@IsNotEmpty({ groups: [CREATE] })
@IsString({ always: true })
@Column({ nullable: true })
name: string;
@Column()
password: string;
}
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.
Most helpful comment
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 :smile: ), so, yeah @kamilmysliwiec I spread the love for Nest.js around the world :smile_cat:
@yamid-ny the solution for that in particular is done in two steps, first one:
The
{fallbackOnErrors: true}
is required, because Nest throw Exception when DI doesn't have required class.Second thing:
IsUserAlreadyExist
have to be Injetable and registered in _nest_ moduleLike so:
Then when I try to do
POST /users
twice with payload as follow:I have got following response:
Hell yeah!
@fmeynard @jmaicaaan @hershko765 @roeehershko - you also guys might be interested how to solve this problem.
Regards ;)