Nest.js를 배워보자

✨ Prisma 7과 NestJS, MySQL로 시작하기: 차세대 ORM 가이드

_Blue_Sky_ 2025. 12. 9. 12:07
728x90

 

Mustak Sahariar 원작 | 📝 번역 및 재구성


🚀 Prisma, 이제 7.0 시대를 열다

Prisma는 Node.js와 TypeScript를 위한 강력한 ORM(Object-Relational Mapper)으로, 데이터베이스 모델과 관계를 Prisma 스키마를 통해 정의하고 관리할 수 있도록 돕습니다. 최신 Prisma 7은 더욱 간결하고 빨라졌다고 합니다. (물론 "간결한"지는 좀 더 봐야겠지만요. 😉)

 
 

Prisma 7에서 가장 눈에 띄는 변화들을 먼저 살펴보겠습니다.

🌟 주요 변경 사항

  • Rust 바이너리 제거: Prisma Client가 더 이상 네이티브 Rust 바이너리를 사용하지 않습니다.
  • 완전한 TypeScript 재작성: 순수 JS 런타임에서 실행되도록 완전히 TypeScript로 재작성되었습니다.
  • 클라이언트 출력 경로 변경: 생성된 클라이언트 코드가 더 이상 node_modules 내부에 위치하지 않고, 프로젝트 소스(project source) 내부에 생성됩니다.
  • 핫 리로드 지원: Prisma Client 재생성 시 개발 서버를 재시작할 필요가 없습니다.
  • 새로운 Prisma 설정 파일: prisma.config.ts 파일이 도입되었습니다.
  • 드라이버 어댑터 필수: 모든 데이터베이스 드라이버에 대해 드라이버 어댑터(Driver Adapter)가 필수가 되었습니다.
  • 데이터베이스 불가지론적 클라이언트: 생성된 Prisma 클라이언트가 데이터베이스 인스턴스가 아닌 스키마 구조에만 관심을 갖는, 데이터베이스에 독립적인(database-agnostic) 클라이언트입니다.
  • 새로운 Prisma Studio: npx prisma studio로 접속하는 GUI 툴도 업데이트되었습니다.
  • 매핑된 Enum 지원: mapped enums를 지원합니다.

🛠️ NestJS 프로젝트에 Prisma 7 설정하기

NestJS 프로젝트 설정 부분은 건너뛰고, 곧바로 Prisma 설정에 집중하겠습니다.

1. 의존성 설치

Prisma와 @prisma/client 패키지를 설치합니다.

npm install @prisma/client@7
npm install -D prisma@7

Prisma 7은 기본적으로 환경 변수를 로드하지 않기 때문에, .env 파일을 사용하려면 dotenv를 설치해야 합니다.

npm install dotenv

💡 참고: 작성자는 Node v24.11.1 및 npm v11.6.2 환경에서 진행했습니다.

2. 초기 Prisma 설정

다음 명령어로 초기 Prisma 파일 구조를 생성합니다.

npx prisma init

이 명령어는 prisma 디렉토리와 다음 파일들을 생성합니다.

  • schema.prisma: 데이터베이스 연결 정보와 스키마를 정의합니다.
  •  
  • prisma.config.ts: 새로운 Prisma 설정 파일입니다.
  •  
  • .env: 데이터베이스 자격 증명을 저장하는 파일입니다.

3. 드라이버 어댑터 설치

Prisma 7에서는 이제 드라이버 어댑터가 필수입니다. MySQL을 사용하므로 MariaDB 어댑터를 설치합니다.

npm install @prisma/adapter-mariadb

4. 설정 파일 구성

데이터베이스 URL, 스키마 위치, 마이그레이션 출력 경로 등을 설정합니다. prisma.config.ts 파일은 package.json이 있는 프로젝트 루트에 위치해야 합니다.

  • .env 파일 데이터베이스 연결 정보를 설정합니다.
     
    DATABASE_URL="mysql://test_user:test_password@localhost:3306/test_db"
    

prisma.config.ts 파일 환경 변수를 로드하고 데이터소스 URL을 정의합니다.

