CRUD는 단순하지만 DB 성능과 데이터 정합성을 결정짓는 핵심 로직입니다.
| 조회(Read) | select로 최소 데이터만, include는 관계 필요 시만 사용 |
| 생성(Create) | Zod 검증 + 트랜잭션 + 중복 방지 (@@unique) |
| 수정(Update) | 권한 검증 + updateMany 통한 조건 기반 안전 수정 |
| 삭제(Delete) | onDelete: Cascade 또는 Soft Delete 전략 병행 |
데이터 조회 (Read)
예시
export async function getUserByClerkId(clerkId: string) {
return prisma.user.findUnique({
where: { clerkId },
include: {
_count: { select: { followers: true, following: true, posts: true } },
},
});
}
- select vs include 구분
- select: 필요한 필드만 반환 → 응답 크기 최소화.
- include: 관계 데이터(posts, followers 등)가 꼭 필요할 때만 사용.
- _count는 관계형 데이터의 수만 빠르게 가져오는 Prisma 내부 최적화 쿼리입니다.
- 대용량 relation은 include를 남발하면 직렬화 비용이 급격히 커집니다.
_count
Prisma에서 리스트 전체를 join하지 않고, 관계의 “개수만” 빠르게 조회할 수 있는 내장 서브쿼리 기능입니다.
export async function getUserByClerkId(clerkId: string) {
return prisma.user.findUnique({
where: { clerkId },
include: {
_count: {
select: {
followers: true,
following: true,
posts: true,
},
},
},
});
}
- 이렇게 하면 Prisma가 실제로는 아래와 유사한 SQL 서브쿼리를 날립니다
SELECT
"User"."id",
"User"."name",
(
SELECT COUNT(*) FROM "Follow" WHERE "Follow"."followingId" = "User"."id"
) AS "followers",
(
SELECT COUNT(*) FROM "Follow" WHERE "Follow"."followerId" = "User"."id"
) AS "following",
(
SELECT COUNT(*) FROM "Post" WHERE "Post"."authorId" = "User"."id"
) AS "posts"
FROM "User"
WHERE "User"."clerkId" = 'user_123';
- join 없이 서브쿼리로 count만 따로 계산하기 때문에 followers 전체를 join해서 가져오는 것보다 훨씬 빠릅니다.
결과 구조
{
"id": "user_123",
"name": "Appbae Princess",
"_count": {
"followers": 15,
"following": 6,
"posts": 42
}
}
데이터 생성 (Create)
예시
export async function syncUser() {
try {
const { userId } = await auth();
const user = await currentUser();
if (!userId || !user) return;
const existingUser = await prisma.user.findUnique({ where: { clerkId: userId } });
if (existingUser) return existingUser;
const dbUser = await prisma.user.create({
data: {
clerkId: userId,
name: `${user.firstName || ''} ${user.lastName || ''}`,
username: user.username ?? user.emailAddresses[0].emailAddress.split('@')[0],
email: user.emailAddresses[0].emailAddress,
image: user.imageUrl,
},
});
return dbUser;
} catch (error) {
console.log('Error in syncUser', error);
}
}
- Clerk 연동 시 syncUser는 로그인 직후 1회만 실행되면 충분합니다.
- 이미 존재하는 사용자는 findUnique로 중복 방지.
- Prisma의 create는 Promise 기반이므로 try/catch로 감싸 로그를 남겨야 디버깅이 용이합니다.
- 생성 시 Zod 검증을 활용하면 타입 안전성을 유지할 수 있습니다.
const CreatePostSchema = z.object({
authorId: z.string(),
title: z.string().min(1),
body: z.string().optional(),
});
export async function createPost(input: z.infer<typeof CreatePostSchema>) {
const data = CreatePostSchema.parse(input);
return prisma.post.create({ data });
}
데이터 수정 (Update)
예시
export async function updatePost(input: { postId: string; requesterId: string; title?: string; body?: string }) {
const { postId, requesterId, ...patch } = input;
const post = await prisma.post.findUnique({ where: { id: postId }, select: { authorId: true } });
if (!post) throw new Error('Post not found');
if (post.authorId !== requesterId) throw new Error('Forbidden');
return prisma.post.update({ where: { id: postId }, data: patch });
}
- 수정 전 반드시 권한 검증: post.authorId와 요청자 ID 일치 확인.
- 다중 조건 업데이트는 updateMany로 안전하게 수행 가능.
- updatedAt 컬럼을 사용하면 optimistic update도 구현 가능.
데이터 삭제 (Delete)
예시
export async function deletePost(postId: string, requesterId: string) {
const post = await prisma.post.findUnique({ where: { id: postId }, select: { authorId: true } });
if (!post) throw new Error('Post not found');
if (post.authorId !== requesterId) throw new Error('Forbidden');
await prisma.post.delete({ where: { id: postId } });
}
- 삭제 전 권한 검증은 필수.
- Prisma 스키마에서 onDelete: Cascade 설정 시 관계 데이터 자동 삭제.
정리
| 기능 | 주요 메서드 | 핵심 개념 | 실무 주의점 |
| 조회 | findUnique, findMany | select / include, _count | 큰 relation은 지양, 커서 페이지네이션 필수 |
| 생성 | create, $transaction | Zod 검증, 유저 동기화 | 중복 방지, 에러 로깅 |
| 수정 | update, updateMany | 권한 검증, 낙관적 락 | 조건 불일치 시 0건 처리 주의 |
| 삭제 | delete, update | Cascade / Soft Delete | 실데이터 삭제 여부 명확히 설계 |
'개발 공부' 카테고리의 다른 글
| shadcn/ui (2) example) Button · Card (0) | 2025.11.17 |
|---|---|
| shadcn/ui (1) (0) | 2025.11.16 |
| Prisma DB 연결 (0) | 2025.11.09 |
| Prisma 관계(Relation) (0) | 2025.11.02 |
| Prisma 스키마 (2) - 인덱스, 유니크, 복합키 (0) | 2025.11.01 |