“풀스택 개발의 핵심!” - Prisma로 데이터베이스를 쉽고 안전하게!
🎯 이 글에서 배울 내용
- Prisma ORM 설정
- 스키마 정의와 마이그레이션
- CRUD 작업
- 관계형 데이터 모델링
- 트랜잭션과 최적화
예상 소요 시간: 50분
📋 시작하기 전에: PostgreSQL 설치
Prisma를 사용하려면 먼저 데이터베이스가 필요합니다. 이 튜토리얼에서는 PostgreSQL을 사용합니다.
PostgreSQL이란?
PostgreSQL은 가장 인기 있는 오픈소스 관계형 데이터베이스입니다.
1
2
3
4
5
| 데이터베이스 = 데이터를 저장하는 창고 🏪
- 사용자 정보 저장
- 게시물 저장
- 댓글 저장
- 관계 설정 (사용자 → 게시물)
|
설치 방법
Windows 사용자
방법 1: 공식 설치 프로그램 (추천)
- PostgreSQL 다운로드
- 설치 파일 실행
- 설치 과정에서 비밀번호 설정 (잊지 마세요!)
- 포트: 5432 (기본값 유지)
- 설치 완료!
설치 확인:
1
2
3
| # 명령 프롬프트에서
psql --version
# PostgreSQL 16.x 출력되면 성공!
|
방법 2: Docker 사용 (개발자 추천)
1
| docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres
|
macOS 사용자
방법 1: Homebrew (추천)
1
2
3
4
5
6
7
8
| # PostgreSQL 설치
brew install postgresql@16
# 서비스 시작
brew services start postgresql@16
# 설치 확인
psql --version
|
방법 2: Postgres.app (GUI 앱)
- Postgres.app 다운로드
- Applications 폴더로 이동
- 앱 실행
- Initialize 버튼 클릭
방법 3: Docker
1
| docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres
|
Linux (Ubuntu/Debian) 사용자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # PostgreSQL 설치
sudo apt update
sudo apt install postgresql postgresql-contrib
# 서비스 시작
sudo systemctl start postgresql
sudo systemctl enable postgresql
# 설치 확인
psql --version
# PostgreSQL 사용자로 전환
sudo -i -u postgres
# psql 접속
psql
|
데이터베이스 생성
PostgreSQL 설치 후 개발용 데이터베이스를 만들어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
| # psql 접속 (비밀번호 입력 필요)
psql -U postgres
# 데이터베이스 생성
CREATE DATABASE myapp_dev;
# 데이터베이스 목록 확인
\l
# 종료
\q
|
💡 Tip: Docker를 사용하면 설치 없이 바로 시작할 수 있습니다!
1
2
3
4
5
6
7
8
9
| # Docker로 PostgreSQL 실행
docker run --name my-postgres \
-e POSTGRES_PASSWORD=mysecretpassword \
-e POSTGRES_DB=myapp_dev \
-p 5432:5432 \
-d postgres
# 접속 확인
docker exec -it my-postgres psql -U postgres -d myapp_dev
|
연결 정보 확인
설치가 완료되면 다음 정보를 확인하세요:
1
2
3
4
5
| 호스트: localhost
포트: 5432
사용자: postgres
비밀번호: (설치 시 설정한 비밀번호)
데이터베이스: myapp_dev
|
데이터베이스 URL 형식:
1
2
3
4
| postgresql://사용자:비밀번호@호스트:포트/데이터베이스명
예시:
postgresql://postgres:mysecretpassword@localhost:5432/myapp_dev
|
이 URL을 .env 파일에 DATABASE_URL로 저장할 것입니다!
🎯 빠른 시작 (Docker 추천)
Docker가 설치되어 있다면 가장 빠릅니다:
1
2
3
4
5
6
7
| # 1. PostgreSQL 실행 (한 줄로!)
docker run --name nextjs-db -e POSTGRES_PASSWORD=password -e POSTGRES_DB=nextjs_dev -p 5432:5432 -d postgres
# 2. .env 파일에 추가
echo 'DATABASE_URL="postgresql://postgres:password@localhost:5432/nextjs_dev"' > .env
# 3. 완료! 이제 Prisma 사용 가능 🎉
|
🗃️ Prisma 설정
1. 설치
1
2
| npm install prisma @prisma/client
npx prisma init
|
2. 환경 변수
# .env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
3. 스키마 정의
// 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())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
tags Tag[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Tag {
id String @id @default(cuid())
name String @unique
posts Post[]
}
model Comment {
id String @id @default(cuid())
content String
post Post @relation(fields: [postId], references: [id])
postId String
author String
createdAt DateTime @default(now())
}
4. 마이그레이션
1
2
3
4
5
| # 마이그레이션 생성
npx prisma migrate dev --name init
# Prisma Client 생성
npx prisma generate
|
5. Prisma Client 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: ['query', 'error', 'warn'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
✍️ CRUD 작업
Create (생성)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // app/actions/posts.ts
'use server';
import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const authorId = formData.get('authorId') as string;
const post = await prisma.post.create({
data: {
title,
content,
author: {
connect: { id: authorId }
}
},
include: {
author: true
}
});
revalidatePath('/blog');
return post;
}
|
Read (조회)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // app/blog/page.tsx
import { prisma } from '@/lib/prisma';
export default async function BlogPage() {
const posts = await prisma.post.findMany({
where: {
published: true
},
include: {
author: {
select: {
name: true,
email: true
}
},
_count: {
select: {
comments: true
}
}
},
orderBy: {
createdAt: 'desc'
},
take: 10
});
return (
<div className="p-8">
<h1 className="text-4xl font-bold mb-8">블로그</h1>
{posts.map(post => (
<article key={post.id} className="mb-6 p-6 border rounded">
<h2 className="text-2xl font-semibold mb-2">{post.title}</h2>
<p className="text-gray-600 mb-2">작성자: {post.author.name}</p>
<p className="text-sm text-gray-500">
댓글 {post._count.comments}개
</p>
</article>
))}
</div>
);
}
|
Update (수정)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 'use server';
export async function updatePost(id: string, formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const post = await prisma.post.update({
where: { id },
data: {
title,
content,
updatedAt: new Date()
}
});
revalidatePath(`/blog/${id}`);
return post;
}
|
Delete (삭제)
1
2
3
4
5
6
7
8
9
| 'use server';
export async function deletePost(id: string) {
await prisma.post.delete({
where: { id }
});
revalidatePath('/blog');
}
|
🔗 관계형 데이터
Many-to-Many 관계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // 포스트에 태그 추가
export async function addTagToPost(postId: string, tagName: string) {
await prisma.post.update({
where: { id: postId },
data: {
tags: {
connectOrCreate: {
where: { name: tagName },
create: { name: tagName }
}
}
}
});
revalidatePath(`/blog/${postId}`);
}
// 포스트의 태그 조회
const post = await prisma.post.findUnique({
where: { id: postId },
include: {
tags: true
}
});
|
💾 트랜잭션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // 여러 작업을 하나의 트랜잭션으로
export async function transferPost(postId: string, newAuthorId: string) {
await prisma.$transaction(async (tx) => {
// 1. 포스트 소유자 변경
await tx.post.update({
where: { id: postId },
data: { authorId: newAuthorId }
});
// 2. 알림 생성
await tx.notification.create({
data: {
userId: newAuthorId,
message: `새로운 포스트가 할당되었습니다`
}
});
// 3. 로그 기록
await tx.activityLog.create({
data: {
action: 'TRANSFER_POST',
postId,
userId: newAuthorId
}
});
});
}
|
⚡ 성능 최적화
1. 선택적 필드 가져오기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // ❌ 나쁨: 모든 필드 가져오기
const user = await prisma.user.findUnique({
where: { id }
});
// ✅ 좋음: 필요한 필드만
const user = await prisma.user.findUnique({
where: { id },
select: {
id: true,
name: true,
email: true
}
});
|
2. 병렬 쿼리
1
2
3
4
5
6
7
8
9
| // ❌ 순차: 느림
const user = await prisma.user.findUnique({ where: { id } });
const posts = await prisma.post.findMany({ where: { authorId: id } });
// ✅ 병렬: 빠름
const [user, posts] = await Promise.all([
prisma.user.findUnique({ where: { id } }),
prisma.post.findMany({ where: { authorId: id } })
]);
|
3. 페이지네이션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| export async function getPosts(page: number = 1, pageSize: number = 10) {
const skip = (page - 1) * pageSize;
const [posts, total] = await Promise.all([
prisma.post.findMany({
skip,
take: pageSize,
orderBy: { createdAt: 'desc' }
}),
prisma.post.count()
]);
return {
posts,
total,
pages: Math.ceil(total / pageSize)
};
}
|
🎯 오늘 배운 내용 정리
- Prisma 설정
- CRUD 작업
- Create, Read, Update, Delete
- 관계형 데이터
- 고급 기능
📚 시리즈 네비게이션
“데이터베이스 마스터하기!” 🗃️