⚠️ 오류 해결: DATABASE_URL 누락 오류나 datasource 속성 필수 오류가 발생할 수 있습니다. 위와 같이 prisma.config.ts에서 dotenv/config를 import하고 datasource 속성을 명시하면 해결됩니다.

  •  

     

     

    // 이 파일은 Prisma에 의해 생성되었으며, 다음 패키지가 설치되어 있다고 가정합니다.
    // npm install --save-dev prisma dotenv
    import "dotenv/config";
    import { defineConfig, env } from "prisma/config";
    
    export default defineConfig({
      schema: "prisma/schema.prisma",
      migrations: {
        path: "prisma/migrations",
        seed: 'tsx prisma/seed.ts', // 시드 스크립트 실행 명령어
      },
      datasource: {
        url: process.env.DATABASE_URL!
      },
    });
    

     

5. schema.prisma 정의

클라이언트 출력 경로와 데이터베이스 스키마를 정의합니다. NestJS 환경에 맞춰 moduleFormat을 cjs로 설정하는 것이 중요합니다.

generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
  moduleFormat = "cjs" // NestJS의 CommonJS 런타임 환경과 일치시킵니다.
}

datasource db {
  provider = "mysql"
}

// 예시 모델
model user {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

6. NestJS Prisma Service(prisma.service.ts)

NestJS에서 Prisma Client의 연결 생명주기(Connection Lifecycle)를 관리하는 서비스를 생성합니다. 드라이버 어댑터를 사용하여 PrismaClient를 초기화합니다.

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
// 경로에 맞게 수정하세요.
import { PrismaClient } from '../src/generated/prisma/client'; 
import { PrismaMariaDb } from '@prisma/adapter-mariadb';

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  constructor() {
    // 설치한 드라이버 어댑터를 사용하여 초기화
    const adapter = new PrismaMariaDb(process.env.DATABASE_URL!);
    super({ adapter, log: ['info', 'warn', 'error'] });
  }

  async onModuleInit() {
    try {
      await this.$connect();
      // 데이터베이스 연결 확인을 위한 간단한 쿼리
      await this.$queryRaw`SELECT 1`;
      console.log('✅ Prisma connected to MySQL');
    } catch (error) {
      console.error('❌ Prisma connection error:', error);
      throw error;
    }
  }

  async onModuleDestroy() {
    await this.$disconnect();
    console.log('🔌 Prisma disconnected from MySQL');
  }
}

7. Prisma Module(prisma.module.ts)

PrismaService를 모듈화하고 @Global() 데코레이터를 사용하여 전체 애플리케이션에서 사용할 수 있도록 합니다.

import { Module, Global } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

8. 시드 스크립트(seed.ts)

테스트 데이터를 삽입하기 위한 스크립트입니다. Prisma 7에서는 시드 스크립트에도 드라이버 어댑터가 필요합니다.

import { PrismaClient } from '../src/generated/prisma/client';
import { PrismaMariaDb } from '@prisma/adapter-mariadb';
import 'dotenv/config';

// 시드에도 어댑터를 사용합니다.
const adapter = new PrismaMariaDb(process.env.DATABASE_URL!);
const prisma = new PrismaClient({ adapter });

async function main() {
  console.log('🌱 Starting database seeding...');

  // 데이터 존재 여부 확인
  const userCount = await prisma.user.count();
  
  if (userCount > 0) {
    console.log('⚠️  Database already contains data. Skipping seed.');
    console.log(`   Found ${userCount} users in the database.`);
    return;
  }

  console.log('📭 Database is empty. Starting seed...');

  // 사용자 생성
  const hashedPassword = 'test12345'; // 실제 환경에서는 해싱 필수

  const user1 = await prisma.user.create({
    data: {
      email: 'robot@gmail.com',
      name: 'Mr Robot',
      password: hashedPassword,
    },
  });

  console.log('✅ Created users');
}

