Nest: Transforming and serializing entities to JSON

Created on 1 Aug 2018  路  7Comments  路  Source: nestjs/nest

I'm submitting a...


[ ] Regression 
[ ] Bug report
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current / expected behavior

Hey! I'm currently evaluating Nest for use on a larger project. Really liking the framework so far.

One thing I can't seem to see clearly in the documentation is a recommended approach to JSON serialization. Coming from Laravel and Rails, I'm used to using packages such as ThePHPLeague / Fractal, or Rails' ActiveModel::Serializer to define the structure of each entity for when it's serialized as JSON.

I've seen recommendations to use class-transformer, which allows you to hide/show properties using decorators. However, I see some significant benefits when using the two libraries mentioned above:

  1. Serializers are kept seperate from the entity or class that needs to be transformed. This makes it possible to supply different serializers depending on the context of the request.
  2. Allows the definition, and subsequent optional loading of related entities. This introduces powerful possibilities when used with HTTP - eg. <root>/posts?include=author,comments.author will return posts, with comments and the author of each comment.
  3. Support for pagination as standard.

Is there a common package or process used to achieve parts (or all) of the above in Nest / Node?

question 馃檶

Most helpful comment

What about serializers on mongoose models? These are not covered.

All 7 comments

Hi @jonlambert,
Thank you!

I think that you should be able to easily achieve all these functionalities using interceptors.

  1. Use class-validator or put a custom logic inside an interceptor (treat it like a serializer)
  2. Create an interceptor that reads query params (using context.switchToHttp().getRequest()) and utility functions like pick or get from lodash https://lodash.com/docs/4.17.10#pick, and afterward, map the result.
  3. Same as 2

Thanks for the response! For those finding this issue later, serializers just like these have been added to Nest docs. Cheers @kamilmysliwiec 馃憤

What about serializers on mongoose models? These are not covered.

Bump, what about mongoose? @kamilmysliwiec

anyway of using this with mongoose?

@DimosthenisK, @rlesniak, @cerireyhan If you are still interested, I was able to find one approach to make serialization work with mongoose and that is by using typegoose.

TL;DR: Set prototype of returned object to a prototype of a class with your serialization decorators.

Versions that I worked with:
"@nestjs/common": "6.6.7",
"@hasezoey/typegoose": "6.0.0-27",
"typescript": "3.7.0-dev.20190907",

Problem: The way I see it, functions like 'find'/'save'/etc.. return Documents and for serialization to work, you have to map those results to concrete types with serialization decorators. One way is to use any available mapping mechanism (e.g. morphism npm package), and that way you might not even need a Serialization approach. But if you want to use serialization on defined model, then typegoose helps a little by removing a need to maintain an interface and defining serialization decorators in your model.

Examples below might be missing some parts like imports and setup of modules/providers, but should be enough

Model:

import { prop as Property } from '@hasezoey/typegoose';

export class Asset {
  public id!: string;

  @Property({ required: true, index: true })
  public title!: string;

  @Exclude()
  @Property()
  public excludeMe: string;
}

Controller

@UseInterceptors(ClassSerializerInterceptor)
@SerializeOptions({
  excludePrefixes: ['_'],
})
@Controller('assets')
export class AssetsController {
  constructor(private readonly assetsService: AssetsService) {}

  @Get('/:id')
  async getById(@Param('id') id: string): Promise<Asset> {
    return await this.assetsService.getById(id);
  }
}

Service

import { DocumentType } from '@hasezoey/typegoose';

@Injectable()
export class AssetsService {
  constructor(
    @Inject('assets')
    private readonly model: Model<DocumentType<Asset>>,
  ) {
  }

  async getById(id: ObjectId | string): Promise<Asset> {
    const result = await this.model.findById(id);
    if (result) {  
         result = result.toObject();
         const classProto = Object.getPrototypeOf(new Asset());
         Object.setPrototypeOf(result, classProto);
         return result ;
    }

    throw new NotFoundException(`Asset with Id '${id}' not found.`);
  }
}

To me, it kind of looks like a hack, but I have not found a better way without introducing dedicated DTOs to return. Trying to do this in mongoose middleware seems to not work and creating a separate nestjs interceptor will not guarantee a positive result, because in some cases you want to return a DTO which contains a property of Document type, and in that case you basically have to iterate through all properties to find ones to transform, which is a hassle.

Hope this helps a little :)

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

2233322 picture 2233322  路  3Comments

cdiaz picture cdiaz  路  3Comments

janckerchen picture janckerchen  路  3Comments

cojack picture cojack  路  3Comments

mishelashala picture mishelashala  路  3Comments