Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NestJs: How to have Body input shape different from entity's DTO?

I have DTOs for my Photo and Tag objects that look like this:

export class PhotoDto {
    readonly title: string
    readonly file: string
    readonly tags: TagDto[]
}

export class TagDto {
    readonly name: string
}

I use the PhotoDto in my photo.service.ts and eventually in the photo.controller.ts for the creation of Photo:

// In photo.service.ts
async create(createPhotoDto: PhotoDto): Promise<PhotoEntity> {
   // ...
   return await this.photoRepo.create(createPhotoDto)
}

// In photo.controller.ts
@Post()
async create(@Body() createPhotoDto: PhotoDto): Promise<PhotoEntity> {
   // ...
}

However, the input in the Body of the API is expected to have this structure:

{
   "title": "Photo Title",
   "file": "/some/path/file.jpg",
   "tags": [
      {
         "name": "holiday"
      },
      {
         "name": "memories"
      }
   ]
}

How can I change the input shape of the Body to accept this structure instead?

{
   "title": "Photo Title",
   "file": "/some/path/file.jpg",
   "tags": ["holiday", "memories"]
}

I have tried creating 2 different DTOs, a CreatePhotoDto and an InputPhotoDto, one for the desired input shape in the controller and one for use with the service and entity, but this ends up very messy because there is a lot of work with converting between the 2 DTOs.

What is the correct way to have a different input shape from the Body of a Post request and then have it turned into the DTO required for use by the entity?

like image 887
Carven Avatar asked Apr 01 '19 04:04

Carven


Video Answer


1 Answers

You can use the auto-transform of the ValidationPipe():

1) Add the ValidationPipe to your controller:

@UsePipes(new ValidationPipe({ transform: true }))
@Post()
async create(@Body() createPhotoDto: PhotoDto): Promise<PhotoEntity> {
   // ...
}

2) Add a @Transform to your PhotoDto:

// Transforms string[] to TagDto[]
const transformTags = tags => {
  if (Array.isArray(tags)) {
    return tags.map(tag => ({name: tag}))
  } else {
    return tags;
  }
}


import { Transform } from 'class-transformer';
export class PhotoDto {
    readonly title: string
    readonly file: string
    @Transform(transformTags, {toClassOnly: true})
    readonly tags: TagDto[]
}
like image 165
Kim Kern Avatar answered Oct 09 '22 18:10

Kim Kern