NestJs/swagger: Define ref schemas without DTO classes
Asked Answered
C

3

10

I have an app where I define the API response schemas as plain javascript objects according to the open-api spec. Currently I am passing that to the ApiResponse decorator in @nestjs/swagger as follows:

class CatsController {

  @Get()
  @ApiResponse({
    status: 200,
    schema: catSchema // plain js object imported from another file
  })
  getAll() {}
}

This is working great. However, the output open-api spec contains the verbose schema for every endpoint which uses the catSchema. Instead, I want the output swagger file to have the catSchema under the components section, and have a corresponding $ref in the paths section.

components:
  schemas:
    Cat:
      properties:
        name:
          type: string
paths:
  /cats/{id}:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cat'

So far, it seems the only way to do that would be to define the schema as a DTO class and use the ApiProperty decorator for each class property. In my case, that means I have to refactor all the plain object schemas in open-api spec to be DTO classes.

Is there a way to feed the raw schema to the library and get the expected outcome?

// instead of this:
class CatDto {
  @ApiProperty()
  name: string;
}

// I want to do:
const catSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' }
  }
}
Counteraccusation answered 20/9, 2020 at 11:46 Comment(2)
In short, No The reason behind this is: @ApiProperty is the tag by which swagger in NestJs understands the schema and it is not supported in inline schema design. Can you explain a bit more in detail on how do you want to pass the schema?Ragged
Thanks for the response @TanmoyBhattacharjee. I want to pass the inline schema to @ApiResponse directly. It works well, except it doesn't do $ref schemas. The output contains the schema directly under the responses section. Does that make sense?Counteraccusation
C
5

After days and days of trial and error, I was able to pull this off using an interesting trick in Javascript.

First I created the open-api spec as a plain object (as asked in the question). Then passed that to a new decorator, where the magic happens.

In the decorator, I create a DTO class with a predefined name, and map the properties from the plain object to the DTO class. The tricky part is to give it a name dynamically. That can be achieved by the following technique.

const dynamicName = 'foo'; // passed as a parameter to the decorator

class IntermediateDTO {
  @ApiProperty(schema) // schema as a plain object
  data: any;
}

const proxyObject = {
  [dynamicName] = class extends IntermediateDTO {}
}

By using the proxy object, and assigning class extends IntermediateDTO {} to a property in that, the entry gets a name dynamically. Now this new DTO with the dynamic name can be passed to the ApiResponse decorator of @nestjs/swagger to achieve the expected result.

Counteraccusation answered 24/10, 2020 at 13:23 Comment(1)
Does this still work? Can you please clarify the usage in @ApiResponse()?Sternpost
S
6

I guess this can also be achieved with using getSchemaPath and ApiExtraModels:

import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger';

@ApiExtraModels(CatDto) // for CatDto to be found by getSchemaPath()
@ApiResponse({
  schema: {
    '$ref': getSchemaPath(CatDto)
  }
})

More on extra models: https://docs.nestjs.com/openapi/types-and-parameters#extra-models


In my case, that means I have to refactor all the plain object schemas in open-api spec to be DTO classes.

You don't need to manually annotate objects, you can also use this plugin, which is opt-in: https://docs.nestjs.com/openapi/cli-plugin

Stans answered 26/4, 2022 at 9:52 Comment(1)
Thanks for the answer. My question is more about sticking with the plain schema json, rather than converting them to be DTO classes.Counteraccusation
C
5

After days and days of trial and error, I was able to pull this off using an interesting trick in Javascript.

First I created the open-api spec as a plain object (as asked in the question). Then passed that to a new decorator, where the magic happens.

In the decorator, I create a DTO class with a predefined name, and map the properties from the plain object to the DTO class. The tricky part is to give it a name dynamically. That can be achieved by the following technique.

const dynamicName = 'foo'; // passed as a parameter to the decorator

class IntermediateDTO {
  @ApiProperty(schema) // schema as a plain object
  data: any;
}

const proxyObject = {
  [dynamicName] = class extends IntermediateDTO {}
}

By using the proxy object, and assigning class extends IntermediateDTO {} to a property in that, the entry gets a name dynamically. Now this new DTO with the dynamic name can be passed to the ApiResponse decorator of @nestjs/swagger to achieve the expected result.

Counteraccusation answered 24/10, 2020 at 13:23 Comment(1)
Does this still work? Can you please clarify the usage in @ApiResponse()?Sternpost
A
1

I don't know if this is what you want, I did it like this

@ApiResponse({
    status: 200,
    schema: {
      example: // write the response you want here
      [    
        {
          userId: 1,
          name: 'name',
          
        },
      ],
    },
  })
Archine answered 9/3, 2022 at 7:43 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Use

© 2022 - 2024 — McMap. All rights reserved.