'NestJs/swagger: Define ref schemas without DTO classes

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' }
  }
}


Solution 1:[1]

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.

Solution 2:[2]

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',
          
        },
      ],
    },
  })

Solution 3:[3]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pubudu Dodangoda
Solution 2 InnerPeace
Solution 3