15.2 Prisma CRUD 기본 — 완전한 타입 안전 쿼리
생성 (Create)
import { prisma } from './lib/prisma'
// 단일 레코드 생성
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
},
})
// 반환 타입: User (자동 추론)
// 관계와 함께 생성 (중첩 쓰기)
const userWithPost = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
posts: {
create: [
{ title: '첫 번째 글', content: '내용...' },
{ title: '두 번째 글' },
],
},
profile: {
create: { bio: '안녕하세요!' },
},
},
include: {
posts: true,
profile: true,
},
})
// 여러 레코드 한 번에 생성
const result = await prisma.user.createMany({
data: [
{ email: 'charlie@example.com', name: 'Charlie' },
{ email: 'diana@example.com', name: 'Diana' },
],
skipDuplicates: true, // 중복 무시
})
console.log(result.count) // 생성된 레코드 수
조회 (Read)
// 단일 조회 — 없으면 null
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
})
// 단일 조회 — 없으면 throw
const user2 = await prisma.user.findUniqueOrThrow({
where: { id: 1 },
})
// 첫 번째 일치 레코드
const firstUser = await prisma.user.findFirst({
where: { name: { contains: 'Alice' } },
orderBy: { createdAt: 'desc' },
})
// 여러 레코드 조회
const users = await prisma.user.findMany({
where: {
AND: [
{ email: { contains: '@example.com' } },
{ createdAt: { gte: new Date('2024-01-01') } },
],
},
orderBy: [
{ name: 'asc' },
{ createdAt: 'desc' },
],
skip: 0, // 페이지네이션
take: 10,
})
// 선택적 필드 반환 (select)
const userNames = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
// password: false — 기본적으로 false이므로 생략 가능
},
})
// 반환 타입: { id: number; name: string | null; email: string }[]
// 관계 포함 (include)
const usersWithPosts = await prisma.user.findMany({
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5,
},
profile: true,
},
})
수정 (Update)
// 단일 레코드 수정
const updatedUser = await prisma.user.update({
where: { id: 1 },
data: {
name: 'Alice Updated',
},
})
// 원자적 숫자 연산
const post = await prisma.post.update({
where: { id: 1 },
data: {
viewCount: { increment: 1 },
likeCount: { decrement: 1 },
},
})
// 관계 수정
const userWithNewPost = await prisma.user.update({
where: { id: 1 },
data: {
posts: {
create: { title: '새 글' },
connect: { id: 5 }, // 기존 포스트 연결
disconnect: { id: 3 }, // 연결 해제
deleteMany: { published: false }, // 조건부 삭제
},
},
include: { posts: true },
})
// 여러 레코드 수정
const result = await prisma.user.updateMany({
where: { email: { contains: '@old-domain.com' } },
data: { name: 'Migrated User' },
})
console.log(result.count)
// upsert — 있으면 수정, 없으면 생성
const upserted = await prisma.user.upsert({
where: { email: 'new@example.com' },
update: { name: 'Updated Name' },
create: { email: 'new@example.com', name: 'New User' },
})
삭제 (Delete)
// 단일 레코드 삭제
const deleted = await prisma.user.delete({
where: { id: 1 },
})
// 여러 레코드 삭제
const result = await prisma.post.deleteMany({
where: {
published: false,
createdAt: { lt: new Date('2023-01-01') },
},
})
console.log(result.count)
집계 (Aggregate)
// 개수 세기
const count = await prisma.user.count()
const publishedCount = await prisma.post.count({
where: { published: true },
})
// 숫자 집계
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { viewCount: true },
_max: { viewCount: true },
_min: { viewCount: true },
_sum: { viewCount: true },
})
// 그룹화
const postsByUser = await prisma.post.groupBy({
by: ['authorId'],
_count: { id: true },
_avg: { viewCount: true },
having: {
viewCount: { _avg: { gt: 100 } }, // 평균 조회수 100 이상
},
orderBy: {
_count: { id: 'desc' },
},
})
고수 팁
타입 안전한 where 필터 패턴
import { Prisma } from '@prisma/client'
// 동적 필터 빌드
function buildUserFilter(params: {
search?: string
role?: 'ADMIN' | 'USER'
active?: boolean
}): Prisma.UserWhereInput {
return {
...(params.search && {
OR: [
{ name: { contains: params.search, mode: 'insensitive' } },
{ email: { contains: params.search, mode: 'insensitive' } },
],
}),
...(params.role && { role: params.role }),
...(params.active !== undefined && { isActive: params.active }),
}
}
const users = await prisma.user.findMany({
where: buildUserFilter({ search: 'alice', role: 'USER' }),
})