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