I have tried using Class Transformer, but it doesn't make any sense since Prisma doesn't need Entity, and Prisma Type cannot be Exclude()
Is there any way to exclude key from Prisma object e.g. createdAt or password? Thank you
I did it this way
In file: user.entity.ts
import { Role, User as UserPrisma } from '#prisma/client';
import { Exclude } from 'class-transformer';
export class User implements UserPrisma {
id: string;
name: string;
email: string;
#Exclude()
password: string;
#Exclude()
role: Role;
createdAt: Date;
updatedAt: Date;
}
There are few options
select specific fields (not ideal, lots of duplication and leaves room for error)
add user.password = undefined before returning (not very clean, also room for error)
create entity mirroring prisma schema and use class-transformer (imo this misses the point of using prisma in general. In this situation you might as well use TypeORM or Sequelize to avoid duplication and multiple sources of truth)
create custom interceptor (imo the best solution atm)
Interceptor example:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '#nestjs/common';
import { map, Observable } from 'rxjs';
#Injectable()
export class RemovePasswordInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((value) => {
value.password = undefined;
return value;
}),
);
}
}
This issue is being discussed atm, so I suggest looking into this thread
Related
I have a DTO to validate data coming from the POST request to create my Entity.
I want to have the "same" DTO to update my Entity during PATCH request, but some fields need to be Optional.
I tried to use Partial, but in that Way I miss some important control.
I can't use #ValidateIf because I don't have a property to detect if is it a POST or PATCH.
I can't use Extends, because I need to override every properties with #IsOptional
Is it possible to use "createDTO" to and update the entity, without duplicate the it and rename to "updateDTO" only for add #IsOptional property?
I think you can accomplish this in two ways:
Implement a custom interceptor/decorator that inject the http method in the body with a special key, then use ValidateIf with that injected property, like.
#Injectable()
export class HttpMethodInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<any> {
const req = context.switchToHttp().getRequest();
req.body.httpMethod = req.method;
return next.handle();
}
}
export class TestDto {
#IsNotEmpty()
#IsString()
public property: string;
#IsNotEmpty()
#IsString()
#ValidateIf(o => o.httpMethod === 'POST')
public otherProperty: string;
#IsString()
public httpMethod: string;
}
Use class validators groups passing the method as a value for each function, and pass the expected value on the decorators, something like:
#Post()
#UsePipes(new ValidationPipe({ groups: ['post'] }))
public create(#Body() body: TestDto): Promise<any> {
return;
}
#Patch()
#UsePipes(new ValidationPipe({ groups: ['patch'] }))
public update(#Body() body: TestDto): Promise<any> {
return;
}
export class TestDto {
#IsNumberString(null, { groups: ['post'] }) //This validation will only apply if group has post in it, so on patch it will do nothing
public test: string;
}
I have a situation where my client user can enter zero or multiple addresses. My problem is that if he enters an address, some fields need to be mandatory.
user.controller.ts
#Post()
#UsePipes(ValidationPipe)
async createUser(
#Body() createUser: CreateUserDto,
) {
return await this.service.saveUserAndAddress(createUser);
}
create-user.dto.ts
export class CreateUserDto {
#IsNotEmpty({ message: 'ERROR_REQUIRED_FULL_NAME' })
fullName?: string;
#IsNotEmpty({ message: 'ERROR_REQUIRED_PASSWORD' })
password?: string;
#IsNotEmpty({ message: 'ERROR_REQUIRED_EMAIL' })
#IsEmail({}, { message: 'ERROR_INVALID_EMAIL' })
email?: string;
...
addresses?: CreateUserAddressDto[];
}
create-user-address.dto.ts
export class CreateUserAddressDto {
...
#IsNotEmpty()
street: string;
...
}
CreateUserDto data is validated correctly and generates InternalServerErrorResponse, but CreateUserAddressDto data is not validated when there is some item in my array. Any idea how I can do this validation?
Nest fw uses class-transformer to convert a json to a class object. You have to set the correct type for the sub-attribute if it is not a primitive value. And your attribute is an array, you have to config to tell class-validator that it is an array, and validate on each item.
Let's update CreateUserDto
import { Type } from 'class-transformer';
import { ..., ValidateNested } from 'class-validator';
export class CreateUserAddressDto {
...
#ValidateNested({ each: true })
#Type(() => CreateUserAddressDto)
addresses?: CreateUserAddressDto[];
...
}
What you are trying to do is - to basically add logic to primitive validators provided out of the box with nest - aka - defining a custom validator.
This can be done by using the two classes ValidatorConstraint and ValidatorConstraintInterface provided by the class validator.
In order to sort this, transform the incoming input / club whatever data you want to validate at once into an object - either using a pipe in nestjs or sent it as an object in the API call itself, then attach a validator on top of it.
To define a custom validator:
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
/**
* declare your custom validator here
*/
#ValidatorConstraint({ name: 'MyValidator', async: false })
export class MyValidator implements ValidatorConstraintInterface {
/** return true when tests pass **/
validate(incomingObject: myIncomingDataInterface) {
try {
// your logic regarding what all is required in the object
const output = someLogic(incomingObject);
return output;
} catch (e) {
return false;
}
}
defaultMessage() {
return 'Address update needs ... xyz';
}
}
Once you have defined this, keep this safe somewhere as per your project structure. Now you just need to call it whenever you want to put this validation.
In the data transfer object,
// import the validator
import { Validate } from 'class-validator';
import { MyValidator } from './../some/safe/place'
export class SomeDto{
#ApiProperty({...})
#Validate(MyValidator)
thisBecomesIncomingObjectInFunction: string;
}
As simple as that.
I have UpdateUserDto:
export class UpdateUserDto extends PartialType(CreateUserDto) {
}
CreateUserDto:
export class CreateUserDto {
#ValidateNested({ each: true })
#IsOptional()
Point: CreateUserPointDto;
}
CreateUserPointDto:
export class CreateUserPointDto{
#IsString()
name: string
#IsString()
color: string
}
Now partial type makes all properties of CreateUserDto optional, the problem is, it doesn't create all properties of Point that is inside CreateUserDto optional.
How do I go about solving this issue?
Also another unrelated problem, any validation to Point in UpdateUser only works with { PartialType } from '#nestjs/mapped-types'
If I use import { PartialType } from '#nestjs/swagger', For the same code it says Point.property name/color should not exist.
I'm sure you may have moved on from this, but here's something that may resolve the issue in case you come around in the future.
You need to use #Type from class-transformers to ensure you get the types for the nested Point attribute.
Sample code
import { Type } from 'class-transformer';
export class CreateUserDto {
#ValidateNested({ each: true })
#IsOptional()
#Type(() => CreateUserPointDto) // -> this line
Point: CreateUserPointDto;
}
Reading the NestJS docs on the MongoDB technique, I've came along a confusing example:
#Injectable()
export class CatsService {
constructor(#InjectModel(Cat.name) private catModel: Model<CatDocument>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
The line that confuses me is the constructor one; where to #InjectModel is given Cat.name. But, in the cats.schema.ts file, there's no inheritance from another class, nor any valorised static property with that name:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
#Schema()
export class Cat {
#Prop()
name: string;
#Prop()
age: number;
#Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
Am I missing something or could it be a "bug" in the docs?
Cat.name in this case refers to the inherent static property name that all classes have (really all functions have). This gives us a built in constant that we can refer to without having to write our own, but if you'd prefer you can use the string 'Cat' as well. In this case, Cat.name is the string 'Cat', just a different way to reference it that is built in to all classes (and functions).
JavaScript docs on Function.name
I'm writing a NestJS application using Sequelize-Typescript as ORM.
Here I'm trying to get a users favorited jobs (M:M) and so I have a UserEntity, a JobEntity (not relevant for this question) and a FavoriteEntity.
favorite.entity.ts
import { Table, Column, Model, PrimaryKey, ForeignKey, BelongsTo, NotNull } from "sequelize-typescript";
import { IDefineOptions } from "sequelize-typescript/lib/interfaces/IDefineOptions";
import { UserEntity } from "../users/user.entity";
import { JobEntity } from "../jobs/job.entity";
const tableOptions: IDefineOptions = {
timestamp: true,
tableName: "favorites",
schema: process.env.DB_SCHEMA,
} as IDefineOptions;
#Table(tableOptions)
export class FavoriteEntity extends Model<FavoriteEntity> {
#BelongsTo(() => UserEntity)
user: UserEntity;
#ForeignKey(() => UserEntity)
#PrimaryKey
#NotNull
#Column
userId: number;
#BelongsTo(() => JobEntity)
job: JobEntity;
#ForeignKey(() => JobEntity)
#PrimaryKey
#NotNull
#Column
jobId: number;
}
favorite.service.ts
import { Inject, Injectable } from "#nestjs/common";
import { Model } from "sequelize-typescript";
import { IFavorite, IFavoriteService } from "./interfaces";
import { FavoriteEntity } from "./favorite.entity";
#Injectable()
export class FavoriteService implements IFavoriteService {
constructor(
#Inject("FavoriteRepository") private readonly favoriteRepository: typeof Model,
#Inject("SequelizeInstance") private readonly sequelizeInstance,
) {}
public async findByUserId(userId: number): Promise<FavoriteEntity | null> {
return await FavoriteEntity.scope().findAll<FavoriteEntity>({
where: { userId },
});
}
}
I'm getting a type error that I really don't understand:
TSError: тип Unable to compile TypeScript:
src/modules/favorite/favorite.service.ts(21,5): error TS2322: Type
'FavoriteEntity[]' is not assignable to type 'FavoriteEntity | null'.
Type 'FavoriteEntity[]' is not assignable to type 'FavoriteEntity'.
Property 'user' is missing in type 'FavoriteEntity[]'.
I don't understand why it's complaining about a missing user, it's clearly there. (If I make it optional (?) in the entity it complains about the next property until I make them all optional, and then It's complaining about a missing property dataValues in the same way)
What am I missing?
Thanks!
UPDATE:
So I might have figured out something. If I write like this
return await FavoriteEntity.scope().findAll<FavoriteEntity[]>({
where: { userId },
});
with FavoriteEntity[] instead of FavoriteEntity, I get
Property 'dataValues' is missing in type 'FavoriteEntity[]'
I don't really know which is the correct way to write it, but I still have a problem either way...
findAll will return an array of entities. You only want one or null. For that, use findOne. Also, you don't need to do return await here. Unless in a try-catch block, this is redundant.