Search code examples
typescriptswaggernestjsopenapi

Custom param decorator with Swagger documentation


In Nestjs you can use @Query() query: MyDto to automatically validate your query params, and to generate a Swagger documentation.

I've created a custom decorator, that kinda replaces the @Query decorator. My custom decorator does a few things differently.

BUT, I can't find a way to generate the Swagger documentation automatically, for example:

Using @Query: Swagger screenshot with param documented

Controller:

@Get()
  findAll(@Query() query: AqpDto, @Tenant() tenantId: string) {
    return this.logsService.findAll(query, tenantId);
  }

Using my decorator: Swagger screenshot without params

@Get()
  findAll(@Aqp() query: AqpDto, @Tenant() tenantId: string) {
    return this.logsService.findAll(query, tenantId);
  }

I consume these APIs using swagger-client so the lack of the query param definitions is not only missing documentation, but also breaks the API call, as swagger-client doesn't send the params as expected.

I've tried applyDecorators, I tried to find ways to automatically execute the Query once my decorator is called, I tried to find the source code of Query to identify how it adds the Dto params to Swagger. I don't know what else to do do.

I'm trying to find a clean solution, but no luck so far. Any help is appreciated.


Solution

  • The Query() related code can be found in the @nestjs/common package, in the route-params.decorator.ts file.

    https://github.com/nestjs/nest/blob/master/packages/common/decorators/http/route-params.decorator.ts#L411

    It doesn't do anything with Swagger, and it shouldn't, that's not its responsibility.

    export function Query(
      property?: string | (Type<PipeTransform> | PipeTransform),
      ...pipes: (Type<PipeTransform> | PipeTransform)[]
    ): ParameterDecorator {
      return createPipesRouteParamDecorator(RouteParamtypes.QUERY)(
        property,
        ...pipes,
      );
    }
    

    The @Body(), @Query(), @Param()...etc. decorators all have a very similar implementation. They all call the createPipesRouteParamDecorator(...) function with a RouteParamtypes value.

    export enum RouteParamtypes {
      BODY,
      QUERY,
      PARAM,
      ...
    }
    

    https://github.com/nestjs/nest/blob/master/packages/common/enums/route-paramtypes.enum.ts

    In the end it uses Reflect.defineMetadata(...) to set metadata on the controller about the query, body, param...etc.

    The @nestjs/swagger packages uses the constants under which this metadata is saved and gathers this metadata (using Reflect.getMetadata()).

    For example:

    https://github.com/nestjs/swagger/blob/master/lib/services/parameter-metadata-accessor.ts

    Based on the gathered metadata its able to construct the Swagger documentation. You can tweak this by adding extra decorators provided by the @nestjs/swagger package. With these you can provide extra metadata that is gathered to help generate the Swagger documenation. In your case you can use the @ApiQuery() decorator to provide extra hints.

    @ApiQuery({ 
      name: 'query', 
      schema: { .... },
      type: AqpDto,
      required: true
    })
    @Get()
    findAll(@Aqp() query: AqpDto, @Tenant() tenantId: string) {
      ...
    }