Skip to main content

Transformers

Transformer provides a presentation and transformation layer for complex data output, for example JSON response in REST APIs. Let's see any example on how it works.

caution

Supports only JSON response for now.

We have already added a base Transformer abstract class inside the core module, the driver of our logic.

Let's create a BookTransformer for a model, say Book.

// src/transformers/book.ts
import { Transformer } from '@app/core';

export class BookTransformer extends Transformer {
async transform(book: Book$Model): Promise<Record<string, any>> {
return {
id: book.uuid,
name: book.name,
publisherName: book.publisher,
publishedOn: book.publishedAt,
};
}
}

Now to use the transformer, follow the steps below:

//TheFileWhereYouWantToUseIt.ts
import { BookTransformer } from '@app/transformers/book';

const transformer = new BookTransformer();
const payload = await transformer.work({
uuid: '75442486-0878-440c-9db1-a7006c25a39f',
name: "NestJS Boilerplate",
publisher: 'Squareboat'
publishedAt: "2020-12-03 22:00:00",
});

/**
* {
* id: "75442486-0878-440c-9db1-a7006c25a39f",
* name: "NestJS BoilerPlate",
* publisherName: "Squareboat",
* publishedAt: '2020-12-03'
* }
*/
console.log(payload)

Wait, transformer provide much more than a wrapper class.

While creating REST APIs you may come across a case where you want to fetch some related data with the main data.

For example, you may want to fetch author details along with the details of the book's detail that you requested.

Transformer provides an option to add all the available includes and default includes a transformer.

import { Transformer } from '@app/core';

export class BookTransformer extends Transformer {
availableIncludes = ['author']; // will be included on request
defaultIncludes = []; // included by default

async transform(book: Book$Model): Promise<Record<string, any>> {
return {
id: book.uuid,
name: book.name,
publisherName: book.publisher,
publishedOn: moment(book.publishedAt).format('YYYY-MM-DD'),
};
}

async includeAuthor(book: Book$Model): Promise<Record<string, any>> {
await book.$load({ author: true })
return this.item(book.author, new AuthorTransformer());
}
}

Notice the "author" inside the availableIncludes and includeAuthor method, transformer will add include prefix to the requested include. For example, transformer will look for includeAuthor method when you request include=author

Now to use the include the author option, we need to pass the include query params in the URL, like: /books/75442486-0878-440c-9db1-a7006c25a39f?include=author

Bonus

For multiple includes, send comma seperated include options like include=author,publisher,releaseDetails

Now, inside your controller, do the following:

import { Get, Controller } from '@nestjs/common';
import { BookTransformer } from '@app/transformers';

@Controller('books')
export class BookController extends RestController {

@Get('/:uuid')
async get(@Req() req: Request, @Res() res: Response): Promise<Response> {
const inputs = req.all();
const data = {
uuid: '75442486-0878-440c-9db1-a7006c25a39f',
name: "NestJS Boilerplate",
publisher: 'Squareboat'
publishedAt: "2020-12-03 22:00:00",
};

return res.success(await this.transform(data, new BookTransformer, { req }))

/**
* {
* success: true,
* code: 200,
* data: {
* id: "75442486-0878-440c-9db1-a7006c25a39f",
* name: "NestJS BoilerPlate",
* publisherName: "Squareboat",
* publishedAt: '2020-12-03'
* }
* }
*/
}
}

You now know how to include data in your response on-demand, we understand there can be cases where you may want to include some nested relation as well.

You can do it by ?include=author[ratings], now make the following changes in BookTransformer

import { Transformer, Transformer$IncludeMethodOptions } from '@app/core';

export class BookTransformer extends Transformer {
availableIncludes = ['author']; // will be included on request
defaultIncludes = []; // included by default

async includeAuthor(
book: Book$Model,
options: Transformer$IncludeMethodOptions
): Promise<Record<string, any>> {
await book.$load({ author: true })
return this.item(book.author, new AuthorTransformer(), options);
}
}

Notice the options method you are receiving, this is auto-generated payload which you need to share it further to the item, collection method.

Now, inside the AuthorTransformer, you can simply add a new include, rating.