DTO ( Data Transfer Object ) with NestJS

박서현·2024년 2월 22일
0

NestJS

목록 보기
1/1

얼마 전, 회사 팀원에게 DTO를 왜 굳이 사용해야하는지?에 대한 질문을 받았다.

그래서 이번 포스팅에서는 DTO를 왜 사용해야하는지에 대해 알아보고자 한다.

먼저, DTO는 Data Transfer Object의 약자로, 데이터 전송 객체라고 한다.

DTO는 데이터베이스에서 데이터를 얻어 서비스나 비즈니스 로직으로 전달할 때 사용하는 객체이다.

아마, 앞서 받은 질문은, 엔터티를 그대로 사용하면 되지 왜 DTO를 사용해야하나? 라는 의문이었을 것이다.



1. 보안상의 이유 - 엔터티를 그대로 사용하면, 엔터티의 모든 데이터가 노출된다.

아래 예시는, User 에 관한 엔터티 클래스이다.

    // user.entity.ts
    import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

    @Entity()
    export class User {
        @PrimaryGeneratedColumn()
        id: number;

        @Column()
        loginType: string;

        @Column()
        email: string;

        @Column()
        password: string;

        @Column()
        socialId: string;

        @Column()
        socialAccessToken: string;

        @Column()
        name: string;

        @Column()
        nickname: string;

        @Column()
        profileImage: string;
    }

위와 같은 엔터티를 그대로 사용하면, 모든 데이터가 노출되기 때문에 보안상의 이유로 DTO를 사용한다.

아래 예시는, 특정 User 를 조회하는 API 이다.

    // user.controller.ts
    import { Controller, Get, Param } from '@nestjs/common';
    import { UserService } from './user.service';

    @Controller('user')
    class UserController {
        constructor(private readonly userService: UserService) {}

        @Get(':id')
        async getUser(@Param('id') id: number) {
            return await this.userService.getUser(id);
        }
    }

    // user.service.ts
    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    import { User } from './user.entity';

    @Injectable()
    class UserService {
        constructor(
            @InjectRepository(User)
            private readonly userRepository: Repository<User>,
        ) {}

        async getUser(id: number) {
            return await this.userRepository.findOne(id);
        }
    }

이 API 를 호출하면, 아래와 같은 응답이 온다.

    {
        "id": 1,
        "loginType": "email",
        "email": "test@test.com",
        "password": "test",
        "socialId": null,
        "socialAccessToken": null,
        "name": "test",
        "nickname": "test",
        "profileImage": null
    }

모든 데이터가 노출되기 때문에 보안상의 이유로 DTO를 사용한다.

2. API 의 안정성 - 엔터티를 그대로 사용하면, 엔터티의 데이터가 변경되면 API 응답 데이터도 변경된다.

엔터티를 그대로 사용하면, 엔터티의 데이터가 변경되면 API 응답 데이터도 변경된다.

기존 User 테이블에서, lastLoginAt 컬럼을 추가하였다고 가정하자.

    @Column()
    lastLoginAt: Date;

이 경우, API 응답 데이터도 변경되어야 한다.

백엔드에서는, API 응답 데이터를 변경하는 것은 큰 문제가 되지 않지만, 프론트엔드에서는 큰 문제가 된다.

프론트엔드에서는, API 응답 데이터가 변경되면, API 응답 데이터를 사용하는 모든 곳에서 변경된 데이터를 사용해야 한다.

이러한 문제를 해결하기 위해 DTO를 사용한다.

아래 예시는, 본인이 아닌, 다른 User 를 조회하는 API 이다.

    // 엔터티를 그대로 사용하는 경우
    {
        "id": 1,
        "loginType": "email",
        "email": "test@test.com",
        "password": "test",
        "socialId": null,
        "socialAccessToken": null,
        "name": "test",
        "nickname": "test",
        "profileImage": null,
        "lastLoginAt": "2021-10-24T12:00:00.000Z" // 추가된 컬럼
    }

    // DTO를 사용하는 경우
    {
        "id": 1,
        "email": "test@test.com",
        "name": "test",
        "nickname": "test",
        "profileImage": null // 필요한 데이터만 응답받아서, 추가된 컬럼이 응답되지 않는다.
    }

위와 같이, 필요한 데이터만 응답받아서, 추가된 컬럼이 응답되지 않는다.

이러한 이유로, 엔터티를 그대로 사용하지 않고, DTO를 사용한다.


3. API 응답 속도 - 불필요한 데이터가 포함되어, API 응답 데이터의 크기가 커진다.

엔터티를 그대로 사용하면, 불필요한 데이터가 포함되어, API 응답 데이터의 크기가 커진다.

예를 들어, User 테이블과 연관된 Post 테이블이 있다고 가정하자.

이 경우, 엔터티를 그대로 사용하면, User 테이블과 연관된 Post 테이블의 데이터도 응답된다.

하지만, User 테이블과 연관된 Post 테이블의 데이터는 필요하지 않다.

profile
불편함을 당연시 여기지 않고, 더 나은 프로세스를 위해, 더 나은 사용자 경험을 위해, 공부하고, 메모하고, 개발하는 박서현 입니다.

0개의 댓글