Skip to content

Commit 0708da0

Browse files
content(): minor tweaks to the generic api response tutorial
1 parent c994c97 commit 0708da0

File tree

2 files changed

+56
-58
lines changed

2 files changed

+56
-58
lines changed

content/openapi/operations.md

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -153,26 +153,26 @@ To add an Extension to a request use the `@ApiExtension()` decorator. The extens
153153
@ApiExtension('x-foo', { hello: 'world' })
154154
```
155155

156-
#### Advanced: Generics ApiResponse
156+
#### Advanced: Generic `ApiResponse`
157157

158-
With the ability to provide **Raw Definition**, we can provide **Generics** schema for SwaggerUI. Assume we have the following *generics DTO*
158+
With the ability to provide [Raw Definitions](/openapi/types-and-parameters#raw-definitions), we can define Generic schema for Swagger UI. Assume we have the following DTO:
159159

160160
```ts
161161
export class PaginatedDto<TData> {
162162
@ApiProperty()
163163
total: number;
164-
164+
165165
@ApiProperty()
166166
limit: number;
167-
167+
168168
@ApiProperty()
169169
offset: number;
170-
170+
171171
results: TData[];
172172
}
173173
```
174174

175-
We skip decorating `results` because we will be providing **Raw Definition** for it later. Now, let's assume we have the following `CatDto`
175+
We skip decorating `results` as we will be providing a raw definition for it later. Now, let's define another DTO and name it, for example, `CatDto`, as follows:
176176

177177
```ts
178178
export class CatDto {
@@ -187,56 +187,44 @@ export class CatDto {
187187
}
188188
```
189189

190-
Now, we can start providing a `PaginatedDto<CatDto>` on `CatController`
190+
With this in place, we can define a `PaginatedDto<CatDto>` response, as follows:
191191

192192
```ts
193-
@Controller(...)
194-
export class CatController {
195-
196-
@Get()
197-
@ApiOkResponse({
198-
schema: {
199-
allOf: [
200-
{ $ref: getSchemaPath(PaginatedDto) },
201-
{
202-
properties: {
203-
results: {
204-
type: 'array',
205-
items: { $ref: getSchemaPath(CatDto) },
206-
},
193+
@ApiOkResponse({
194+
schema: {
195+
allOf: [
196+
{ $ref: getSchemaPath(PaginatedDto) },
197+
{
198+
properties: {
199+
results: {
200+
type: 'array',
201+
items: { $ref: getSchemaPath(CatDto) },
207202
},
208203
},
209-
],
210-
},
211-
})
212-
async get(...): Promise<PaginatedDto<CatDto>> {
213-
...
214-
}
215-
}
204+
},
205+
],
206+
},
207+
})
208+
async findAll(): Promise<PaginatedDto<CatDto>> {}
216209
```
217210

218-
We are not done. `PaginatedDto` isn't part of any controller by itself so `SwaggerModule` won't be able to scan it
219-
during initialization. But, `nestjs/swagger` provides an `ApiExtraModels()` decorator for such cases.
211+
In this example, we specify that the response will have allOf `PaginatedDto` and the `results` property will be of type `Array<CatDto>`.
212+
213+
- `getSchemaPath()` function that returns the OpenAPI Schema path from within the OpenAPI Spec File for a given model.
214+
- `allOf` is a concept that OAS 3 provides to cover various Inheritance related use-cases.
215+
216+
Lastly, since `PaginatedDto` is not directly referenced by any controller, the `SwaggerModule` will not be able to generate a corresponding model definition just yet. In this case, we must add it as an [Extra Model](/openapi/types-and-parameters#extra-models). For example, we can use the `@ApiExtraModels()` decorator on the controller level, as follows:
220217

221218
```ts
222-
@Controller(...)
219+
@Controller('cats')
223220
@ApiExtraModels(PaginatedDto)
224-
export class CatController {
225-
...
226-
}
221+
export class CatsController {}
227222
```
228223

229-
> info **Hint** You only need to use `ApiExtraModels` for a specific `Dto` once so find a place where it makes sense for you to do so.
230-
231-
- `getSchemaPath()` returns the OpenAPI Schema path from within the OpenAPI Spec File that `ApiExtraModels` helps `nestjs/swagger` generates,
232-
or `nestjs/swagger` is able to scan automatically.
233-
- `allOf` is a concept that OpenAPI 3 has to cover Inheritance use-cases.
234-
235-
In this case, we tell SwaggerUI that this response will have **allOf** `PaginatedDto` and the `results` property will be of type array and each item will be of type `CatDto`.
236-
If you run the SwaggerUI now, you'd see the generated `swagger.json` for this specific endpoint like the following:
224+
If you run Swagger now, the generated `swagger.json` for this specific endpoint should have the followng response defined:
237225

238226
```json
239-
responses": {
227+
"responses": {
240228
"200": {
241229
"description": "",
242230
"content": {
@@ -261,10 +249,12 @@ responses": {
261249
}
262250
```
263251

264-
Now that we know it works, we can create a custom decorator for `PaginatedDto` as follow:
252+
To make it reusable, we can create a custom decorator for `PaginatedDto`, as follows:
265253

266254
```ts
267-
export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => {
255+
export const ApiPaginatedResponse = <TModel extends Type<any>>(
256+
model: TModel,
257+
) => {
268258
return applyDecorators(
269259
ApiOkResponse({
270260
schema: {
@@ -285,27 +275,25 @@ export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) =>
285275
};
286276
```
287277

288-
then we can use `ApiPaginatedResponse` on our endpoint:
278+
> info **Hint** `Type<any>` interface and `applyDecorators` function are imported from the `@nestjs/common` package.
279+
280+
With this in place, we can use the custom `@ApiPaginatedResponse()` decorator on our endpoint:
289281

290282
```ts
291-
@Get()
292283
@ApiPaginatedResponse(CatDto)
293-
async get(): Promise<PaginatedDto<CatDto>> {}
284+
async findAll(): Promise<PaginatedDto<CatDto>> {}
294285
```
295286

296-
You can modify `ApiPaginatedResponse` as you see fit, maybe make it more generics to handle non-array `results` or maybe different property name than `results`.
297-
Knowing the capabilities of `nestjs/swagger` APIs, you can totally go wild with it and make sure your OpenAPI Spec is correct and covered.
287+
For client generation tools, this approach poses an ambiguity in how the `PaginatedResponse<TModel>` is being generated for the client. The following snippet is an example of a client generator result for the above `GET /` endpoint.
298288

299-
For client generation tools, this approach poses an ambiguity in how this `PaginatedResponse<TModel>` is being generated for the client. The following snippet is an example of a client generator result of the above **GET** request:
300-
301-
```ts
289+
```typescript
302290
// Angular
303-
get(): Observable<{ total: number, limit: number, offset: number, results: CatDto[] }>
291+
findAll(): Observable<{ total: number, limit: number, offset: number, results: CatDto[] }>
304292
```
305293

306294
As you can see, the **Return Type** here is ambiguous. To workaround this issue, you can add a `title` property to the `schema` for `ApiPaginatedResponse`:
307295

308-
```ts
296+
```typescript
309297
export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => {
310298
return applyDecorators(
311299
ApiOkResponse({
@@ -324,5 +312,5 @@ Now the result of the client generator tool will become:
324312

325313
```ts
326314
// Angular
327-
get(): Observable<PaginatedResponseOfCatDto>
315+
findAll(): Observable<PaginatedResponseOfCatDto>
328316
```

content/openapi/types-and-parameters.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,24 @@ async create(@Body() coords: number[][]) {}
239239

240240
#### Extra models
241241

242-
To define additional models that should be inspected by the Swagger module, use the `@ApiExtraModels()` decorator:
242+
To define additional models that are not directly referenced in your controllers but should be inspected by the Swagger module, use the `@ApiExtraModels()` decorator:
243243

244244
```typescript
245245
@ApiExtraModels(ExtraModel)
246246
export class CreateCatDto {}
247247
```
248248

249-
Then, you can get the reference (`$ref`) to your model using `getSchemaPath(ExtraModel)`:
249+
> info **Hint** You only need to use `@ApiExtraModels()` once for a specific model class.
250+
251+
Alternatively, you can pass an options object with the `extraModels` property specified to the `SwaggerModule#createDocument()` method, as follows:
252+
253+
```typescript
254+
const document = SwaggerModule.createDocument(app, options, {
255+
extraModels: [ExtraModel],
256+
});
257+
```
258+
259+
To get a reference (`$ref`) to your model, use the `getSchemaPath(ExtraModel)` function:
250260

251261
```typescript
252262
'application/vnd.api+json': {

0 commit comments

Comments
 (0)