1
votes

I am new to the NestJs and i am stuck on problem with returning response entity from my backend.

The problem is that even when i return Promise-PostDTO-. It's still returns Post entity from database. With all the properties and nestjs ignore return type.

Do you know where can be problem? I though that automatic conversion works in both ways. Maybe problem is in returning Promise and not PostDTO.

I hope that someone can help me. Thanks

The code below.

post.entity.ts

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
} from 'typeorm';

@Entity()
export default class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Column()
  perex: string;

  @Column()
  content: string;

  @CreateDateColumn()
  createdAt: Date;
}

post.dto.ts

import { IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class PostDTO {
  @Expose()
  @IsString()
  readonly title: string;
  @Expose()
  @IsString()
  readonly perex: string;
  @Expose()
  @IsString()
  readonly content: string;
}

post.repository.ts

import { Repository, EntityRepository } from 'typeorm';
import Post from './post.entity';
@EntityRepository(Post)
export class PostRepository extends Repository<Post> {}

post.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import Post from './post.entity';
import { PostRepository } from './post.repository';
import { PostDTO, PostRO } from './post.dto';

@Injectable()
export class PostService {
  constructor(
    @InjectRepository(Post) private readonly postRepository: PostRepository,
  ) {}
  async createPost(post: PostDTO): Promise<PostDTO> {
    return await this.postRepository.save(post);
  }
}

post.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  UseInterceptors,
  ClassSerializerInterceptor,
} from '@nestjs/common';
import { PostService } from './post.service';
import { PostDTO } from './post.dto';
@Controller('post')
@UseInterceptors(ClassSerializerInterceptor)
export class PostController {
  constructor(private readonly postService: PostService) {}

  @Post()
  async createPost(@Body() post: PostDTO): Promise<PostDTO> {
    return await this.postService.createPost(post);
  }
}

Based on the Jay McDoniel's answer i refactor my code to this.

I used custom nestjs interceptor that converts classes.

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToClass } from 'class-transformer';

interface ClassType<T> {
  new (): T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<Partial<T>, T> {
  constructor(private readonly classType: ClassType<T>) {}

  intercept(
    context: ExecutionContext,
    call$: CallHandler<Partial<T>>,
  ): Observable<T> {
    return call$.handle().pipe(map(data => plainToClass(this.classType, data)));
  }
}

I also refactor my DTO with class-transformer package.

import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class ArticleDTO {
  @Expose()
  @ApiProperty({ required: true })
  @IsString()
  readonly title: string;
  @Expose()
  @ApiProperty({ required: true })
  @IsString()
  readonly perex: string;
  @Expose()
  @ApiProperty({ required: true })
  @IsString()
  readonly content: string;
}

Usage of the new iterceptor in my controller.

import { Controller, Get, Post, Body, UseInterceptors } from '@nestjs/common';
import { ArticleService } from './article.service';
import { ArticleDTO } from './dto/article.dto';
import { TransformInterceptor } from 'src/transform.interceptor';
@Controller('articles')
@UseInterceptors(new TransformInterceptor(ArticleDTO))
export class ArticleController {
  constructor(private readonly articleService: ArticleService) {}
  @Get()
  async getAllPosts(): Promise<ArticleDTO[]> {
    return await this.articleService.getAllPosts();
  }

  @Post()
  async createPost(@Body() post: ArticleDTO): Promise<ArticleDTO> {
    return await this.articleService.createPost(post);
  }
}

After these code updates it works as expected, so it returns DTO instead of DB Entity.

1

1 Answers

2
votes

Typescript is a very useful dev tool to work with, but there are times where things fall through gaps. In this case, you're telling Typescript that PostController#createPost will return a PostDTO, which as mentioned above has properties of title, perex, and content. Now, when you run the query for this.postRepository.save() that method returns a Post object, which has id, title, perex, content, and createdAt. Because a Post object has the same fields (and a few more) as PostDTO, Typescript does not tell you you aren't returning the same object. Similarly, these types disappear at runtime, because Typescript is a development tool, not a runtime (unless you're using Deno), so all that the JavaScript sees is return await this.postRepository.save() and that's what's going to be sent back to the caller.

How you can get around this is

  1. Use a local variable and remove the id and createdAt fields
  2. Use a serializer like class-transformer or MarshalTS to transform the result to the desired object.