
사용자 계정이 있을 시 좋아요와 북마크 기능을 추가하려고 했습니다.
아무래도 데이터를 저장하고 관리하려면 데이터베이스가 필요할 것 같아서, 학습도 할 겸 Prisma와 MongoDB를 사용하기로 결정했습니다.
Prisma와 MongoDB 설정
Prisma는 MongoDB와 같은 NoSQL 데이터베이스에서도 편리하게 데이터를 다룰 수 있도록 해줍니다.
이를 위해 schema.prisma 파일을 다음과 같이 작성했습니다.
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid()) @map("_id")
auth0Id String @unique
bookmarks String[]
liked String[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
bookmarks와 liked 필드는 문자열 배열로 정의하여 포켓몬 이름을 저장합니다.
Prisma 클라이언트를 생성하기 위해 npx prisma generate 명령어를 실행했습니다.
Prisma 클라이언트 설정
PrismaClient를 여러 인스턴스로 생성하는 문제를 방지하기 위해 전역으로 관리합니다.
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
export default prisma;
개발 환경에서는 Prisma 클라이언트를 재사용하고, 배포 환경에서는 새 인스턴스를 생성합니다.
API 엔드포인트 구현
POST 요청: 북마크/좋아요 토글
사용자가 좋아요 또는 북마크를 클릭할 때 데이터를 추가하거나 제거하는 API입니다.
import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/utils/connect';
export async function POST(req: NextRequest) {
try {
const { userId, pokemon, action } = await req.json();
if (!['bookmark', 'like'].includes(action)) {
return NextResponse.json(
{ message: '잘못된 작업입니다. (action이 bookmark 또는 like 여야 합니다.)' },
{ status: 400 }
);
}
let user = await prisma.user.findUnique({
where: { auth0Id: userId },
});
if (!user) {
user = await prisma.user.create({
data: {
auth0Id: userId,
bookmarks: [],
liked: [],
},
});
}
const fieldToUpdate = action === 'bookmark' ? 'bookmarks' : 'liked';
const currentItems = user[fieldToUpdate];
const updatedItems = currentItems.includes(pokemon)
? currentItems.filter((item) => item !== pokemon)
: [...currentItems, pokemon];
await prisma.user.update({
where: { auth0Id: userId },
data: { [fieldToUpdate]: updatedItems },
});
return NextResponse.json({
toggledOff: currentItems.includes(pokemon),
success: true,
message: `성공적으로 ${pokemon}를 ${action === 'bookmark' ? '북마크' : '좋아요'} 처리했습니다.`,
});
} catch (error) {
console.log('링크 또는 북마크 처리 중 오류 발생', error);
return NextResponse.json(
{ message: '요청을 처리하는 동안 오류가 발생했습니다.' },
{ status: 500 }
);
}
}
prisma.user.findUnique로 유저를 조회하고, 존재하지 않으면 새로 생성합니다.
좋아요/북마크 배열에서 항목을 추가하거나 제거한 후, 업데이트합니다.
GET 요청: 사용자 데이터 반환
현재 사용자의 정보를 반환하는 API입니다.
import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/utils/connect';
export async function GET(req: NextRequest, { params }: { params: { userId: string } }) {
try {
const { userId } = params;
if (!userId) {
return NextResponse.json(
{ message: '유효하지 않은 사용자입니다.' },
{ status: 400 }
);
}
const user = await prisma.user.findUnique({
where: { auth0Id: userId },
});
if (!user) {
return NextResponse.json(
{ message: '사용자를 찾을 수 없습니다.' },
{ status: 404 }
);
}
return NextResponse.json(user);
} catch (error) {
console.log('Error', error);
return NextResponse.json({ message: 'Error' }, { status: 500 });
}
}
Zustand를 활용한 상태 관리
Zustand를 사용하여 사용자 데이터를 상태로 관리했습니다. API 요청으로 데이터를 가져오거나, 좋아요와 북마크 작업을 수행합니다.
export const useUserStore = create<UserStore>((set, get) => ({
userDetails: null,
fetchUserDetails: async (userId) => {
try {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) {
throw new Error('사용자 정보를 가져오는 데 실패했습니다.');
}
const data = await response.json();
set({ userDetails: data });
} catch (error) {
console.error('사용자 정보를 가져오는 중 오류가 발생했습니다:', error);
}
},
performAction: async (userId, pokemon, action) => {
try {
set((state) => {
if (!state.userDetails) return state;
const updatedBookmarks =
action === 'bookmark'
? state.userDetails.bookmarks.includes(pokemon)
? state.userDetails.bookmarks.filter((p) => p !== pokemon)
: [...state.userDetails.bookmarks, pokemon]
: state.userDetails.bookmarks;
const updatedLikes =
action === 'like'
? state.userDetails.liked.includes(pokemon)
? state.userDetails.liked.filter((p) => p !== pokemon)
: [...state.userDetails.liked, pokemon]
: state.userDetails.liked;
return {
userDetails: {
...state.userDetails,
bookmarks: updatedBookmarks,
liked: updatedLikes,
},
};
});
const response = await fetch('/api/pokemon', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, pokemon, action }),
});
if (!response.ok) {
throw new Error('요청을 수행하는 데 실패했습니다.');
}
} catch (error) {
console.error('요청을 수행하는 중 오류가 발생했습니다:', error);
const { fetchUserDetails } = get();
await fetchUserDetails(userId);
}
},
}));
'Next.js > Pokemon' 카테고리의 다른 글
Zustand로 상태 관리, 포켓몬 검색 기능 구현하기 (0) | 2025.01.02 |
---|---|
PokeAPI와 페이지네이션 기능 구현하기 (0) | 2024.12.30 |
Next.js 15.1.2 -> 14.2.21로 다운그레이드 (0) | 2024.12.27 |
Next.js와 Auth0를 이용한 간단한 로그인 기능 구현하기 (0) | 2024.12.23 |

