본문 바로가기

개발 & IT/백엔드

Prisma로 Next.js 블로그 만들기: 데이터베이스 설정부터 마이그레이션까지

PostgreSQL과 Next.js로 블로그를 만들면서 Prisma ORM을 처음 도입하게 되었습니다. MySQL을 주로 사용하던 입장에서 새로운 도구를 배우는 과정에서 겪은 시행착오와 해결 방법을 공유합니다.

Prisma가 뭔가요?

Prisma는 Node.js와 TypeScript를 위한 차세대 ORM(Object-Relational Mapping) 도구입니다. 기존의 SQL 쿼리를 직접 작성하는 방식에서 벗어나, 타입 안전성을 보장하면서도 직관적인 방식으로 데이터베이스를 다룰 수 있게 해줍니다.

왜 Prisma를 선택했나?

기존에 Laravel의 Eloquent ORM과 비슷하지만, TypeScript 지원이 훨씬 뛰어납니다:

  • 타입 안전성: 데이터베이스 스키마에서 자동으로 TypeScript 타입 생성
  • 직관적인 API: 복잡한 쿼리도 읽기 쉽게 작성
  • 마이그레이션 자동화: 스키마 변경 이력 관리
  • Prisma Studio: GUI로 데이터 확인 가능

프로젝트 설정

1. Prisma 설치

npm install prisma @prisma/client
npx prisma init

설치하면 프로젝트에 두 가지가 생성됩니다:

  • prisma/schema.prisma: 데이터베이스 스키마 정의
  • .env: 데이터베이스 연결 정보

2. 환경변수 로딩 설정

최신 Prisma 버전에서는 추가 설정이 필요할 수 있습니다:

npm install dotenv

프로젝트 루트에 prisma.config.ts 파일 생성:

import "dotenv/config";

⚠️ URL 인코딩 주의: 비밀번호에 특수문자가 있으면 인코딩이 필요합니다!

# → %23
@ → %40
: → %3A

예시:

# 비밀번호: pass#word
DATABASE_URL="postgresql://user:pass%23word@host:5432/db"

스키마 작성하기

prisma/schema.prisma 파일에서 데이터베이스 구조를 정의합니다. 간단한 블로그 예시:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String   @db.Text
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

스키마 문법 이해하기

기본 어노테이션

  • @id: 기본키(Primary Key)
  • @unique: 중복 불가
  • @default(): 기본값 설정
  • @updatedAt: 수정 시 자동으로 현재 시간으로 업데이트
  • ?: Optional, null 허용

관계(Relation) 설정

1:N 관계 (한 사용자가 여러 글 작성):

model User {
  posts Post[]  // 배열 = 여러 개
}

model Post {
  author   User   @relation(fields: [authorId], references: [id])
  authorId String // 외래키
}

양방향 관계를 명시해야 합니다. User.posts와 Post.author 둘 다 필요!

M:N 관계 (글 ↔ 태그):

model Post {
  tags Tag[]
}

model Tag {
  posts Post[]
}

Prisma가 자동으로 중간 테이블(_PostToTag)을 생성합니다.

Supabase 연결하기

무료 PostgreSQL 호스팅으로 Supabase를 사용했습니다.

Connection String 종류

Supabase는 두 가지 연결 방식을 제공합니다:

  1. Session Pooler (포트 5432)
    • 세션 단위 연결 유지
    • Migration 가능 ✅
    • 일반적인 앱 실행에 적합
  2. Transaction Pooler (포트 6543)
    • 트랜잭션 단위로 연결 교체
    • 더 많은 동시 접속 처리
    • Migration에는 부적합

권장 설정

Session Pooler만 사용하는 경우 (간단):

DATABASE_URL="postgresql://postgres.xxx:password@aws-0-ap-northeast-2.pooler.supabase.com:5432/postgres"

둘 다 사용하는 경우 (최적화):

# Transaction Pooler (앱 실행용)
DATABASE_URL="postgresql://postgres.xxx:password@aws-0-ap-northeast-2.pooler.supabase.com:6543/postgres?pgbouncer=true"

# Direct Connection (Migration용)
DIRECT_URL="postgresql://postgres:password@db.xxx.supabase.co:5432/postgres"

schema.prisma에 추가:

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")  // 추가
}

블로그 규모라면 Session Pooler만으로도 충분합니다!

마이그레이션 실행

스키마 작성이 끝났으면 실제 데이터베이스에 테이블을 생성합니다:

npx prisma migrate dev --name init

--name init은 마이그레이션 이름입니다. Git commit 메시지처럼 의미있게 작성하세요:

npx prisma migrate dev --name add_tags
npx prisma migrate dev --name add_view_count

마이그레이션 파일

prisma/migrations/ 폴더에 SQL 파일이 자동 생성됩니다:

prisma/migrations/
└── 20250426123456_init/
    └── migration.sql

이 파일들은 Git으로 관리되며, 팀원들과 데이터베이스 변경 이력을 공유할 수 있습니다.

Prisma Client 사용하기

마이그레이션 후 자동으로 생성된 Prisma Client로 데이터베이스 작업을 수행합니다.

Client 초기화

lib/prisma.ts 파일 생성:

import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

기본 CRUD 작업

Create (생성):

const post = await prisma.post.create({
  data: {
    title: '첫 번째 글',
    content: '안녕하세요!',
    slug: 'first-post',
    author: {
      connect: { id: userId }
    }
  }
})

Read (조회):

// 전체 조회
const posts = await prisma.post.findMany({
  where: { published: true },
  include: {
    author: true,
    tags: true
  },
  orderBy: { createdAt: 'desc' }
})

// 단일 조회
const post = await prisma.post.findUnique({
  where: { slug: 'first-post' }
})

Update (수정):

const post = await prisma.post.update({
  where: { id: postId },
  data: { 
    title: '수정된 제목',
    published: true
  }
})

Delete (삭제):

await prisma.post.delete({
  where: { id: postId }
})

유용한 도구: Prisma Studio

GUI로 데이터를 확인하고 수정할 수 있는 도구입니다:

npx prisma studio

http://localhost:5555에서 테이블 데이터를 시각적으로 관리할 수 있습니다.

마치며

Prisma의 강점

  • TypeScript 타입 자동 생성으로 개발 경험 향상
  • 마이그레이션 자동화로 스키마 변경 추적 용이
  • 직관적인 API로 복잡한 관계도 쉽게 표현
  • Prisma Studio로 빠른 데이터 확인 가능

주의할 점

  • 특수문자 비밀번호는 URL 인코딩 필수
  • Connection Pooler 설정 확인 (Session vs Transaction)
  • 최신 버전에서는 prisma.config.ts 추가 필요
  • 양방향 관계는 반드시 양쪽 모델에 명시

MySQL을 주로 사용하던 입장에서 PostgreSQL + Prisma 조합은 새로운 경험이었습니다. 특히 TypeScript와의 통합이 타입 안전성을 크게 높여주어 실수를 줄일 수 있었습니다.


참고 자료:

반응형