The problem with generating a generic response for Swagger in Nest.js

The problem with generating a generic response for Swagger in Nest.js

Description

In the Nest.js application, for GET controllers (retrieving a list of items), I want to generate a sample response in Swagger. The response object contains fields: status, notification, and an object called details. Inside the details object, there are pagination information and an items field, which is an array of returned elements. The items array should be passed generically because the type of returned elements will vary for different controllers. According to the Nest.js documentation, to pass a generic type, a custom decorator needs to be created (in my case, it’s called ApiPaginatedResponse).

What’s the problem?

Unfortunately, when items are nested within the details object, they are generated outside of the details object. The documentation does not include an example with nesting (double generic passing).

What do I want to achieve?

The items array should be located inside the details object. Perhaps someone has encountered a similar issue and can help me. I’ve tried many ways already, but I don’t think I fully understand how it works.

Dto’s:

export class GetUsersDto {
  id: number;
  first_name: string;
  last_name: string;
  email_address: string;
}

export class Pagination {
  pageSize: number;
  total: number;
  current: number;
}

export class Details<TData> {
  @ApiProperty({ type: Pagination })
  pagination: Pagination;

  @ApiProperty()
  items: TData[];
}

export class PaginatedResponseDto<TData> {
  @ApiProperty()
  status: number = 200;

  @ApiProperty({ type: NotificationDto })
  notification: NotificationDto;

  @ApiProperty()
  details: Details<TData>;
}

Controller getUsers:

@Get('get-users')
  @ApiPaginatedResponse(GetUsersDto)
  async getUsers(
    @Query() filterDto: GetUsersFilterDto,
    @Req() { user: { userId } }: TUserReq,
    @Res() res: Response,
  ): Promise<Response<TResponse>> {
    const result = await this.adminInstitutionService.getUsers(filterDto, userId);
    return res.status(result.status).send(result);
  }

ApiPaginatedResponse.decorator.ts:

import { applyDecorators, Type } from '@nestjs/common';
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
import { PaginatedResponseDto } from '../dto';

export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => {
  return applyDecorators(
    ApiExtraModels(PaginatedResponseDto, model),
    ApiOkResponse({
      schema: {
        allOf: [
          { $ref: getSchemaPath(PaginatedResponseDto) },
          {
            properties: {
              items: {
                type: 'array',
                items: { $ref: getSchemaPath(model) },
              },
            },
          },
        ],
      },
    }),
  );
};

The generated response. As you can see, items are generated outside of the details object.

{
  "status": 200,
  "notification": {
    "title": "string",
    "message": "string"
  },
  "details": {
    "pagination": {
      "pageSize": 0,
      "total": 0,
      "current": 0
    },
    "items": [
      "string"
    ]
  },
  "items": [
    {
      "id": 0,
      "first_name": "string",
      "last_name": "string",
      "email_address": "string",
    }
  ]
}