So I have a DTO:
#IsBoolean()
someBool: boolean
#IsString()
someStr: string
???
someObject: any
How do I validate someObject? Its value is a plain JS object, just some arbitrary JSON data, so it's NOT another DTO. It seems to me I'm supposed to create a custom validator / decorator in this case. Is this correct?
Or is there a better way?
This cannot be done using just JSON. You have to create a class type and use that as follows:
#IsBoolean()
someBool: boolean
#IsString()
someStr: string
#ValidateNested()
#Type(() => SomeClass)
someObject: SomeClass
And in SomeClass you also add validation decorators to the props.
Related
I need to use an interface through class-validator to validate the incoming form for a specific field in the incoming request body.
The interface:
export enum Fields {
Full_Stack_Dev = 'full stack dev',
Frontend_Dev = 'frontend dev',
Backend_Dev = 'backend dev',
}
export interface Experience {
field: Fields;
years: number;
}
And here is the DTO Class:
#IsEnum(Languages)
languages: Languages[];
experience: Experience[]; // 👈 Not sure which decorator to use for interfaces
Okay after doing a lot of research, I found a workaound for this:
First of all, Interfaces CANNOT be used directly. Officially declared by class-validators issue here
This is what I did:
Changed the interface into a separate class and added validation on its properties
class ExperienceDto {
#IsEnum(Fields)
field: Fields;
#IsNumber()
years: number;
}
Then used this class as type to validate Array of Objects in the ACTUAL DTO CLASS (not the above one)
#ArrayNotEmpty()
#ArrayMinSize(1)
#ArrayMaxSize(3)
#ValidateNested({ each: true })
#Type(() => ExperienceDto) // imported from class-transformer package
experience: ExperienceDto[];
I'm validating my DTOs with the class-validator package. I enabled the transformation via
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
in my main.ts file as described in the docs
https://docs.nestjs.com/techniques/validation#transform-payload-objects
I have a optional configuration field in my DTO. This field should be transformed to an empty object if it doesn't exist. The transformation decorator is described here
https://docs.nestjs.com/techniques/serialization#transform
I was hoping to come up with this solution:
export class MyDTO {
#IsObject()
#IsOptional()
#Transform(configuration => configuration || {})
public configuration: object;
}
When I call my API route
#Post()
public create(#Body() myDTO: MyDTO): void {
console.log(myDTO);
}
with an empty body, so without the field configuration my MyDTO instance is
{}
although I would expect it to be
{
configuration: {}
}
What is wrong or what am I missing? I tried to debug the code and it never hits the transformation function. So the #Transform doesn't trigger.
Update
It seems I have to do this
#IsObject()
#IsOptional()
#Transform(configuration => configuration || {}) // will be used if the field exists
public configuration: object = {}; // will be used if the field doesn't exist
The initial value will be used if you pass in an empty body. The transformation only runs if you pass in the field but assign a value like null to it.
Gonna go ahead n put this here too: why not just let typescript manage the default value with setting the value like
export class MyDTO {
#IsObject()
#IsOptional()
public configuration: object = {};
}
That way, if you get a value, great, and if it isn't there, class-transform will put the correct value there.
Looks like there is more discussion about solutions here.
Whilst studying NestJS I encountered an issue, I have the following DTO:
export default class SearchNotesDto {
query: string;
createdAfter: Date;
createdBefore: Date;
}
Which I wish to get when a GET request is made to an endpoint, which is handled by the following function in my controller:
#Get()
getNotes(#Query() searchNotesDto: SearchNotesDto): Note[] {
if (Object.keys(searchNotesDto).length) {
return this.notesService.searchNotes(searchNotesDto);
}
return this.notesService.getAllNotes();
}
My problem is that createdAfter and createdBefore are strings in searchNotesDto, and I wish to work with the Date object, is there a way to implicitly convert those fields to a Date?
#Query will serialize all properties to type string because that's how query string works in terms of HTTP Request. You will need to utilize a Pipe to transform your query to the right shape of data.
https://docs.nestjs.com/pipes
export class SearchNotePipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
// value will be your `searchNotesDto`
const notesDto = new SearchNotesDto();
// do your transformation here
return notesDto;
}
}
I have a class CreateFolderDto with two readonly fields:
export class CreateFolderDto {
public readonly name: string
public readonly user_id: number
}
I have a controller which is:
#UseGuards(AuthGuard('jwt'))
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
The request send to my controller is a good one, I only send the name in json format with an accessToken in the header. The accessToken permit me to get my user_id from the request with req.user.id.
The DTO field user_id is not automatically filled. I would like to fill it automatically.
Is it a way to auto-fill my createFolderDto.user_id variable ?
#Body only wraps actual request body into instance of the CreateFolderDto class. As the body which comes to your endpoint has no such a field, you need to add it manually.
Normally, aggregated fields could be added with custom constructor of your DTO:
export class CreateFolderDto {
public readonly name: string
public readonly session_uuid: string
constructor(bodyValue: any = {}) {
this.name = bodyValue.name
this.session_uuid = generateUuid()
}
}
But in your case, user is attached to request itself, so I believe you have the following options:
Check out your code which attaches the user to request itself. If you are using JWT Auth described in NestJS docs, you cannot do this that way.
You can write custom Interceptor:
Injectable()
export class ExtendBodyWithUserId implements NestInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest()
request.body.user_id = request.user
return next.handle()
}
}
// usage
#UseGuards(AuthGuard('jwt'))
#UseInterceptors(ExtendBodyWithUserId)
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
Last but not least, some personal recommendation. Consider how much you will use this interceptor as an extension, as too many of 'extras' like this bloat the codebase.
I would recommend to change the folderService signature to:
create(createFolderDto: CreateFolderDto, user: User), where folder dto has only the name, without user-related entry. You keep the consistency, separation and clear intentions. In the implementation of create you can just pass user.id further.
And going this way, you don't have to write custom interceptors.
Pick your way and may the consistency in your codebase be with you!
Why does JHipster generate interfaces for each Angular model object?
e.g.
export interface IStudent {
id?: number;
studentIdentifier?: string;
firstName?: string;
lastName?: string;
}
export class Student implements IStudent {
constructor(
public id?: number,
public studentIdentifier?: string,
public firstName?: string,
public lastName?: string,
) {}
}
I cannot find the original discussion but in my understanding, this is because of the way how interfaces work in TypeScript, which is a little different than in Java. They not just describe how a class should look like by defining its method, but also which fields should be present. So you can define, how a JSON from somewhere should look like. Like a POJO. Or a POTO (plain old TypeScript object) :)
By example, you could do that:
let student: IStudent = { id: 123, studentIdentifier: '...',...}
and TS would check if your provided object satisfies the defined structure of student. When you get an object out from the API, you just map a JSON directly this way, so there is no class in between. From the other side, it's more handy to work with classes rather than interfaces, when building objects of IStudent directly. As it also satisfies IStudent, you can make just
let student: IStudent = new Student(123, '...', ..)
which is shorter.
You could rely also on my first snippet (this is how ionic does it, btw. Using interfaces as POJOs/POTOs). Using classes only in TS leads to a bad developer experience IMHO.
Hope that helps a little bit out