NestJS + Prisma 7 연결 에러 추적 기록
최근 프로젝트에서 Prisma 7을 도입하며 겪은 DB 연결 이슈와, 이를 해결하며 공부한 Prisma의 아키텍처 변화
1. 문제 상황
구글링을 통해 흔히 알려진 Prisma + PostgreSQL + NestJS 설정을 진행했습니다. Rust 기반의 Query Engine이 직접 연결을 담당하는 전통적인 방식이었죠.
[기존 설정 코드]
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// src/prisma/prisma.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {}
결과: npm run start:dev 실행 시 에러 발생
ERROR [ExceptionHandler] PrismaClientInitializationError: PrismaClient needs to be constructed with a non-empty, valid PrismaClientOptions
분석
PrismaClientInitializationError-> PrismaClient 초기화 실패?PrismaClient` needs to be constructed with a non-empty, valid `PrismaClientOptions->PrismaClientOptions이 필요?
2. super에 db url을 주입해줘야하나?
constructor() {
super({
datasources: { db: { url: process.env.DATABASE_URL } },
});
}
- 결과 (TS 에러):
Object literal may only specify known properties, and 'datasources' does not exist... ts(2353) - 분석
- 객체 리터럴은 알려진 속성만 지정할 수 있으며 PrismaClientOptions에는 datasources가 존재하지 않는다.
- 타입 문제인가?
시도 2: as any로 타입 무시
constructor() {
super({
datasources: process.env.DATABASE_URL,
} as any);
}
- 결과: (런타임에러)
[Nest] 7984 - 2026. 04. 16. PM 4:24:32 ERROR [ExceptionHandler] PrismaClientConstructorValidationError: Unknown property datasources provided to PrismaClient constructor.
- 분석: 생성자유효성검증실패에 알 수 없는 속성을 넣었다고 해서 db url을 직접 넣어줘야되나 싶었고, env는 잘 읽히는지 검증해보자 생각함.
시도 3: as any로 타입 무시 및 환경변수 검증
constructor() {
console.log('DATABASE_URL 확인:', process.env.DATABASE_URL);
super({
datasources: {
db: { url: process.env.DATABASE_URL }
},
} as any);
}
- 결과: (런타임에러)
DATABASE_URL 확인: postgresql://postgres:xxxxx ... [Nest] 33248 - 2026. 04. 16. PM 4:25:51 ERROR [ExceptionHandler] PrismaClientConstructorValidationError: Unknown property datasources provided to PrismaClient constructor.
- 분석
- env는 잘 읽히는데 똑같은 에러.
- 아 그럼 스키마에도 datasource라고 되어있으니깐 단수로 바꿔볼까 생각해서 datasource로 수정해서 다시 실행
4. 여전히 같은 에러. 공식 문서 확인.
해결책: Prisma 7의 새로운 표준 'Driver Adapter'
결국 공식 문서를 확인한 결과, 문제는 문법이 아닌 버전 아키텍처의 변화였습니다. Prisma 7에서는 안정성과 유연성을 위해 Driver Adapter 구조를 사용하는 것이 표준입니다.
[최종 수정된 PrismaService]
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
// 1. pg 라이브러리를 통해 직접 Connection Pool 생성
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
});
// 2. Prisma 전용 어댑터 생성
const adapter = new PrismaPg(pool);
// 3. 어댑터를 PrismaClient에 주입
super({ adapter });
}
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
기존 Prisma 5는 NestJS / Node → Prisma Client → Rust Query Engine → DB 구조로, Prisma 내부의 Rust 엔진이 DB 연결과 쿼리 실행을 모두 담당했다.
하지만 서버리스 환경 대응, 배포 복잡성 제거, 그리고 다양한 런타임(Edge 등) 지원을 위해 Prisma 7에서는 Driver Adapter 구조로 변경되었다.
현재는 NestJS / Node → Prisma Client → Driver Adapter → pg(Node driver) → DB 구조이며, Prisma가 직접 DB에 연결하지 않고 driver를 통해 연결된다.
이로 인해 DB 연결 및 connection pool 관리는 Prisma가 아닌 driver(pg)가 담당하게 되었고, 애플리케이션에서 pool 설정(max 등)을 직접 제어할 수 있게 되었다.
또한 디버깅과 확장성이 향상되었다.
5. 깊게 들여다보기: 무엇이 변했는가?
앱 시작 시점에 PrismaService의 constructor에서 pg Pool이 생성되고, Adapter와 PrismaClient가 초기화된다. 이 시점에서는 DB connection이 즉시 생성되기보다는, 필요 시 사용할 수 있도록 준비된 상태다.
onModuleInit에서 $connect()를 호출하면 Prisma와 Adapter를 통해 DB 연결을 사용할 수 있는 상태가 되며, 실제 connection은 이후 쿼리 실행 시점에 생성 및 사용된다.
요청이 발생하면 Controller → Service → PrismaClient → Adapter → pg Pool → DB 순으로 흐르며, pg Pool은 idle connection을 재사용하거나 필요 시 새 connection을 생성한다.
요청이 끝나면 connection은 pool로 반환되어 idle 상태가 되고, 이후 요청에서 재사용된다.
이 구조에서 connection은 매번 새로 생성되는 것이 아니라 pool을 통해 재사용되는 것이 핵심이다.
Prisma 5의 경우, Rust Query Engine 내부에서 connection pool을 관리했으며, 서버리스 환경에서 인스턴스가 여러 개 생성될 경우 각 인스턴스마다 PrismaClient와 Rust 엔진이 실행되면서 connection이 증가하는 문제가 발생할 수 있었다.
이로 인해 DB의 max connection을 초과하여 “too many connections” 에러가 발생할 수 있었고, 또한 배포 시 OS별 바이너리가 필요하며 내부 로직이 Rust 엔진에 존재하여 디버깅이 어려운 문제가 있었다.
6. ai로 정리
아키텍처 비교: Prisma 5 vs Prisma 7
- Prisma 5:
NestJS → Prisma Client → Rust Query Engine → DB- Prisma 내부의 Rust 엔진이 DB 연결과 쿼리 실행을 모두 독점했습니다.
- Prisma 7:
NestJS → Prisma Client → Driver Adapter → pg(Node driver) → DB- Prisma가 직접 연결하지 않고, 외부 드라이버(
pg)를 어댑터로 거쳐 연결합니다.
- Prisma가 직접 연결하지 않고, 외부 드라이버(
왜 이렇게 바뀌었을까?
- 커넥션 풀(Connection Pool) 제어권: 이제 Prisma가 아닌
pg드라이버가 풀 관리를 담당합니다. 덕분에 앱 수준에서max연결 수 등을 직접 제어할 수 있습니다. - 서버리스(Serverless) 대응: 인스턴스가 여러 개 생성되는 환경에서 각 Rust 엔진이 커넥션을 과점하던 문제를 해결, "too many connections" 에러를 효과적으로 방지합니다.
- 배포 및 디버깅 용량: OS별 Rust 바이너리가 필요 없어 배포가 단순해지고, 내부 로직이 JS 드라이버를 거치므로 디버깅이 훨씬 쉬워졌습니다.
런타임 동작 흐름 (Data Flow)
이 구조에서 데이터가 어떻게 흐르는지 정리해 보았습니다.
- 초기화:
constructor실행 시pg Pool이 생성되고Adapter가 준비됩니다. (실제 연결은 쿼리 시점에 생성) - 모듈 시작:
onModuleInit의$connect()호출로 연결 가능 상태가 됩니다. - 요청 발생:
Controller→Service→PrismaClient→Adapter→pg Pool→DB순으로 흐릅니다. - 효율성:
pg Pool은 사용이 끝난 커넥션을 파괴하지 않고idle상태로 유지했다가 다음 요청 시 재사용합니다.