iBetter Books
수정

Ch 01. 프로젝트 설계와 DB 스키마

코드를 한 줄 작성하기 전에 먼저 무엇을 만들지 명확히 해야 합니다. 막연하게 시작하면 중간에 방향을 잃기 쉽습니다.

구현할 기능 목록

이번 블로그 서비스에서 만들 기능은 다음과 같습니다.

  • 회원 가입, 로그인, 로그아웃
  • 게시글 목록 조회, 상세 보기, 작성, 수정, 삭제
  • 댓글 작성, 삭제
  • 이미지 업로드 (게시글 대표 이미지)

권한 규칙도 미리 정합니다. 게시글 수정과 삭제는 작성자 본인만 할 수 있고, 댓글 삭제도 마찬가지입니다. 이 규칙은 서버 API와 프론트엔드 양쪽에서 모두 검증합니다.

프로젝트 디렉토리 구조

blog-app/
├── prisma/
│   ├── schema.prisma
│   └── migrations/
├── server/
│   ├── api/
│   │   ├── auth/
│   │   │   ├── login.post.ts
│   │   │   ├── logout.post.ts
│   │   │   └── register.post.ts
│   │   ├── posts/
│   │   │   ├── index.get.ts
│   │   │   ├── index.post.ts
│   │   │   ├── [id].get.ts
│   │   │   ├── [id].put.ts
│   │   │   ├── [id].delete.ts
│   │   │   └── [id]/
│   │   │       ├── comments.get.ts
│   │   │       └── comments.post.ts
│   │   ├── comments/
│   │   │   └── [id].delete.ts
│   │   └── upload.post.ts
│   ├── middleware/
│   │   └── auth.ts
│   └── utils/
│       └── prisma.ts
├── stores/
│   └── auth.ts
├── pages/
│   ├── index.vue
│   ├── login.vue
│   ├── register.vue
│   └── posts/
│       ├── write.vue
│       ├── [id].vue
│       └── [id]/
│           └── edit.vue
├── middleware/
│   └── auth.ts
└── public/
    └── uploads/

Prisma 스키마

데이터 모델은 세 가지입니다. User, Post, Comment이며, 관계는 User가 Post와 Comment를 여러 개 가질 수 있고, Post가 Comment를 여러 개 가질 수 있는 구조입니다. Post가 삭제되면 댓글도 함께 삭제되도록 onDelete: Cascade를 설정합니다.

// prisma/schema.prisma
datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  password  String
  name      String
  posts     Post[]
  comments  Comment[]
  createdAt DateTime  @default(now())
}

model Post {
  id        Int       @id @default(autoincrement())
  title     String
  content   String
  imageUrl  String?
  author    User      @relation(fields: [authorId], references: [id])
  authorId  Int
  comments  Comment[]
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
}

model Comment {
  id        Int      @id @default(autoincrement())
  content   String
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  postId    Int
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

스키마를 작성했으면 마이그레이션을 실행합니다.

npx prisma migrate dev --name init

이 명령은 prisma/migrations/ 폴더에 SQL 파일을 생성하고 데이터베이스에 테이블을 만듭니다. .env 파일에 DATABASE_URL="file:./dev.db"가 설정되어 있어야 합니다.

PART 05 Ch 04에서 만든 server/utils/prisma.ts가 그대로 사용됩니다. usePrisma()를 호출하면 싱글톤 PrismaClient 인스턴스가 반환되고, Nitro 자동 임포트 덕분에 API 파일에서 import 없이 바로 쓸 수 있습니다. 새 스키마로 마이그레이션하면 Prisma가 User, Post, Comment 모델을 새로 인식하므로 유틸리티 파일을 수정할 필요는 없습니다.

서버 미들웨어 수정

PART 05 Ch 05에서 만든 서버 미들웨어는 /api/protected/ 경로만 인증 검사를 했습니다. 블로그 프로젝트는 /api/posts, /api/comments 등 직접 경로를 사용하므로, 미들웨어 전략을 변경합니다. 모든 요청에서 쿠키를 파싱해 userId를 설정하고, 403 처리는 각 핸들러가 직접 담당합니다.

// server/middleware/auth.tsimport jwt from 'jsonwebtoken'export default defineEventHandler((event) => {  const token = getCookie(event, 'auth_token')  if (!token) return  try {    const payload = jwt.verify(token, process.env.JWT_SECRET!) as { userId: number }    event.context.userId = payload.userId  } catch {    // 만료되거나 유효하지 않은 토큰 — userId를 설정하지 않고 통과  }})

이렇게 하면 로그인한 사용자는 모든 API에서 event.context.userId를 통해 식별되고, 각 핸들러에서 if (!userId) throw createError({ statusCode: 401 })로 인증 여부를 결정합니다.