Home > Mobile >  How to combine multiple DTO classes in NestJS?
How to combine multiple DTO classes in NestJS?

Time:09-17

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:

  1. Speed of development - we have a lot of entities that can be ordered and/or paginated

  2. 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)

  3. 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
}
  • Related