Assuming I have endpoints such as:
GET /api/invoices?page=1&limit=10&id=2022001
- Allows pagination ('page', 'limit')
- Allows custom filtering ('id')
GET /api/users?order_by=first_name&order_direction=asc
- Allows ordering ('order_by', 'order_direction')
GET /api/products?page=1&limit=10&order_by=created_at&order_direction=asc&description=lorem
- Allows pagination ('page', 'limit')
- Allows ordering ('order_by', 'order_direction')
- Allows custom filtering ('description')
Is there a way to create base DTO classes that I could reuse in implementation of the individual endpoints?
In ideal world I would like to have common base DTO classes:
import { IsInt, IsEnum, IsString } from 'class-validator'
class PaginationDto {
@IsInt()
limit: number
@IsInt()
page: number
}
class OrderDto {
@IsEnum(['asc', 'desc'])
order_direction: 'asc' | 'desc'
/**
* Even better would be custom enum because each entity can
* be ordered by different keys
*/
@IsString()
order_by: string
}
And endpoint specific DTO classes that would extend the base classes:
import { IsInt, IsString } from 'class-validator'
import { PaginationDto, OrderDto } from '@shared/base.dto'
/**
* This works fine
*/
class GetInvoicesQueryDto extends PaginationDto {
@IsInt()
id: number
}
/**
* This works but I want the validation to fail if 'order_by'
* is not in list of allowed keys (e.g. 'first_name' and 'created_at')
*/
class GetUsersQueryDto extends OrderDto {}
/**
* This doesn't work because class can only extend a single class
*
* I can imagine extending even more than 2 classes in the future
*/
class GetProductsQueryDto extends PaginationDto, OrderDto {
@IsString()
description: string
}
We also use OpenAPI so the implementation should support it as well.
I looked into TS Mixins, but find the usage a bit awkward and would prefer not using it.
My main motivation is:
Speed of development - we have a lot of entities that can be ordered and/or paginated
Flexibility - assuming we would need to change accepted values of 'order_direction' from lowercase 'asc' / 'desc' to uppercase 'ASC' / 'DESC' we would have to do a global search for each orderable DTO and update it accordingly (our use case isn't only 'order_direction' but also custom properties so changes over time are likely to happen)
Consistency - it would prevent mismatch such as 'order_direction' and 'orderDirection'
Thanks!
CodePudding user response:
Rather than using class extension, you can use the mapped-types
fomr @nestjs/swagger
and do something like
export class GetProductsQueryDto extends IntersectionType(PaginationDto, OrderDto) {
@IsString()
description: string
}