NestJS class-validators on incoming requests using interface - node.js

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[];

Related

Custom param decorator which transform param to database entity

In Laravel (php) has route /article/:article, and in controller method I get the model:
function getArticle(ArticleModel $article) {...}
How to make this in NestJS?
My controller:
#Controller('/articles')
export class ArticlesController {
#Get('/:article/edit')
editArticle(#Param('article') articleId: number) {...}
}
How to transform #Param('article') to custom decorator #ArticleParam() which will return my Article entity by id in request?
You can implement a custom pipe that injects a TypeORM repository and returns the database entity when prompted with an ID, something like this:
#Injectable()
export class ArticlePipe implements PipeTransform {
constructor(#InjectRepository(Article) private repository: Repository<Article>) {}
transform(value: id, metadata: ArgumentsMetadata): Promise<Article|null> {
return this.repository.findOneBy({ id });
}
}
Then use it like
#Get('/article/:id')
getArticle(#Param('id', ArticlePipe) article: Article) { ... }
You just need to make sure to use the pipe only on modules that provide the Article EntityRepository.
Then, if you need the specific #ArticleParam, it should be like this:
export function ArticleParam = () => applyDecorators(
Param(ArticlePipe)
)

How can I set an entity type within a dto [NestJS]?

I don't even think I've got a proper question so I apologize greatly.
I am consuming a 3rd party API and to update or create records, one of the fields is the entity type.
For example, here is my CreateOrganizationDTO:
export class CreateOrganizationDto {
#ApiProperty({
description: "The entity type. Only 'organization' is allowed.",
example: "organization",
})
#IsNotEmpty()
readonly #type: string; <-- this
...
I'm not sure how to properly handle the # character. I've tried different versions of '#type' or ['#type']. Clearly, neither are correct. I'm also not sure how to send that data through my service. For example:
// MyService.ts
const {name, #type, active} = updateOrganizationDto;
organization.name = name;
organization.#type = #type; <-- not going to work :(
organization.active = active;
...
What is the proper way to "escape" this # throughout my application? Thank you for any suggestions!

NestJS deserializing #Query() to a DTO with complex types

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

NestJS - Validating body conditionally, based on one property

I'm trying to find a nice way to validate a body using DTO (using the brilliant class-validator and class-transformer libraries). It works really well, even for nested structures but in my case I'd like to have the body property based on some conditions.
Example that will probably help to understand:
Let's imagine my body should always have selectedCategory.
Based on that field, the content could either be from category 1, which contains prop1 OR from category 2, which contains prop2.
I do not want to allow a null for both of them, I really want to have to either have prop1 defined or prop2 based on the selectedCategory.
I think that I could use a pipe, but then how can I specify the correct DTO to use?
I've built a "base" class with all the common properties and few other classes that inherit from it.
I could instantiate the pipe manually based on the property selectedCategory, that'd be ideal but I have no clue what to pass as a second argument of the pipe (metadata).
Thanks for your help.
Have you tried using groups?
Instead of having multiple DTOs, you just create one DTO. Every property is assigned to one or multiple groups:
#Min(12, {groups: ['registration', 'update']})
age: number;
#Length(2, 20, {groups: ['registration']})
name: string;
You can then conditionally pass the groups to class transformer / validator:
#Injectable()
export class ConditionalValidationPipe implements PipeTransform {
async transform(entity: any, metadata: ArgumentMetadata) {
// Dynamically determine the groups
const groups = [];
if (entity.selectedCategory === 1) {
groups.push('registration');
}
// Transform to class with groups
const entityClass = plainToClass(EntityDto, entity, { groups })
// Validate with groups
const errors = await validate(entityClass, { groups });
if (errors.length > 0) {
throw this.createError(errors);
}
return entityClass;
}
}
Have you tried the ValidateIf statement?
You can have multiple validations for props1 or props2 and apply them if selectedCategory is "category 1" or "category 2" accordingly.

Why does JHipster generate Interfaces for Angular model objects?

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

Resources