Flexible entities with class-transformer
By: Ildar Timerbaev
Tags: API, Typescript, Axios, JavaScript, Vuejs

Hello dear readers!

Introduction

Each developer worked, working or will work with API for getting remote data. These data returns as some kind of models, entities or format. But We often have a lot of problems with data format and code case.

Problems

For example let’s make request to TMDB to get latest movie(https://developers.themoviedb.org/):

{

"title": "Movie name",

"overview": "Movie overview",

"release_date": "2020-11-02",

"adult": false,

"runtime": 5400

}

Next We could use simple JavaScript object or class instance for storing data.

const response = await axios.get(

'https://api.themoviedb.org/3/movie/latest',

...

)

const data = response.data; // or class LatestMovie {

// Properties



constructor(params) {

// Init properties from params

}

}

But both cases has problems:

Simple JavaScript object don’t give any information about stored data and force us to work with snake_case code(No JS way); Class description has a lot of boilerplate code(describing properties and initialization in constructor and also working with snake_case code in constructor);

Solution

We could to solve this problem fast and painless with class-transformer. This package gives us instrument for declarative classes describing, changing property names and properties transformation at init of class instance. So now We could rewrite our data class as:

import { Expose, Type, Transform, plainToClass } from 'class-transformer'; class LatestMovie {

@Expose()

public title: string;



@Expose()

public overview: string; @Expose({ name: 'release_date')

@Type(() => Date)

public releaseDate: Date; @Expose({ name: 'adult' })

public isAdult: boolean; @Expose()

@Transform(value => `${value / 60} minutes`, { toClassOnly: true })

public runtime: string;

} const response = await axios.get(

'https://api.themoviedb.org/3/movie/latest',

...

)

const latestMovie = plainToClass(LatestMovie, response.data);

As you can see We shouldn’t write constructor and describe properties. Class-transformer do it itself with decorators:

@Expose mark property as transformable; @Type describes type of property(it could be built-in package types or another class-transformer class); @Transform describes custom value transformation; plainToClass(Class, data) set data to class properties.

Generalization

Writing and importing plainToClass function each time may be not convenient. Let’s make code more flexible an generic:

// axios.ts import Axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

import { plainToClass, plainToClassFromExist } from 'class-transformer'; function createAxiosInstance(): AxiosInstance {

return Axios.create({

baseURL: process.env.apiUrl,

responseType: 'json',

validateStatus(status) {

return [200].includes(status)

},

})

}



export const axiosInstance: AxiosInstance = createAxiosInstance; export async function request<T>(config: AxiosRequestConfig, Model: any): Promise<T> {

const response = await axiosInstance.request<T>(config);

return plainToClass<T, AxiosResponse['data']>(Model, response.data);

}

Now we can use it as:

const movie = await request<LatestMovie>({

method: 'get',

url: 'https://api.themoviedb.org/3/movie/latest',

}, LatestMovie);

And movie object will instance of model class. That’s all!

Conclusion

Class-transformer gives us declarative functionality for describing models that We should transform before using. Also one model may be as property of another model and one model may extends another model. So We have JavaScript class that transform properties before using.

Thanks for reading!