Загружаем каталог…
Загружаем каталог…
Node.js(NestJS)를 사용한 4주차 실습 및 미션 코드 3주차에서는 순수 SQL(Raw SQL)을 사용하여 DB와 통신했다면, 이번 4주차는 객체 지향적인 접근을 위해 ORM(TypeORM)과 DTO를 도입 하여 리팩터링하는 과정을 다룹니다. Spring Boot 환경의 JPA/Hibernate에 익숙한 관점에서, Node.js(NestJS)에서는 이를 어떻게 구현하는지 주요 개념과 함께 비교하며 정리했습니다. 1. ORM과 엔티티 (Entity) 매핑 데이터베이스 테이블을 객체로 매핑하는 과정입니다. Spring의 JPA와 NestJS의 TypeORM은 데코레이터(어노테이션)의 이름만 다를 뿐, 패러다임은 매우 유사합니다. Spring Boot (JPA): @Entity , @Id , @GeneratedValue , @ManyToOne , @JoinColumn 등을 사용합니다. 지연 로딩( FetchType.LAZY )을 명시적으로 설정하는 경우가 많습니다. NestJS (TypeORM): @Entity() , @PrimaryGeneratedColumn() , @ManyToOne() , @JoinColumn() 등을 사용합니다. TypeORM은 기본적으로 즉시 로딩(Eager Loading)을 하지 않으며, Repository에서 조회 시 relations 옵션을 주어 연관 데이터를 가져옵니다. [NestJS: Category 및 Book Entity 구현] // src/entities/category.entity.ts import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; import { Book } from './book.entity'; @Entity('category') export class Category { @PrimaryGeneratedColumn({ name: 'category_id' }) categoryId: number; @Column({ length: 50, nullable: false }) name: string; @OneToMany(() => Book, (book) => book.category) books: Book[]; } // src/entities/book.entity.ts import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; import { Category } from './category.entity'; @Entity('book') export class Book { @PrimaryGeneratedColumn({ name: 'book_id' }) bookId: number; @ManyToOne(() => Category, (category) => category.books, { nullable: false }) @JoinColumn({ name: 'category_id' }) category: Category; // 선택 심화 미션: UNIQUE 제약조건 추가 @Column({ length: 100, nullable: false, unique: true }) title: string; @Column({ type: 'text', nullable: true }) description: string; @Column({ name: 'is_available', default: true }) isAvailable: boolean; } 2. DTO와 유효성 검사 (Validation) 클라이언트의 요청을 안전하게 받기 위해 DTO를 사용합니다. Spring Boot: jakarta.validation 패키지의 @NotBlank , @NotNull 을 사용하며, Controller 파라미터에 @Valid 를 붙여 검증합니다. NestJS: class-validator 와 class-transformer 라이브러리를 사용합니다. 전역적으로 ValidationPipe 를 등록해두면 (보통 main.ts 에 app.useGlobalPipes(new ValidationPipe()) 추가) 데코레이터 기반으로 자동 검증이 수행됩니다. [NestJS: DTO 구현] // src/dto/create-book.dto.ts import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; import { Type } from 'class-transformer'; export class CreateBookDto { @IsInt() @Type(() => Number) // JSON의 숫자 타입 변환을 보장 categoryId: number; @IsNotEmpty({ message: '제목은 필수입니다.' }) @MaxLength(100) title: string; @IsOptional() @IsString() description?: string; } // src/dto/book-response.dto.ts import { Book } from '../entities/book.entity'; export class BookResponseDto { bookId: number; title: string; description: string | null; categoryName: string; isAvailable: boolean; // Entity를 DTO로 변환하는 정적 팩토리 메서드 static from(book: Book): BookResponseDto { return { bookId: book.bookId, title: book.title, description: book.description, categoryName: book.category.name, isAvailable: book.isAvailable, }; } } 3. 서비스 로직 (Repository & Service) DB 조작을 담당하는 Repository와 비즈니스 로직을 처리하는 Service 계층입니다. Spring Boot: JpaRepository<T, ID> 인터페이스를 상속받기만 하면 프레임워크가 구현체를 런타임에 생성해 줍니다. NestJS: TypeORM의 Repository<T> 클래스를 @InjectRepository(Entity) 데코레이터를 사용하여 Service에 직접 주입받아 사용합니다. 메서드 이름 기반 쿼리 생성 기능은 없으며, 대신 find , findOne , create , save 등의 내장 메서드에 옵션 객체( FindManyOptions 등)를 전달하여 조작합니다. [NestJS: Service 구현 (선택 미션 포함)] // src/books.service.ts import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Like } from 'typeorm'; import { Book } from './entities/book.entity'; import { Category } from './entities/category.entity'; import { CreateBookDto } from './dto/create-book.dto'; import { BookResponseDto } from './dto/book-response.dto'; @Injectable() export class BooksService { constructor( @InjectRepository(Book) private readonly bookRepository: Repository<Book>, @InjectRepository(Category) private readonly categoryRepository: Repository<Category>, ) {} // 도서 목록 조회 (선택미션: 키워드 검색 적용) async getBooks(keyword?: string): Promise<BookResponseDto[]> { const whereCondition = keyword ? { title: Like(`%${keyword}%`) } : {}; const books = await this.bookRepository.find({ where: whereCondition, relations: ['category'], // N+1 문제를 방지하고 카테고리명을 가져오기 위한 조인 order: { bookId: 'DESC' }, }); return books.map((book) => BookResponseDto.from(book)); } // 신규 도서 등록 (필수 및 선택미션: 예외처리, Unique 제약조건 에러 핸들링) async createBook(dto: CreateBookDto): Promise<BookResponseDto> { const category = await this.categoryRepository.findOne({ where: { categoryId: dto.categoryId }, }); if (!category) { throw new NotFoundException('존재하지 않는 카테고리입니다.'); } // 엔티티 인스턴스 생성 (DB 저장 전) const book = this.bookRepository.create({ category, title: dto.title, description: dto.description, }); try { // DB에 INSERT 실행 const savedBook = await this.bookRepository.save(book); return BookResponseDto.from(savedBook); } catch (error) { // MySQL Unique 제약 조건 에러 코드 (ER_DUP_ENTRY) 처리 if (error.code === 'ER_DUP_ENTRY') { throw new ConflictException('이미 존재하는 도서 제목입니다.'); } throw error; } } } 4. API 컨트롤러 (Controller) HTTP 요청을 처리하고 서비스 계층에 위임하는 역할을 합니다. [NestJS: Controller 구현] // src/books.controller.ts import { Controller, Get, Post, Body, Query, HttpCode, HttpStatus } from '@nestjs/common'; import { BooksService } from './books.service'; import { CreateBookDto } from './dto/create-book.dto'; import { BookResponseDto } from './dto/book-response.dto'; @Controller('books') export class BooksController { constructor(private readonly booksService: BooksService) {} @Get() async getBooks(@Query('keyword') keyword?: string): Promise<BookResponseDto[]> { return this.booksService.getBooks(keyword); } @Post() @HttpCode(HttpStatus.CREATED) // 201 Created 명시 (Spring의 @ResponseStatus 와 동일) async createBook(@Body() createBookDto: CreateBookDto): Promise<BookResponseDto> { return this.booksService.createBook(createBookDto); } } 5. Raw SQL 방식과 비교해 바뀐 점 (Node.js 관점) 타입 안정성과 구조화: 3주차에 Record<string, any> 로 아무 데이터나 쿼리 문자열에 때려 넣던 방식에서 벗어나, CreateBookDto 와 class-validator 를 도입함으로써 클라이언트의 요청이 올바른지 진입점(Controller)에서 원천적으로 차단할 수 있게 되었습니다. 생산성과 가독성 증가: SQL 문자열 안에서 물음표(?) 매핑 순서를 눈으로 맞추거나 구조 분해 할당을 매번 하던 번거로움이 사라졌습니다. TypeORM의 create() 와 save() 메서드만으로 객체 지향적인 데이터 저장이 가능해졌습니다. 데이터 은닉 및 API 계약(Contract) 준수: DB 테이블 스키마가 변경되더라도, API 응답은 BookResponseDto.from() 변환 계층을 통해 나가므로 프론트엔드와의 통신 스펙을 일정하게 유지하고 민감한 데이터 노출을 막을 수 있게 되었습니다.
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
2026-2 UMC 스터디 4주차 Backend WIL. Node.js(NestJS)를 사용한 4주차 실습 및 미션 코드 3주차에서는 순수 SQL(Raw SQL)을 사용하여 DB와 통신했다면, 이번 4주차는 객체 지향적인 접근을 위해 ORM(TypeORM)과 DTO를 도입 하여 리팩터링하는 과정을 다룹니다. Spring Boot 환경의 JPA/Hibernate에 익숙한 관점에서, Node.js(NestJS)에서는 이를 어떻게 구현하는지 주요 개념과 함께 비교하며 정리했습니다. 1. ORM과 엔티티 (Entity) 매핑 데이터베이스 테이블을 객체로 매핑하는 과정입니다. Spring의 JPA와 NestJS의 TypeORM은 데코레이터(어노테이션)의 이름만 다를 뿐, 패러다임은 매우 유사합니다. Spring…
Открыть источник