Documenting paginated responses

A manual intervention will be necessary.

If we check the Swagger UI once again, we may notice that the findAll() routes don't show a response schema anymore. This is due to the fact that the response is no longer mapped to an entity (or DTO). To fix this will require some manual intervention, as we'll have to dive deeper into Swagger schemas. But at least, we can create a reusable decorator for paginated responses. We'll just need to pass the entity to the decorator (The solution was inspired by this NestJS Doc).

Well, the first step is to make the PaginationMeta interface, a class. Interfaces don't exist at runtime, so Swagger cannot analyze them. As for semantics, let's add the .schema suffix to the file. Lastly, relocate it, while still inside the querying folder, into swagger -> schemas.

The next step is to create a decorator for documenting paginated routes. Let's then create it in querying -> swagger -> decorators -> api-paginated-response.decorator. It will have a generic representing the model (class), so it extends Type, and a parameter with this type.

export const ApiPaginatedResponse = <TModel extends Type>(model: TModel) => {};

We'll then combine some decorators.

applyDecorators();

First, the PaginationMeta is not directly used in any controllers as input/output. Due to this, Swagger won't automatically document it. To fix this, we need to use the @ApiExtraModels() decorator here, in order to explicitly document it.

ApiExtraModels(PaginationMeta),

Then, we'll pass an @ApiOkResponse() to document the OK response. And now, instead of passing the type like in previous cases, we'll pass schema, which is a more raw form of documentation.

ApiOkResponse({
  schema: {
    // ...
  },
}),

Inside it, first let's create a title for a clear name for this schema.

title: `PaginatedResponseOf${model.name}`,

Then, let's use the properties field to indicate which fields this schema has.

properties: {
  // ...
},

Inside it, first we'll have the data property. It will be an array of the model passed. We can represent this like the following:

data: {
  type: 'array',
  items: { $ref: getSchemaPath(model) },
},

Finally, the meta field will be typed as the PaginationMeta.

meta: { $ref: getSchemaPath(PaginationMeta) },

And we're done! this should be our final result:

export const ApiPaginatedResponse = <TModel extends Type>(model: TModel) =>
  applyDecorators(
    ApiExtraModels(PaginationMeta),
    ApiOkResponse({
      schema: {
        title: `PaginatedResponseOf${model.name}`,
        properties: {
          data: {
            type: 'array',
            items: { $ref: getSchemaPath(model) },
          },
          meta: { $ref: getSchemaPath(PaginationMeta) },
        },
      },
    }),
  );

If, in the future, we create response DTOs to represent the returned data (manual response documentation), these too could be used with the decorator.

Commit - Creating decorator for paginated response documentation

We can then use it on the findAll() route in the ProductsController like this:

@ApiPaginatedResponse(Product)

And now, just use it on the remaining findAll() routes.

In the UsersController, you may click on the User decorator in the imports, press F2 and rename it, for instance, to CurrentUser. This will prevent a name collision. You may also rename the User entity to UserEntity, if you prefer.

Commit - Documenting paginated responses

Last updated