main()
  .catch((e) => {
    console.error('❌ Error during seeding:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

9. 메인 모듈 Import(app.module.ts)

메인 AppModule에 PrismaModule을 Import합니다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './modules/auth/auth.module';
import { ConfigModule } from '@nestjs/config';
// 경로에 맞게 수정하세요.
import { PrismaModule } from 'prisma/prisma.module'; 

@Module({
  imports: [AuthModule, PrismaModule, ConfigModule.forRoot()],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

🏃 실행 및 마무리

데이터베이스가 실행 중인지 확인한 후 다음 명령을 순서대로 실행합니다.

# Prisma Client 재생성
npx prisma generate 

# 스키마를 데이터베이스에 반영 (마이그레이션 대신)
npx prisma db push

# 시드 데이터 실행
npx prisma db seed

# NestJS 개발 서버 실행
npm run start:dev

.gitignore 파일에 다음 경로를 추가하여 생성된 클라이언트 코드가 Git에 커밋되는 것을 방지합니다.

/src/generated

마지막으로, 로컬에서 데이터를 시각화할 수 있는 GUI 도구인 Prisma Studio를 실행해 보세요.

 
npx prisma studio

🧐 드라이버 어댑터(Driver Adapters)의 이해

Prisma 7의 핵심 변화인 드라이버 어댑터가 왜 필요한지 알아보겠습니다.

이전 버전의 Prisma Client는 Rust 기반 쿼리 엔진(Query Engine)에 의존했습니다. 이 엔진의 역할은 다음과 같았습니다.

  • Prisma Client 쿼리를 SQL 문으로 변환
  • 내장 드라이버를 사용하여 TCP를 통해 데이터베이스와의 연결 유지

Prisma 7은 Rust 바이너리에 있던 내장 드라이버를 제거하고, JS 런타임에서 실행되는 쿼리 컴파일러(Client Engine)를 도입했습니다. 이제 데이터베이스 연결 및 쿼리 실행을 위해 JavaScript 데이터베이스 드라이버를 사용해야 합니다.

이때, 쿼리 엔진/컴파일러데이터베이스 드라이버 사이에서 번역기 역할을 하는 것이 바로 드라이버 어댑터입니다. MySQL의 경우 @prisma/adapter-mariadb를 사용해야 하는 이유가 여기에 있습니다.

"로그를 신뢰하라(Trust logs)."



❌ Prisma connection error: TypeError: Cannot read properties of undefined (reading 'acquireTimeout')
    at new PoolOptions (/Users/ihongseok/MyProject/prisma-app/node_modules/mariadb/lib/config/pool-options.js:28:32)
    at Object.createPool (/Users/ihongseok/MyProject/prisma-app/node_modules/mariadb/promise.js:44:19)
    at PrismaMariaDbAdapterFactory.connect (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/adapter-mariadb/dist/index.js:443:26)
    at e.connect (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/LocalExecutor.ts:42:58)
    at Dt.#connectExecutor (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:220:34)
    at callback (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:169:35)
    at Object.runInChildSpan (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/tracing/TracingHelper.ts:28:12)
    at ti.runInChildSpan (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/tracing/TracingHelper.ts:53:36)
    at Dt.#ensureStarted (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:164:47)
    at Dt.start (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:356:16)
/Users/ihongseok/MyProject/prisma-app/node_modules/mariadb/lib/config/pool-options.js:28
    this.acquireTimeout = opts.acquireTimeout === undefined ? 10000 : Number(opts.acquireTimeout);
                               ^

TypeError: Cannot read properties of undefined (reading 'acquireTimeout')
    at new PoolOptions (/Users/ihongseok/MyProject/prisma-app/node_modules/mariadb/lib/config/pool-options.js:28:32)
    at Object.createPool (/Users/ihongseok/MyProject/prisma-app/node_modules/mariadb/promise.js:44:19)
    at PrismaMariaDbAdapterFactory.connect (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/adapter-mariadb/dist/index.js:443:26)
    at e.connect (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/LocalExecutor.ts:42:58)
    at Dt.#connectExecutor (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:220:34)
    at callback (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:169:35)
    at Object.runInChildSpan (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/tracing/TracingHelper.ts:28:12)
    at ti.runInChildSpan (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/tracing/TracingHelper.ts:53:36)
    at Dt.#ensureStarted (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:164:47)
    at Dt.start (/Users/ihongseok/MyProject/prisma-app/node_modules/@prisma/client/src/runtime/core/engines/client/ClientEngine.ts:356:16)

Node.js v24.11.1

728x90