Search code examples
node.jstypescriptserializationnestjsclass-transformer

Nestjs Response Serialization with array of objects


I want to serialize a controller response by the nestjs serialization technique. I didn't find any approach and my solution is as follows:

User Entity

export type UserRoleType = "admin" | "editor" | "ghost";

@Entity()
export class User {
    @PrimaryGeneratedColumn() id: number;

    @Column('text')
        username: string;
    @Column('text') 
        password: string;
    @Column({
        type: "enum",
        enum: ["admin", "editor", "ghost"],
        default: "ghost"
    })
    roles: UserRoleType;
        @Column({ nullable: true })
                profileId: number;  
}

User Response Classes

import { Exclude } from 'class-transformer';

export class UserResponse {
    id: number;

    username: string;

    @Exclude()
    roles: string;

    @Exclude()
    password: string;

    @Exclude()
    profileId: number;  

    constructor(partial: Partial<UserResponse>) {
        Object.assign(this, partial);
    }
}

import { Exclude, Type } from 'class-transformer';
import { User } from 'src/_entities/user.entity';
import { UserResponse } from './user.response';

export class UsersResponse {

    @Type(() => UserResponse)
    users: User[]   

    constructor() { }
}

Controller

@Controller('user')
export class UsersController {
    constructor(
        private readonly userService: UserService
    ) {

    }
    @UseInterceptors(ClassSerializerInterceptor)
    @Get('all')
    async findAll(
    ): Promise<UsersResponse> {
        let users = await this.userService.findAll().catch(e => { throw new   NotAcceptableException(e) })
        let rsp =new UsersResponse() 
        rsp.users = users
        return rsp
    }

It works, but I must explicitly assign the db query result to the response users member. Is there a better way? Thanks a lot

Here the actual Response and wanted result, for a better explanation.

Result in this Approach

{
  "users": [
    {
      "id": 1,
      "username": "a"
    },
    {
      "id": 2,
      "username": "bbbbbb"
    }
  ]
}

Result Wanted

{
    {
      "id": 1,
      "username": "a"
    },
    {
      "id": 2,
      "username": "bbbbbb"
    }
}

Solution

  • I would recommend to directly put the @Exclude decorators on your entity class User instead of duplicating the properties in UserResponse. The following answer assumes you have done so.


    Flat Response

    If you have a look at the code of the ClassSerializerInterceptor, you can see that it automatically handles arrays:

    return isArray
      ? (response as PlainLiteralObject[]).map(item =>
          this.transformToPlain(item, options),
        )
      : this.transformToPlain(response, options);
    

    However, it will only transform them, if you directly return the array, so return users instead of return {users: users}:

    @UseInterceptors(ClassSerializerInterceptor)
    @Get('all')
    async findAll(): Promise<User> {
        return this.userService.findAll()
    }
    

    Nested Response

    If you need the nested response, then your way is a good solution. Alternatively, you can call class-transformer's serialize directly instead of using the ClassSerializerInterceptor. It also handles arrays automatically:

    import { serialize } from 'class-transformer';
    
    @Get('all')
    async findAll(): Promise<UsersResponse> {
      const users: User[] = await this.userService.findAll();
      return {users: serialize(users)};
    }