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

匆匆过客 提交于 2020-08-09 23:44:39

问题


I've a DTO for my Photo and Tag object that looks 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 different input shape from the Body of a Post request and then have it turned into the DTO required for use by the entity?


回答1:


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[]
}



回答2:


You can create a nest custom decorator to convert input data to your DTO object.

export const ConvertToCreateCatDto = createRouteParamDecorator((data, req): CreateCatDto => { // `createParamDecorator` for nest old version
    if (req.body.tags.every(value => typeof value === "string")) { // if input tags is a string[]
        req.body.tags = (req.body.tags as string[]).map<TagDto>((tag) => {
            return { // convert to TagDto
                name: tag + ""
            }
        });
    }
    let result = new CreateCatDto(req.body);
    // TODO: validate `result` object
    return result;
});

add constructor to CreateCatDto

export class CreateCatDto {
    readonly title: string;
    readonly file: number;
    readonly tags: TagDto[];

    constructor(obj: any) {
        this.title = obj.title;
        this.file = obj.file;
        this.tags = obj.tags;
    }
}

Finally, use @ConvertToCreateCatDto instead of @Body in you controller.

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



回答3:


Update DTO to

export class PhotoDto { readonly title: string readonly file: string readonly tags: Array<string> }

It will change the API structure to

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

currently your tags property is an array of object of type TagDto, change tags property to just array of string.



来源:https://stackoverflow.com/questions/55448050/nestjs-how-to-have-body-input-shape-different-from-entitys-dto

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!