How to change the type for query param? - nestjs

I want to transform my query param from string to number. I use dto technic.
import { IsOptional, IsInt, Min } from 'class-validator';
import { Transform } from 'class-transformer';
export class PaginationDto {
#IsOptional()
#IsInt()
#Transform(val => Number.parseInt(val))
#Min(1)
perPage: number;
Use dto in controller
#Get('/company')
public async getCompanyNews(
#Query() query: PaginationDto
) {
console.log(typeof query.page);
Result: string.
How do I change the type correctly?

To ensure that DTOs get transformed, the transform: true option must be set for the ValidationPipe. Without that, the original incoming object will be passed after going through validations.

Related

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.

NestJS, serialise bigint parameters in DTO

I have DTOs with parameters which have bigint type. Currently, when I receive these DTOs all these pramaters always have type string. Here is example:
#Get("")
async foo(#Query() query: Foo) {
console.log(typeof Foo.amount) //string
}
My DTO:
export class Foo {
amount: bigint;
}
How to make it works and have bigint type of amount
In your DTO:
import { Transform } from 'class-transformer';
//...
export class Foo {
#Transform(val => BigInt(val.value))
amount: bigint;
}
Also in your controller:
import {ValidationPipe} from '#nestjs/common';
//...
#Get("")
async foo(#Query(new ValidationPipe({ transform: true })) query: Foo) {
console.log(typeof Foo.amount) //should be bigint
}
Whats Happening:
ValidationPipe is a default pipe in NestJS that validates query property with the rules defined in Foo DTO class using Reflection. The option transform: true will transform ie; execute the function inside #Transform decorator and replace the original value with the transformed value (val => BigInt(val) in your case).
This will transform the stringified "bigint" to the primitive "bigint".
EDIT: Updated the function inside Transform decorator to match class-transformer v0.4.0

How to make parameter type-safe for method that uses Sequelize in TypeScript

I'm working with sequelize-typescript in a NestJS project and I have service classes that use sequelize models to run CRUD operations. This is an example:
import { Injectable, Inject } from "#nestjs/common"
import { Platform } from "./platform.model"
import { Game } from "../game/game.model"
import { QueryOptionsDto } from "./dto/queryOptions.dto"
import { FindOptions } from "sequelize/types"
import { PLATFORMS_REPOSITORY } from "src/core/constants"
#Injectable()
export class PlatformService {
constructor(
#Inject(PLATFORMS_REPOSITORY) private platformRepository: typeof Platform,
) {}
// the payload parameter is the problem here
public async addPlatform(payload) {
try {
const platform = await this.platformRepository.create(payload)
return platform
} catch (err) {
return Promise.reject(err)
}
}
}
But I can't assign any type to the payload parameter. Naturally, I want to make this type-safe, but TypeScript seems to want the type of the object passed to .create() to be the same type as the corresponding model and have all the same properties and methods. This is the error I get if I try to give it an object type with the properties it should have ({ name: string, year: number }):
Argument of type '{ name: string; year: number; }' is not assignable to parameter of type 'Platform'.
Type '{ name: string; year: number; }' is missing the following properties from type 'Platform': games, $add, $set, $get, and 33 more.
The only solution is to just leave the type as inferred any but this obviously defeats the purpose of using TypeScript.
Any suggestions?

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

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

NestJS: How to transform an array in a #Query object

I'm new to NestJS and I am trying to fill a filter DTO from query Parameters.
Here is what I have:
Query:
localhost:3000/api/checklists?stations=114630,114666,114667,114668
Controller
#Get()
public async getChecklists(#Query(ValidationPipe) filter: ChecklistFilter): Promise<ChecklistDto[]> {
// ...
}
DTO
export class ChecklistFilter {
#IsOptional()
#IsArray()
#IsString({ each: true })
#Type(() => String)
#Transform((value: string) => value.split(','))
stations?: string[];
// ...
}
With this, the class validator does not complain, however, in the filter object stations is not actually an array but still a single string.
I want to transform it into an array within the validation pipe. How can I achieve that?
You can pass an instance of the ValidationPipe instead of the class, and in doing so you can pass in options such as transform: true which will make class-validatorand class-transformer run, which should pass back the transformed value.
#Get()
public async getChecklists(#Query(new ValidationPipe({ transform: true })) filter: ChecklistFilter): Promise<ChecklistDto[]> {
// ...
}
export class ChecklistFilter {
#IsOptional()
#IsArray()
#IsString({ each: true })
#Type(() => String)
#Transform(({ value }) => value.split(','))
stations?: string[];
// ...
}
--
#Get()
public async getChecklists(#Query() filter: ChecklistFilter): Promise<ChecklistDto[]> {
// ...
}
"class-transformer": "^0.4.0"
"class-validator": "^0.13.1"
This can be handled without a separate DTO class using the ParseArrayPipe:
#Get()
findByIds(
#Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
console.log(ids);
console.log(Array.isArray(ids)); //returns true
return 'This action returns users by ids';
}
ref: https://docs.nestjs.com/techniques/validation#parsing-and-validating-arrays
You can change your initial query a bit:
localhost:3000/api/checklists?stations[]=114630&stations[]=114666&stations[]=114667&stations[]=114668
And your controller:
#Get()
public async getChecklists(#Query('stations') filter: string[]): Promise<ChecklistDto[]> {
// ...
}
This way the default mechanism will work fine and will convert query params into string array and no any additional dependencies or handling required.
You also can wrap it with your DTO if needed, but you get the idea.
the only problem you had there is in the order of validations. You can do it like this:
export class ChecklistFilter {
#IsOptional()
#Transform((params) => params.value.split(',').map(Number))
#IsInt({ each: true })
stations?: number[]
// ...
}
If you want numbers instead of ints: #IsNumber({}, { each: true })
Had a similar issue, what works for me is apply a custom transform:
export class ChecklistFilter {
#ApiProperty({ type: [Number] })
#IsOptional()
#IsArray()
#Transform((item) => item.value.map((v) => parseInt(v, 10)))
stations?: number[];
//...
}

Resources