Search code examples
javascriptnestjsswagger-uinestjs-swagger

@nestjs/swagger - Only One Response (Success) Listed in Swagger-UI


I am attempting ot generate docs for a project and it seems like the CLI plugin for @nestjs/swagger is only picking up 1 response for each function (picking it up correctly, by the way, my 200s are are correct and so are my 201s). I am getting only success responses for some reason. I have multiple other failure responses and it isn't picking it up. Most simple example:

response.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsString, IsNumber, IsObject } from 'class-validator';

export class ResponseBodyDto {
    @IsString() @IsOptional()
    @ApiProperty({ description: "The message associated with the response." })
    message: string;
    
    @IsObject() @IsOptional()
    @ApiProperty({ description: "The data associated with the response." })
    data: any;
}

export class ResponseDto {
    @IsNumber() @ApiProperty({ description: "The response status code." })
    statusCode: number;

    @IsObject() @IsOptional()
    @ApiProperty({ description: "The body, containing message and data, associated with this response." })
    body: ResponseBodyDto;
}
merchant.controller.ts

import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { MerchantService } from './merchant.service';
import { AdminGuard } from '../../auth/admin.guard';
import { ApiTags } from '@nestjs/swagger';
import { ResponseDto } from '../../response.dto';

@UseGuards(AdminGuard)
@Controller(`v${process.env.PAPI_VERSION}/admin/merchant`)
export class MerchantController {
  constructor(private readonly merchantService: MerchantService) {}

  @Get()
  findOne(@Req() request: any): Promise<ResponseDto> {
    return this.merchantService.findOne(request._merchantId);
  }
  
}
import { Injectable } from '@nestjs/common';
import { Merchant } from './entities/merchant.entity';
import { ResponseDto } from '../../response.dto';

@Injectable()
export class MerchantService {

  async findOne(merchantId: string): Promise<ResponseDto> {
    try {
      const { Item } = await Merchant.get({ merchantId: merchantId });
      console.log("Retrieved table entries, ", Item);
      return { statusCode: 200, body: { message: "", data: Item } };
    } catch(err) {
      console.log(err);
      return { statusCode: 500, body: { message: "ERROR.", data: err } };
    }    
  }
}

This code outputs the following to Swagger-UI:

Image of Swagger-UI, only displays code 200 under Responses.

I am lost as to how it catches the success codes, all specific to each function between 200 and 201, but isn't picking up the failures. Is it only because it is the same Dto? In some function, failure can happen well before success, so its not like its just defaulting to the first usage in the file either. Any help is appreciated.

I tried: HttpCode(), ApiResponse(), and a few other related Decorators. None worked. I tried casting the returning objects to the exact Dto class before returning, just in case, and it also did't do anything. In the end of the day, I also don't want to add a decorator for every single controller. That is why I am using the CLI.


Solution

  • For Swagger to understand the error/return codes of your endpoint you have no choice but to specify it! Concerning codes 200 and 201, it is deduced thanks to your endpoint type :

    POST = 201 GET = 200

    For defined returned code and type for swagger, you can use :

    • @ApiOkResponse({ type: ResponseDto })
    • @ApiInternalServerErrorResponse({ type: MyType500Error })

    See this : https://docs.nestjs.com/openapi/operations#responses

    If you don't want to add decorators to your endpoints each time, you can simply create one that applies the decorators you need : https://docs.nestjs.com/custom-decorators

    In your example, so that your endpoint is perfectly documented on swagger, I suggest this :

    
    response.dto.ts;
    
    export class ResponseBodyDto {
        @IsString()
        @IsOptional()
        @ApiProperty({
            description: 'The message associated with the response.',
        })
        declare message: string;
    
        @IsObject()
        @IsOptional()
        @ApiProperty({
            type: TypeThis,
            description: 'The data associated with the response.',
        })
        declare data: TypeThis;
    }
    
    export class ResponseDto {
        @IsNumber()
        @ApiProperty({ description: 'The response status code.' })
        declare statusCode: number;
    
        @IsObject()
        @IsOptional()
        @ApiProperty({
            type: ResponseBodyDto,
            description: 'The body, containing message and data, associated with this response.',
        })
        declare body: ResponseBodyDto;
    }
    
    
    
    
    // merchant.controller.ts;
    
    @UseGuards(AdminGuard)
    @Controller(`v${process.env.PAPI_VERSION}/admin/merchant`)
    export class MerchantController {
        constructor(private readonly merchantService: MerchantService) {}
    
        @Get()
        @ApiOkResponse({ type: ResponseDto })
        @ApiInternalServerErrorResponse({ type: ErrorResponse })
        public findOne(@Req() request: any): Promise<ResponseDto> {
            return this.merchantService.findOne(request._merchantId);
        }
    }