Hi everyone,
I'm recently run into the issue with Swagger response models and different response formats that @nestjsx/crud produces depending on the pagination options. By default, getManyBase returns data using this schema: Entity[], when offset and per_page are set: GetManyDefaultResponse<T>.
But for Swagger metadata only the 1st format is used. This might cause issues when API client is generated from metadata using tools like swagger-api/swagger-codegen.
As far as I know, it's possible to set an alternative response model in OpenAPI 3 but this feature isn't supported by @nestjs/swagger yet: https://github.com/nestjs/swagger/issues/43.
That would be nice to have all responses in the same format: GetManyDefaultResponse<T> or Entity[] (with meta in HTTP headers, for example X-Total-Count).
As a workaround, I'm overriding response metadata using ApiOkResponse decorator + an interceptor to transform responses.
You can change the pagination wrapper by orverride this method https://github.com/nestjsx/crud/blob/0a3684eb57a22b1c06866ba7141fbc60b1d0c1d2/packages/crud/src/services/crud-service.abstract.ts#L28
You can change the pagination wrapper by orverride this method
Yeah, I've used a similar way but with the interceptor which can be easily shared across different controllers without overriding services method:
@Injectable()
export class GetManyResponseInterceptor implements NestInterceptor {
public intercept(_context: ExecutionContext, next: CallHandler): Observable<GetManyResponse<any>> {
return next.handle().pipe(
map(data => {
// from my perspective usage of `X-Total-Count` header is the better design
// but `GetManyDefaultResponse<T>` format is simpler to use in my case
if (Array.isArray(data)) {
return { data };
}
return data;
}),
);
}
}
But, unfortunately, that doesn't solve the issue completely. By default, @nestjsx/crud returns data in different formats (depending on pagination) and Swagger metadata doesn't reflect this.
So my concern is that different response formats may result in parsing issues on the API consumer side and we can't rely on Swagger metadata to avoid these issues since Swagger meta doesn't reflect different response formats (and can't be used to generate a statically typed client using swagger-api/swagger-codegen or similar).
Same issue here. Sadly with your Interceptor swagger still reflects T[] as response type which makes it not usable with a generated consumer.
Any workaround on this?
@DaVarga Another part of the workaround (in addition to the interceptor) is Swagger meta overriding (possible after #113).
Define generic class for the response model (similar to GetManyDefaultResponse interface but with optional props):
class GetManyResponse<Entity> {
@ApiModelProperty({ type: Object, isArray: true }) // will be overwritten
public data: Entity[];
@ApiModelPropertyOptional()
public count?: number;
// ...
Define a factory function:
type Entity = Function;
function getManyResponseFor(type: Entity): typeof GetManyResponse {
class GetManyResponseForEntity<Entity> extends GetManyResponse<Entity> {
@ApiModelProperty({ type, isArray: true })
public data: Entity[];
}
Object.defineProperty(GetManyResponseForEntity, 'name', {
value: `GetManyResponseFor${type.name}`,
});
return GetManyResponseForEntity;
}
ApiOkResponse decorator to options:typescript
@Crud({
model: {
type: Company,
},
routes: {
getManyBase: {
interceptors: [GetManyResponseInterceptor],
decorators: [
ApiOkResponse({ type: getManyResponseFor(Company) }),
// ...
Or as an alternative, simply pass total count as X-Total-Count header in an interceptor. Then no need to redefine Swagger meta.
To chime in on this story, I'd love to see just one output format in the getMany calls. So it always shows the { data: [], count, skip, offset } etc.
I found it quite confusing that the shape of the result changes based on the params you pass in.
Ok, guys. I think one response format would be the best way. Will add it to the next release.
With 4.3.0 release:
getMany response correctly
Most helpful comment
Ok, guys. I think one response format would be the best way. Will add it to the next release.