Nestjs how to make extend partialtype(createDto) make nested properties of dtos inside createDto also optional - nestjs

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;
}

Related

How to serialize Prisma Object in NestJS?

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

Problems with ValidationPipe in NestJS when I need to validate the contents of an array

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.

How to cover with typescript sequelize findAll with include

I'm trying to type static class method of sequelize model which return findAll with include:
import { Model, Optional } from 'sequelize';
import Rule from './rule';
interface OfferAttributes {
id: number;
name: string;
}
type OfferCreationAttributes = Optional<OfferAttributes, 'id'>;
export class Offer extends Model<OfferAttributes, OfferCreationAttributes> implements OfferAttributes {
public readonly id!: number;
public name!: string;
public static getWithRules() {
// error here
return Offer.findAll<Offer & { rules: Rule[] }>({ include: ['rules']});
}
}
I prefer to use generic instead of unknow solution like this:
Offer.findAll({ include: ['rules']}) as unknown as Promise<(Offer & { rules: Rules[] })[]>
but get error Property 'rules' is missing in type 'Offer' but required in type '{ rules: Rule[]; }'. Generic of findAll is <M extends Model> but it's not match <Offer & { rules: Rules[] }>
Does anybody know the most correct way to cover this case with generic?

What is `Cat.name` in NestJS mongoose docs?

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

Sequelize Typescript: Type 'FavoriteEntity[]' is not assignable to type 'FavoriteEntity | null'

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.

Resources