[Next.js + TS 실전] 2. Prisma + SQLite로 DB 붙이기
실전 1편까지 CRUD는 완성됐습니다.
문제는 서버를 재시작하면 메모가 사라진다는 것입니다.
이번 편은 Prisma + SQLite로 진짜 저장을 붙입니다.
이 편에서 할 일
- Prisma + SQLite 설치·초기화
Memo테이블(스키마) 정의lib/prisma.ts클라이언트 만들기lib/memos.ts를 배열 → DB 쿼리로 교체- 시드(초기 데이터) 넣고 재시작해도 남는지 확인
페이지·Server Action 코드는 거의 안 건드립니다.
바뀌는 건 주로 lib/memos.ts입니다. 이게 이번 편의 핵심입니다.
왜 Prisma + SQLite인가
| 선택 | 이유 |
|---|---|
| SQLite | 파일 하나로 동작. 로컬 학습에 제일 단순 |
| Prisma | 스키마 → 타입 생성. TypeScript와 궁합이 좋음 |
나중에 PostgreSQL로 바꿀 때도, 스키마의 provider와 DATABASE_URL만 바꾸면 비슷한 코드로 갈 수 있습니다.
지금은 로컬에서 영속화가 목표입니다.
전체 그림
페이지 / Server Action
↓
lib/memos.ts ← 함수 이름은 그대로
↓
lib/prisma.ts
↓
SQLite 파일 (prisma/dev.db)
getMemos, addMemo 같은 함수 시그니처를 유지하면actions.ts와 페이지는 그대로 두어도 됩니다.
1) 패키지 설치
memo-app 폴더에서:
npm install @prisma/client
npm install -D prisma
초기화 (SQLite):
npx prisma init --datasource-provider sqlite
생성되는 것:
prisma/schema.prisma
.env
.env 예시:
DATABASE_URL="file:./dev.db"
file:./dev.db는 prisma/ 폴더 기준 상대 경로입니다.
실제 DB 파일은 prisma/dev.db에 생깁니다.
.env는 Git에 올리지 마세요.create-next-app이.gitignore에.env*를 넣어 둔 경우가 많습니다.
2) 스키마 정의
prisma/schema.prisma를 아래처럼 맞춥니다.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Memo {
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
}
포인트:
| 필드 | 의미 |
|---|---|
@id |
기본 키 |
@default(cuid()) |
고유 id 자동 생성 (Date.now() 대체) |
DateTime |
DB에는 날짜 타입, 화면에는 문자열로 변환해서 씀 |
@default(now()) |
생성 시각 자동 |
테이블을 만들고 Prisma Client를 생성합니다.
npx prisma migrate dev --name init
성공하면:
prisma/dev.db생성prisma/migrations/생성@prisma/client타입이 갱신됨
3) Prisma Client 싱글톤
Next.js 개발 모드에서는 Hot Reload 때문에PrismaClient를 매번 new 하면 연결이 쌓일 수 있습니다.
경로: 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;
}
개발에서만 globalThis에 붙여 두는 패턴입니다.
공식 문서에서도 비슷한 예시를 씁니다.
4) lib/memos.ts를 DB로 교체
배열과 delay를 지우고, Prisma로 바꿉니다.
import type { Memo } from "@/types/memo";
import { prisma } from "@/lib/prisma";
function toMemo(row: {
id: string;
title: string;
content: string;
createdAt: Date;
}): Memo {
return {
id: row.id,
title: row.title,
content: row.content,
createdAt: row.createdAt.toISOString().slice(0, 10),
};
}
export async function getMemos(): Promise<Memo[]> {
const rows = await prisma.memo.findMany({
orderBy: { createdAt: "desc" },
});
return rows.map(toMemo);
}
export async function getMemoById(id: string): Promise<Memo | undefined> {
const row = await prisma.memo.findUnique({
where: { id },
});
return row ? toMemo(row) : undefined;
}
export async function addMemo(input: {
title: string;
content: string;
}): Promise<Memo> {
const row = await prisma.memo.create({
data: {
title: input.title,
content: input.content,
},
});
return toMemo(row);
}
export async function updateMemo(
id: string,
input: { title: string; content: string }
): Promise<Memo | undefined> {
try {
const row = await prisma.memo.update({
where: { id },
data: {
title: input.title,
content: input.content,
},
});
return toMemo(row);
} catch {
return undefined;
}
}
export async function deleteMemo(id: string): Promise<boolean> {
try {
await prisma.memo.delete({
where: { id },
});
return true;
} catch {
return false;
}
}
포인트:
- 함수 이름·반환 타입은 그대로 → Action/페이지 수정 최소
toMemo로 DBDate→ 화면용string변환- 없는 id update/delete는 Prisma가 에러 →
try/catch로undefined/false - 가짜
delay는 제거 (로딩 UI는 DB 속도에 맡김)
types/memo.ts의 Memo 타입은 그대로 둡니다.
DB 모델과 화면 모델을 toMemo로 연결하는 방식입니다.
5) 초기 데이터(시드) 넣기 (선택)
DB가 비어 있으면 목록이 텅 빕니다.
연습용 데이터를 넣고 싶다면:
prisma/seed.ts:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.memo.createMany({
data: [
{
title: "Next.js 시작",
content: "App Router로 프로젝트 만들었다.",
},
{
title: "TypeScript props",
content: "MemoCard에 타입을 붙였다.",
},
],
});
}
main()
.then(() => prisma.$disconnect())
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
package.json 맨 바깥에 prisma 키를 추가합니다.
(주의: { "prisma": ... }를 파일에 따로 하나 더 붙이면 JSON이 깨집니다.)
예:
{
"name": "memo-app",
"scripts": { "...": "..." },
"dependencies": { "...": "..." },
"devDependencies": { "...": "..." },
"prisma": {
"seed": "npx tsx prisma/seed.ts"
}
}
tsx가 없으면:
npm install -D tsx
시드 전에 반드시 마이그레이션을 먼저 하세요. (Memo 테이블이 있어야 함)
npx prisma migrate dev --name init
npx prisma db seed
또는 Prisma Studio로 직접 넣어도 됩니다.
npx prisma studio
브라우저에서 테이블을 GUI로 볼 수 있습니다.
6) 동작 확인
npm run dev/memos/new에서 메모 추가- 터미널에서 서버 중지 후 다시
npm run dev /memos에 방금 메모가 남아 있으면 성공
이전(메모리 배열)이라면 여기서 사라졌을 겁니다.
Action / 페이지는?
대부분 수정할 필요 없습니다.
createMemo, updateMemoAction, deleteMemoAction은
이미 addMemo / updateMemo / deleteMemo만 호출합니다.
lib/memos.ts 구현만 바뀌었을 뿐입니다.
이게 좋은 설계입니다.
UI · Action · 데이터 레이어를 나누면,
저장소만 바꿔도 화면 코드는 버틸 수 있다.
.gitignore 확인
아래가 무시되는지 확인하세요.
.env
.env*.local
prisma/dev.db
prisma/dev.db-journal
DB 파일과 환경 변수는 저장소에 올리지 않는 편이 안전합니다.
팀원/다른 PC에서는 migrate로 스키마를 다시 만듭니다.
npx prisma migrate dev
자주 하는 실수
1. migrate를 안 돌림
→ prisma.memo가 없거나 테이블이 없어 에러
2. lib/prisma.ts 없이 매 파일에서 new PrismaClient()
→ 개발 중 연결 폭주 가능
3. createdAt을 Date 그대로 JSX에 뿌림
→ toMemo로 문자열 변환 권장 (기존 UI와 맞춤)
4. .env의 DATABASE_URL 오타
→ DB 파일을 못 찾아 실패
5. 예전에 쓰던 id 1, 2를 하드코딩한 링크
→ 이제 id는 cuid()라 긴 문자열입니다. UI의 Link는 memo.id를 쓰면 문제 없습니다.
6. loading이 너무 짧아져서 “로딩이 안 보여요”
→ 정상입니다. 원하면 loading.tsx는 그대로 두고, 느린 네트워크에서만 눈에 띕니다.
이 편에서 가져갈 것
- 메모리 배열은 학습용, 실서비스는 DB가 필요하다
- Prisma = 스키마 + 타입 안전한 쿼리
- SQLite = 로컬에 파일 하나로 시작하기 좋다
- 데이터 레이어만 바꾸면 CRUD UI/Action은 재사용된다
toMemo로 DB 모델과 화면 타입을 연결한다
다음 편 예고
[Next.js + TS 실전] 3. Vercel에 배포하기
- 빌드·환경 변수
- SQLite의 한계와 배포 시 선택지
- 메모 앱을 URL로 열어보기
로컬 DB까지 붙였으니, 다음은 다른 사람이 접속할 수 있게 만드는 단계입니다.
미리 한 줄: Vercel 같은 서버리스 환경에서는 SQLite 파일 DB가 잘 안 맞을 수 있습니다.
다음 편에서 “왜”와 대안(예: Postgres)을 같이 정리합니다.
시리즈 목차 (실전)
- 메모 수정과 삭제
- Prisma + SQLite로 DB 붙이기 ← 지금
- Vercel 배포 (예정)
입문·실전 1편과의 연결
| 이전 | 이번 |
|---|---|
const memos = [...] |
prisma.memo.findMany |
Date.now() id |
cuid() |
| 서버 재시작 → 데이터 소멸 | 서버 재시작 → 데이터 유지 |
delay()로 로딩 연습 |
실제 I/O |
함수 이름은 같고, 속이 DB로 바뀐 것이 이번 편의 전부입니다.
'frontend > NextJs' 카테고리의 다른 글
| # [Next.js + TS 실전] 4. 폼 검증하기 (zod) (0) | 2026.07.30 |
|---|---|
| # [Next.js + TS 실전] 3. Vercel에 배포하기 (0) | 2026.07.30 |
| # [Next.js + TS 실전] 1. 메모 수정과 삭제 (0) | 2026.07.30 |
| # [Next.js + TS] 8. 정리 + 다음 학습 로드맵 (0) | 2026.07.29 |
| # [Next.js + TS] 7. 폼과 서버 액션 입문 (0) | 2026.07.29 |
