Can I use class-validator to validate Entity columns?
This doesn't validate the columns:
import { IsEmail } from 'class-validator';
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
#IsEmail()
email: string;
}
However when I use the class-validator anywhere else in the code other than entities it validates properly and doesn't allow for bad inputs.
This works:
#InputType()
export class RegisterInput {
#Field()
#IsEmail()
email: string;
}
The Entity should be clean
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
email: string;
}
While you define a DTO for checking the incoming request.
export class AdminDto {
#IsEmail()
email: string;
}
In your controller, you would check the incoming request with your AdminDto.
#Controlller('route')
export class AdminController {
constructor(private yourService: yourserviceClass)
#Post('/api')
async createSomething(#Body('email', ValidationPipe) email: string){ //Your request body //would be checked against your DTO
this.yourService.createMethod(email)
}
Hope this answers your question.
Related
I'm working on a nestjs project with TypeORM. I'm setting up a bidirectional ManyToMany relationship between two entities: Notes and Gcalevents. Every Note could be linked to multiple Gcalevents, and every Gcalevent could be linked to many Notes.
Snippets from my entity definitions look like this:
base.entity.ts:
export class BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ type: 'boolean', default: true })
isActive: boolean;
... // some more fields
}
note.entity.ts:
import { Gcalevent } from './gcalevent.entity';
import { BaseEntity } from './base.entity';
#Entity({ name: 'note' })
export class Note extends BaseEntity {
#Column({ type: 'varchar', length: 300 })
title: string;
...
#ManyToMany(() => Gcalevent, (gcalevent) => gcalevent.notes)
#JoinTable()
gcalevents: Gcalevent[]
}
gcalevent.entity.ts:
import { Note } from "./note.entity";
#Entity({ name: 'gcalevent' })
export class Gcalevent {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
type: 'varchar',
nullable: false,
})
eventId: string;
...
#ManyToMany(() => Note, (note) => note.gcalevents)
notes: Note[]
}
I believe I'm declaring the ManyToMany relationships correctly, but when I try to generate the TypeORM migration, I get the error TypeError: Class extends value undefined is not a constructor or null, which points to both the note.entity and gcalevent.entity files
I'm guessing this has something to do with the gcalevent.entity and note.entity files importing each other, combined with the note.entity file importing BaseEntity, but not sure how to fix it!
I had the same problem, I've found here
this "workaround" for the issue.
Try using "strings" instead of "types" in the #ManyToMany decorator:
note.entity.ts:
import { Gcalevent } from './gcalevent.entity';
import { BaseEntity } from './base.entity';
#Entity({ name: 'note' })
export class Note extends BaseEntity {
#Column({ type: 'varchar', length: 300 })
title: string;
...
#ManyToMany("Gcalevent","notes")
#JoinTable()
gcalevents: Gcalevent[]
}
gcalevent.entity.ts:
import { Note } from "./note.entity";
#Entity({ name: 'gcalevent' })
export class Gcalevent {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
type: 'varchar',
nullable: false,
})
eventId: string;
...
#ManyToMany("Note","gcalevents")
notes: Note[]
}
I have tried adding toJSON(){return classtoPlain(this)} in my entity still its not working.
Here is my controller
#Controller('users')
#UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
#HttpCode(200)
#Post()
async create(#Body() user: User): Promise<User> {
return await this.usersService.create(user).catch((err) => {
throw new HttpException({ message: err.message }, HttpStatus.BAD_REQUEST);
});
}
Here is my Entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Gender } from '../Constants/enum';
import { Exclude, instanceToPlain } from 'class-transformer';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
#Column()
#Exclude({ toPlainOnly: true })
password: string;
constructor(partial: Partial<User>) {
Object.assign(this, partial);
}
toJSON() {
return instanceToPlain(this);
}
}
Here is my service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private readonly user_detailsRepository: Repository<User>,
) {}
create(user: User) {
return this.user_detailsRepository.save(user);
}
I have tried many solutions but still nothing works.
For GET its working but for post its showing the password field.If any one can provide any solution it would be great.
You're mixing between Entity definition/validation, which typeORM functions/decorators (in your case) should handle. And DTO definition, which class-transformer/validator should handle
So you should define a createUserDTO.ts file like below, and use it as in the controller/service file:
export class createUserDTO {
#IsNotEmpty()
#IsString()
firstName: string;
#IsNotEmpty()
#IsString()
lastName?: string;
#IsNotEmpty()
#IsEnum(Gender)
gender: Gender;
#IsNotEmpty()
email: string;
#Exclude({ toPlainOnly: true })
password: string;
}
The entity file shouldn't have the password column (just omit the field completely), should look like this:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
}
Then, when you call return this.user_detailsRepository.save(userDto);, it should work fine
Consider a base entity as below:
export abstract class Notification {
#PrimaryGeneratedColumn()
id: number;
#Column({type: "date",nullable: false})
seenAt: Date;
#Column({ type: "integer", nullable: false })
priority: number;
}
and two child entities as below:
#Entity()
export class NotificationType1 extends Notification {}
and
#Entity()
export class NotificationType2 extends Notification {}
Is there a way to find all rows in NotificationType1 and NotificationType2 using a query to the parent class like this?
SELECT * FROM NOTIFICATION;
This query return 0 rows, although there are records in NotificationType1 and NotificationType2 tables.
You should be able to Select from the superclass and retrieve all the records with something like this:
import {getConnection} from "typeorm";
const user = await getConnection().createQueryBuilder()
.select("notification")
.from(Notification, "notification");
You also need to change your abstract class to #TableInheritance to leverage Single Table Inheritance.
This Code:
export abstract class Notification {
#PrimaryGeneratedColumn()
id: number;
#Column({type: "date",nullable: false})
seenAt: Date;
#Column({ type: "integer", nullable: false })
priority: number;
}
Would become:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
export class Notification {
#PrimaryGeneratedColumn()
id: number;
#Column({type: "date",nullable: false})
seenAt: Date;
#Column({ type: "integer", nullable: false })
priority: number;
}
And the Child Entity:
#ChildEntity()
export class NotificationType1 extends Notification {}
The docs have on single table inheritance.
When user registers, I want to him to send his account name, so I will have "Accounts" table with reference to the user entity. I'm using Nest.js.
I'm looking for alternative to the following logic in my users.service.ts Register method:
Find Account by Name
If Account not found, Create It
Create User with the Account found above
Here is my Account Entity:
#Entity()
export class Account extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
name: string;
#Column()
#CreateDateColumn()
createdAt: Date;
}
And my User Entity:
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#ManyToOne(() => Account, (Account) => Account.name, { cascade:true })
#JoinColumn({name: 'name'})
account: Account;
}
My CreateUserDTO:
export class CreateUserDto {
#IsEmail()
email: string;
#IsNotEmpty()
password: string;
#IsNotEmpty()
account: string;
}
And this is the error when I try to do User.create(dto):
Type 'string' is not assignable to type 'Account | DeepPartial<Account>'.
In addition, for some reason, the User.create(dto) returns array of users and not single user, and I don't understand why.
You can proceed like this :
// create-user.dto.ts
export class CreateUserDto {
#IsEmail()
email: string;
#IsString()
#IsNotEmpty()
password: string;
#IsString()
#IsNotEmpty()
accountName: string;
}
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#Column()
name: string; // This is your relations between user and account defined in JoinColumn decorator
#ManyToOne(() => Account, (Account) => Account.name, { cascade: true })
#JoinColumn({ name: 'name' })
account: Account;
}
// user.service.ts
// user.service.ts
#Injectable()
export class UserService {
constructor(
#InjectRepository(Account)
private readonly accountRepository: Repository<Account>, // Or your custom repository
#InjectRepository(User)
private readonly userRepository: Repository<User> // Or your custom repository
) {}
public async register(dto: CreateUserDto): Promise<void> {
let accountToSaveWithUser: Account;
// First check if account exist
const account = await this.accountRepository.findOne({
where: {
name: dto.accountName,
},
});
if (isNil(account)) {
const accountToSave = this.accountRepository.create({
name: dto.accountName,
});
accountToSaveWithUser = await this.accountRepository.save(accountToSave);
} else {
accountToSaveWithUser = account;
}
await this.userRepository.save({
email: dto.email,
password: hash(dto.password), // Use your package for hash password
name: accountToSaveWithUser.name,
});
}
}
I have anum like this:
export enum UserRole {
USER,
ADMIN,
BLOGGER
}
and create.user.dto like this
import { IsEmail, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { UserRole } from './user.entity';
export class CreateUserDto {
#IsEmail()
email: string;
#IsNotEmpty()
firstName: string;
#IsNotEmpty()
lastName: string;
#IsOptional()
username: string;
#IsOptional()
#IsEnum(UserRole)
role: UserRole;
#IsNotEmpty()
password: string;
}
Now role validation does not fail if I only post the role uppercase('ADMIN','USER') or 'BLOGGER'.
How to make class-validator not case sensitive? I mean, validate true also for 'admin' 'aDmIn'.
then you need a regexp validation via #Matches.
#IsOptional()
#Matches(`^${Object.values(UserRole).filter(v => typeof v !== "number").join('|')}$`, 'i')
role: UserRole;
the final rule is /^USER|ADMIN|BLOGGER$/i, where i ignores the case.
your enum should like this
export enum UserRole {
USER = "USER",
ADMIN = "ADMIN",
BLOGGER = "BLOGGER"
}
is proven works in case-insensitive or vice versa