Frank
Frank

Reputation: 745

Nest + Swagger not recognizing endpoints or parameters

I'm having an issue with Nest + Swagger. When I open my swagger docs I see all of the endpoints I expect but am having two issues:

  1. When I click on a method it expands all of the methods for that controller
  2. The post method says No parameters despite having a DTO defined for the body

Ultimately I think the issue is: Swagger + Nest is not creating unique operationId's for each method. My understanding is that methods will only fail to get unique operationId's when they are not sufficiently unique: 2 methods with identical call signatures for example.

In the past when I've had issues like this it was either because I was missing the @ApiTags decorator, or I had accidentally included duplicate endpoints.

In general it feels like I missed a step in configuring Swagger, or I didn't set it up properly with Fastify. I installed fastify-swagger but I'm not actually using it anywhere, but according the docs on Nest's site the route for the swagger JSON should be /api/json when using Fastify, which it is for me.


Things that didn't work:

  1. Annotating method with unique @ApiOperation
  2. Adding a addTag to the DocumentBuilder chain
  3. Deleting the swagger-ui-express and @nestjs/platform-express dependencies
  4. Removing all of the Fastify deps and switching to the Express equivalents

Update:

Adding @ApiOperation({ operationId: 'test' }) to a method does fix this, but I was under impression that @nest/swagger did this automatically. Are my methods not unique enough?

main.ts

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );
  app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); // allows automatic serialization
  app.enableCors();

  const config = new DocumentBuilder().setTitle('PIM API').build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('docs', app, document);

  await app.listen(process.env.APP_PORT || 3001);
}
bootstrap();

some controller

@ApiTags('chat-messages')
@Controller('chat-messages')
export class ChatMessagesController {
  constructor(
    private readonly service: ChatMessagesService,
  ) {}

  @Post()
  create(@Body() createChatMessageDto: CreateChatMessageDto) {
    return this.service.create(createChatMessageDto);
  }

  @Get(':stream_id')
  findByStreamId(@Param('stream_id') streamId: string) {
    return this.service.findByStreamId(streamId);
  }

ChatMessageDto

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

  @IsString()
  value: string;

  @IsRFC3339()
  timestamp: Date;

  @IsNotEmpty()
  streamId: string;
}

Swagger JSON

{
    "/chat-messages":{
       "post":{
          "operationId":"ChatMessagesController_",
          "parameters":[
             
          ],
          "responses":{
             "201":{
                "description":"",
                "content":{
                   "application/json":{
                      "schema":{
                         "$ref":"#/components/schemas/ChatMessage"
                      }
                   }
                }
             }
          },
          "tags":[
             "chat-messages"
          ]
       }
    },
    "/chat-messages/{stream_id}":{
       "get":{
          "operationId":"ChatMessagesController_",
          "parameters":[
             
          ],
          "responses":{
             "200":{
                "description":"",
                "content":{
                   "application/json":{
                      "schema":{
                         "type":"array",
                         "items":{
                            "$ref":"#/components/schemas/ChatMessage"
                         }
                      }
                   }
                }
             }
          },
          "tags":[
             "chat-messages"
          ]
       }
    }
 }

Upvotes: 3

Views: 10668

Answers (2)

linmowang
linmowang

Reputation: 11

No parameters because you did not use @ApiBody or @ApiQuery

ApiBody({ type: dtos.store })(Target, name, descriptor);

In here:

@ApiBody({ type: CreateChatMessageDto })
@Post()
create(@Body() createChatMessageDto: CreateChatMessageDto) {
   return this.service.create(createChatMessageDto);
}
 

Upvotes: 1

jeremynac
jeremynac

Reputation: 1242

Did you try putting @ApiProperty in your dto ?

Like this:

export class CreateChatMessageDto {
  constructor(partial: Partial<CreateChatMessageDto>) {
    Object.assign(this, partial);
  }
  @ApiProperty()
  @IsString()
  value: string;

  @ApiProperty()
  @IsRFC3339()
  timestamp: Date;

  @ApiProperty()
  @IsNotEmpty()
  streamId: string;
}

This allows Swagger to see the properties. This is what NestJs recommends in their documentation See here

Upvotes: 4

Related Questions