사용자 계정이 있을 시 좋아요와 북마크 기능을 추가하려고 했습니다.
아무래도 데이터를 저장하고 관리하려면 데이터베이스가 필요할 것 같아서, 학습도 할 겸 Prisma와 MongoDB를 사용하기로 결정했습니다.
Prisma와 MongoDB 설정
Prisma는 MongoDB와 같은 NoSQL 데이터베이스에서도 편리하게 데이터를 다룰 수 있도록 해줍니다.
이를 위해 schema.prisma 파일을 다음과 같이 작성했습니다.
JAVASCRIPT
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid()) @map("_id")
auth0Id String @unique
bookmarks String[]
liked String[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
bookmarks와 liked 필드는 문자열 배열로 정의하여 포켓몬 이름을 저장합니다.
Prisma 클라이언트를 생성하기 위해 npx prisma generate 명령어를 실행했습니다.
Prisma 클라이언트 설정
PrismaClient를 여러 인스턴스로 생성하는 문제를 방지하기 위해 전역으로 관리합니다.
JAVASCRIPT
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
export default prisma;
개발 환경에서는 Prisma 클라이언트를 재사용하고, 배포 환경에서는 새 인스턴스를 생성합니다.
API 엔드포인트 구현
POST 요청: 북마크/좋아요 토글
사용자가 좋아요 또는 북마크를 클릭할 때 데이터를 추가하거나 제거하는 API입니다.
JAVASCRIPT
import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/utils/connect';
export async function POST(req: NextRequest) {
try {
const { userId, pokemon, action } = await req.json();
if (!['bookmark', 'like'].includes(action)) {
return NextResponse.json(
{ message: '잘못된 작업입니다. (action이 bookmark 또는 like 여야 합니다.)' },
{ status: 400 }
);
}
let user = await prisma.user.findUnique({
where: { auth0Id: userId },
});
if (!user) {
user = await prisma.user.create({
data: {
auth0Id: userId,
bookmarks: [],
liked: [],
},
});
}
const fieldToUpdate = action === 'bookmark' ? 'bookmarks' : 'liked';
const currentItems = user[fieldToUpdate];
const updatedItems = currentItems.includes(pokemon)
? currentItems.filter((item) => item !== pokemon)
: [...currentItems, pokemon];
await prisma.user.update({
where: { auth0Id: userId },
data: { [fieldToUpdate]: updatedItems },
});
return NextResponse.json({
toggledOff: currentItems.includes(pokemon),
success: true,
message: `성공적으로 ${pokemon}를 ${action === 'bookmark' ? '북마크' : '좋아요'} 처리했습니다.`,
});
} catch (error) {
console.log('링크 또는 북마크 처리 중 오류 발생', error);
return NextResponse.json(
{ message: '요청을 처리하는 동안 오류가 발생했습니다.' },
{ status: 500 }
);
}
}
prisma.user.findUnique로 유저를 조회하고, 존재하지 않으면 새로 생성합니다.
좋아요/북마크 배열에서 항목을 추가하거나 제거한 후, 업데이트합니다.
GET 요청: 사용자 데이터 반환
현재 사용자의 정보를 반환하는 API입니다.
JAVASCRIPT
import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/utils/connect';
export async function GET(req: NextRequest, { params }: { params: { userId: string } }) {
try {
const { userId } = params;
if (!userId) {
return NextResponse.json(
{ message: '유효하지 않은 사용자입니다.' },
{ status: 400 }
);
}
const user = await prisma.user.findUnique({
where: { auth0Id: userId },
});
if (!user) {
return NextResponse.json(
{ message: '사용자를 찾을 수 없습니다.' },
{ status: 404 }
);
}
return NextResponse.json(user);
} catch (error) {
console.log('Error', error);
return NextResponse.json({ message: 'Error' }, { status: 500 });
}
}
Zustand를 활용한 상태 관리
Zustand를 사용하여 사용자 데이터를 상태로 관리했습니다. API 요청으로 데이터를 가져오거나, 좋아요와 북마크 작업을 수행합니다.
JAVASCRIPT
export const useUserStore = create<UserStore>((set, get) => ({
userDetails: null,
fetchUserDetails: async (userId) => {
try {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) {
throw new Error('사용자 정보를 가져오는 데 실패했습니다.');
}
const data = await response.json();
set({ userDetails: data });
} catch (error) {
console.error('사용자 정보를 가져오는 중 오류가 발생했습니다:', error);
}
},
performAction: async (userId, pokemon, action) => {
try {
set((state) => {
if (!state.userDetails) return state;
const updatedBookmarks =
action === 'bookmark'
? state.userDetails.bookmarks.includes(pokemon)
? state.userDetails.bookmarks.filter((p) => p !== pokemon)
: [...state.userDetails.bookmarks, pokemon]
: state.userDetails.bookmarks;
const updatedLikes =
action === 'like'
? state.userDetails.liked.includes(pokemon)
? state.userDetails.liked.filter((p) => p !== pokemon)
: [...state.userDetails.liked, pokemon]
: state.userDetails.liked;
return {
userDetails: {
...state.userDetails,
bookmarks: updatedBookmarks,
liked: updatedLikes,
},
};
});
const response = await fetch('/api/pokemon', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, pokemon, action }),
});
if (!response.ok) {
throw new Error('요청을 수행하는 데 실패했습니다.');
}
} catch (error) {
console.error('요청을 수행하는 중 오류가 발생했습니다:', error);
const { fetchUserDetails } = get();
await fetchUserDetails(userId);
}
},
}));
'Next.js > Pokemon' 카테고리의 다른 글
Zustand로 상태 관리, 포켓몬 검색 기능 구현하기 (0) | 2025.01.02 |
---|---|
PokeAPI와 페이지네이션 기능 구현하기 (0) | 2024.12.30 |
Next.js 15.1.2 -> 14.2.21로 다운그레이드 (0) | 2024.12.27 |
Next.js와 Auth0를 이용한 간단한 로그인 기능 구현하기 (0) | 2024.12.23 |