Class validator - The solution for validating the DTO object
Validating the payload passing from the server to the frontend or opposite
The problem
When we were received the response data from the server. Almost we usually write code like
// types.ts
type TGender =
| "male"
| "female"
| "other";
export interface Employee {
id: string;
firstname: string;
lastname: string;
emailAddress: string;
phoneNumber: string;
gender: TGender;
}
// employee.services.ts
export const getEmployee = async (employeeId: string): Employee | null => {
try {
const response = await axios.get<{ employee: Employee; }>("/employee", {
params: {
employeeId,
},
});
return response.data.employee;
} catch (errors) {
console.log(errors);
return null;
}
return null;
};
The ideal in the code above is to use the interface Employee
to define the type for response data. It's good but is not enough. It's just a defined type, not make sure the response data is right
I assume the employee will be received:
{
"id": "1",
"firstname": "Cuong",
"lastname": "Nguyen Huu",
"emailAddress": "cuong email",
"phoneNumber": "0973323842",
"gender": "1",
}
You see, the data is suitable for their type. However, it's not the object what we expect!
The solution
We will use the class-validator to validate the object!
Firstly, we install the needed libraries:
yarn add class-validator class-transformer
class-transformer will convert the literal to the class object, read more in the docs.
Writing the DTO (Data Transfer Object):
export class EmployeeDTO implements Employee {
@IsEmail()
emailAddress: string;
@IsString()
firstname: string;
@IsIn(GENDER_OPTIONS)
gender: TGender
@IsString()
id: string;
@IsString()
lastname: string;
@IsString()
phoneNumber: string;
}
After that we can write the function validatorDto:
import { ClassConstructor, plainToClass } from "class-transformer";
import { validate } from "class-validator";
/**
*
* Validate the payload will be sending or receiving, make sure the data is suitable
*
* @param dto The DTO object to validate
* @param obj The object recieved from response body
*
* @example
* ```ts
* await validatorDto(EmployeeDTO, response.data.employee);
*
* ```
*/
export const validatorDto = async <T extends ClassConstructor<any>>(
dto: T,
obj: Object
) => {
// tranform the literal object to class object
const objInstance = plainToClass(dto, obj);
// validating and check the errors, throw the errors if exist
const errors = await validate(objInstance);
// errors is an array of validation errors
if (errors.length > 0) {
throw new TypeError(
`validation failed. The error fields : ${errors.map(
({ property }) => property
)}`
);
}
};
And the function getEmployees to become:
export const getEmployee = async (employeeId: string): Employee | null => {
try {
const response = await axios.get<{ employee: Employee; }>("/employee", {
params: {
employeeId,
},
});
// Validating the DTO and throw the errors if any properties not suitable
await validatorDto(EmployeeDTO, response.data.employee);
return response.data.employee;
} catch (errors) {
console.log(errors);
return null;
}
return null;
};
Demo
I prepared the codesandbox for you can play with it.
The server side
Actually, this idea was inspired by NestJS, so the way to use this idea should be on the server side. However, the way is the same, so I will no write the example for the server-side.
The conclusion
This way make your the response data is always right!
Comments