Skip to content

[박수훈] sprint5 리팩토링 및 sprint6 제출#9

Open
mdeeno wants to merge 12 commits intocodeit-sprint-fullstack:express-박수훈from
mdeeno:express-박수훈-sprint6

Hidden character warning

The head ref may contain hidden characters: "express-\ubc15\uc218\ud6c8-sprint6"
Open

[박수훈] sprint5 리팩토링 및 sprint6 제출#9
mdeeno wants to merge 12 commits intocodeit-sprint-fullstack:express-박수훈from
mdeeno:express-박수훈-sprint6

Conversation

@mdeeno
Copy link
Copy Markdown
Collaborator

@mdeeno mdeeno commented Jan 20, 2026

1. 스프린트 미션 요구사항

기본 요구사항

중고마켓

  • mongoDB에서 PostgreSQL을 사용하도록 코드를 마이그레이션 해주세요.

공통

  • PostgreSQL를 이용해 주세요.
  • 데이터 모델 간의 관계를 고려하여 onDelete를 설정해 주세요.
  • 데이터베이스 시딩 코드를 작성해 주세요.
  • 각 API에 적절한 에러 처리를 해 주세요.
  • 각 API 응답에 적절한 상태 코드를 리턴하도록 해 주세요.

자유게시판

  • Article 스키마를 작성해 주세요.
    • id, title, content, createdAt, updatedAt 필드를 가집니다.
  • 게시글 등록 API를 만들어 주세요.
    • title, content를 입력해 게시글을 등록합니다.
  • 게시글 조회 API를 만들어 주세요.
    • id, title, content, createdAt를 조회합니다.
  • 게시글 수정 API를 만들어 주세요.
  • 게시글 삭제 API를 만들어 주세요.
  • 게시글 목록 조회 API를 만들어 주세요.
    • id, title, content, createdAt를 조회합니다.
    • offset 방식의 페이지네이션 기능을 포함해 주세요.
    • 최신순(recent)으로 정렬할 수 있습니다.
    • title, content에 포함된 단어로 검색할 수 있습니다.

댓글

  • 댓글 등록 API를 만들어 주세요.
    • content를 입력하여 댓글을 등록합니다.
    • 중고마켓, 자유게시판 댓글 등록 API를 따로 만들어 주세요.
  • 댓글 수정 API를 만들어 주세요.
    • PATCH 메서드를 사용해 주세요.
  • 댓글 삭제 API를 만들어 주세요.
  • 댓글 목록 조회 API를 만들어 주세요.
    • id, content, createdAt 를 조회합니다.
    • cursor 방식의 페이지네이션 기능을 포함해 주세요.
    • 중고마켓, 자유게시판 댓글 목록 조회 API를 따로 만들어 주세요

2. 주요 변경사항

  • sprint5 기능별 구조로 리팩토링(레포지토리 패턴) 및 sprint6 요구사항 구현

3. 멘토님에게 남길 메시지

  • 기존에 계층형 아키텍쳐로 sprint5를 구현하였으나, 해당 아키텍쳐에 이해가 부족하여... 기능별 계층구조로 sprint5 리팩토링하고 sprint6 진행하였습니다.
  • 리팩토링하면서 npm run dev 시 각 파일에 import 했던 요소 중 Alias(#)를 지정하여 사용한 절대경로들이 에러가 떠서 대부분 상대경로로 바꿨습니다. 이에 꽤 많은 시간이 걸렸는데 혹시 한번에 수정/변경 하는 방법이 있을까요?

@mdeeno mdeeno requested a review from reach0908 January 20, 2026 01:34
@mdeeno mdeeno self-assigned this Jan 20, 2026
@mdeeno mdeeno added the 진행중 아직 스프린트 미션 제출일이 아닙니다. 새로 커밋된 내용에 대해 코드리뷰 해주세요! (2주 이상의 스프린트 미션 중간점검 PR 제출시 사용합니다.) label Jan 20, 2026
@mdeeno mdeeno added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요! and removed 진행중 아직 스프린트 미션 제출일이 아닙니다. 새로 커밋된 내용에 대해 코드리뷰 해주세요! (2주 이상의 스프린트 미션 중간점검 PR 제출시 사용합니다.) labels Jan 24, 2026
@mdeeno mdeeno changed the title [박수훈] sprint6 제출 [박수훈] sprint5 리팩토링 및 sprint6 제출 Jan 24, 2026
@mdeeno mdeeno added 진행중 아직 스프린트 미션 제출일이 아닙니다. 새로 커밋된 내용에 대해 코드리뷰 해주세요! (2주 이상의 스프린트 미션 중간점검 PR 제출시 사용합니다.) and removed 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요! labels Jan 25, 2026
@mdeeno mdeeno added 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요! and removed 진행중 아직 스프린트 미션 제출일이 아닙니다. 새로 커밋된 내용에 대해 코드리뷰 해주세요! (2주 이상의 스프린트 미션 중간점검 PR 제출시 사용합니다.) labels Jan 25, 2026
@reach0908
Copy link
Copy Markdown
Collaborator

  1. 기존에 계층형 아키텍쳐로 sprint5를 구현하였으나, 해당 아키텍쳐에 이해가 부족하여... 기능별 계층구조로 sprint5 리팩토링하고 sprint6 진행하였습니다.

-> 이번 멘토링에서 설명을 드리긴했지만 아직은 어렵다면 지금 프로젝트에서 진행중인 수준의 아키텍처만으로도 아직은 충분할 것 같습니다. 하지만 코드가 길어지거나 반복되는 코드가 많아진다면 항상 이걸 분리해서 간결하게 만든다 는 생각을 해보시면 좋을 것 같아요

  1. 리팩토링하면서 npm run dev 시 각 파일에 import 했던 요소 중 Alias(#)를 지정하여 사용한 절대경로들이 에러가 떠서 대부분 상대경로로 바꿨습니다. 이에 꽤 많은 시간이 걸렸는데 혹시 한번에 수정/변경 하는 방법이 있을까요?
SCR-20260128-ociw vscode의 find and replace라는 기능을 사용하면 좋습니다. 우선은 alias 별칭 설정에 오류가 왜 생겼는지 파악하고 이를 수정하는 방법을 찾는 것이 더 좋을 수도 있습니다. 우선 전체 변경기능을 이용해서 기존의 별칭들을 상대경로가 아닌 src/ 와 같이 절대경로로 변경을 먼저하시면 좋을 것 같아요 상대경로 변경은 AI를 활용하는게 빠를 것 같습니다.

Copy link
Copy Markdown
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

전체적으로 PostgreSQL 마이그레이션과 Repository 패턴 적용이 잘 되어있습니다! 다만 몇 가지 오타와 누락된 부분이 있어 코멘트 남깁니다.

상세 리뷰는 아래 인라인 코멘트들을 확인해주세요.

Copy link
Copy Markdown
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Draft Review

this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] HttpException 계층 구조가 잘 설계되어 있습니다!

Error.captureStackTrace로 스택 트레이스를 깔끔하게 처리하고, statusCode, details 등 필요한 정보를 담을 수 있게 확장한 점이 좋습니다.

CONFLICT: 409,

INTERNAL_SERVER_ERROR: 500,
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] HTTP 상태 코드를 상수로 관리하는 좋은 패턴입니다!

매직 넘버 대신 HTTP_STATUS.CREATED, HTTP_STATUS.NOT_FOUND 등으로 사용하면 코드 가독성이 높아지고 오타로 인한 버그를 방지할 수 있습니다.

return prisma.comment.findMany({
where,
take: Number(take) || 10,
// Cursor 방식 페이지네이션 적용
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] Cursor 기반 페이지네이션을 잘 구현했습니다!

skip: 1, cursor: { id: cursor }로 커서 이후 데이터를 가져오는 방식이 올바르게 적용되어 있습니다. 대량의 데이터에서 offset 방식보다 성능이 좋습니다.

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] 시딩 스크립트가 잘 작성되어 있습니다!

  • 환경 체크로 프로덕션 실수 방지
  • 트랜잭션으로 기존 데이터 초기화
  • faker.js로 현실적인 더미 데이터 생성
  • 적절한 에러 핸들링과 연결 해제

개발 환경 테스트에 매우 유용합니다.


const adapter = new PrismaPg(pool);

export const prisma = new PrismaClient({ adapter });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] Prisma 설정이 깔끔합니다!

Pg Pool과 PrismaPg 어댑터를 활용한 PostgreSQL 연결 구성이 잘 되어 있습니다. 싱글톤 패턴으로 prisma 인스턴스를 export하여 앱 전체에서 재사용하는 것도 좋습니다.

article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade)

productId String?
product Product? @relation(fields: [productId], references: [id], onDelete: Cascade)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] onDelete: Cascade 설정이 잘 되어 있습니다!

부모(Article/Product) 삭제 시 관련 댓글들이 자동으로 삭제되어 데이터 무결성이 유지됩니다. 고아 레코드 문제를 방지하는 좋은 설계입니다.


// 게시글 등록
function createArticle(data) {
return prisma.article.create({ data });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] Repository 패턴이 잘 적용되어 있습니다!

데이터베이스 접근 로직을 Repository로 분리하여:

  • 라우터는 HTTP 요청/응답만 담당
  • Repository는 데이터 조회/조작만 담당

이렇게 관심사를 분리하면 테스트와 유지보수가 쉬워집니다.

PORT: z.coerce.number().min(1000).max(65535).default(5001),

DATABASE_URL: z.string().url(),
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Good] Zod로 환경변수를 검증하는 좋은 패턴입니다!

서버 시작 시점에 환경변수 유효성을 체크하여, 런타임 에러를 방지할 수 있습니다. z.coerce.number()로 문자열을 숫자로 변환하는 것도 깔끔합니다.

Copy link
Copy Markdown
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가 칭찬 코멘트

@@ -0,0 +1,53 @@
import { prisma } from '#db/prisma.js';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존의 다른 파일들처럼 article.repository.ts와 같은 파일 네이밍을 유지하면 좋을 것 같습니다!

전체적으로 router, middleware, repo 들의 네이밍이 모두 컨벤션이 달라서 맞추면 좋을듯 합니다


// 통과
next();
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Zod를 활용한 유효성 검사 미들웨어로 개선할 수 있습니다!

현재 수동으로 각 필드를 검사하고 있는데, Zod 스키마를 활용하면 더 간결하고 재사용 가능합니다.

1. 스키마 정의:

// schemas/product.schema.js
import { z } from 'zod';

export const createProductSchema = z.object({
  name: z.string().min(1, '이름은 필수입니다'),
  description: z.string().min(1, '설명은 필수입니다'),
  price: z.number().positive('가격은 0보다 커야 합니다'),
  tags: z.array(z.string()).optional(),
});

// update는 모든 필드가 optional
export const updateProductSchema = createProductSchema.partial();

2. 범용 validate 미들웨어:

// middleware/validate.js
import { z } from 'zod';
import { BadRequestException } from '../exceptions/BadRequsetException.js';

export const validate = (schema) => (req, res, next) => {
  try {
    req.body = schema.parse(req.body);
    next();
  } catch (error) {
    if (error instanceof z.ZodError) {
      throw new BadRequestException(
        'Validation failed',
        error.flatten().fieldErrors
      );
    }
    next(error);
  }
};

3. 라우터에서 사용:

import { validate } from '../middleware/validate.js';
import { createProductSchema, updateProductSchema } from '../schemas/product.schema.js';

router.post('/', validate(createProductSchema), async (req, res) => {
  // req.body는 이미 검증 및 타입 변환됨
  const product = await productRepository.createProduct(req.body);
  res.status(HTTP_STATUS.CREATED).json(product);
});

router.patch('/:id', validate(updateProductSchema), async (req, res) => {
  // partial() 덕분에 일부 필드만 있어도 OK
});

장점:

  • 스키마 재사용 (.partial(), .pick(), .extend() 활용)
  • 타입 변환 자동화 (z.coerce.number() 등)
  • 상세한 에러 메시지 자동 생성
  • 코드 중복 대폭 감소

Copy link
Copy Markdown
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zod validation 제안

Comment on lines +71 to +73
if (!exists) {
throw new NotFoundException(ERROR_MESSAGE.RESOURCE_NOT_FOUND);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인라인으로 if문을 작성하실때와 괄호를 사용하실때가 나뉘는 것 같은데 통일하면 코드 가독성이 더 오를 것 같습니다!

저는 항상 사용하는 편이 취향이긴합니다!

@@ -0,0 +1,20 @@
export default [
js.configs.recommended,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] js가 import되지 않아 ReferenceError가 발생합니다.

// 상단에 추가
import js from '@eslint/js';

import { defineConfig, env } from 'prisma/config';

export default defineConfig({
schema: 'prisma/shema.prisma',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 경로 오타

'prisma/shema.prisma''prisma/schema.prisma'

}

// 자유게시판
model Article {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] Article 스키마 필드 누락

체크리스트 요구사항에 따르면 imagelikeCount 필드가 필요합니다.

model Article {
  id        String   @id @default(uuid())
  title     String
  content   String
  image     String?          // 추가 필요
  likeCount Int      @default(0)  // 좋아요순 정렬용
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  comments Comment[]
}

server.js Outdated
console.log(`\n${signal} 신호를 받았습니다. 서버를 종료합니다.`);
server.close(async () => {
console.log('HTTP 서버가 닫혔습니다.');
await disconnectDB();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] disconnectDB() 함수가 정의되지 않았습니다.

// 상단에 추가
import { prisma } from '#db/prisma.js';

// 여기서 수정
await prisma.$disconnect();

});
} catch (error) {
if (error instanceof z.ZodError) {
const { fieldErrors } = flateenError(error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 함수명/변수명 오타

  • flateenError → Zod에는 이런 함수가 없습니다
  • filedErrorsfieldErrors
if (error instanceof z.ZodError) {
  const { fieldErrors } = error.flatten();
  console.error('환경 변수 검증 실패:', fieldErrors);
}

if (error instanceof HttpException) {
return res.status(error.statusCode).json({
message: error.message,
stack: config.ENV === 'development' ? error.stack : undefined,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] config가 import되지 않아 ReferenceError가 발생합니다.

// 상단에 추가
import { config } from '../config/config.js';

그리고 config.ENV가 아니라 config.NODE_ENV입니다.


return prisma.product.findMany({
where,
orderBy: {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 오타 2개

  1. 'dest''desc' (Prisma는 desc만 인식)
  2. Line 33: take: Number(take) || 0 → 기본값 0이면 아무것도 반환 안됨
return prisma.product.findMany({
  where,
  orderBy: {
    createdAt: orderBy === 'recent' ? 'desc' : 'asc',
  },
  skip: Number(skip) || 0,
  take: Number(take) || 10,  // 0 → 10
});

// 라우터 연결
router.use('/products', productRouter);
router.use('/articles', articleRouter);
router.use('/articles', commentRouter);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 댓글 라우터 경로 구조 문제

현재 중고마켓 댓글 경로가 /api/articles/products/:productId가 됩니다.
의도한 경로는 /api/products/:productId/comments일 것 같습니다.

현재:

router.use('/products', productRouter);
router.use('/articles', articleRouter);
router.use('/articles', commentRouter);  // 모든 댓글이 /articles 하위

해결 방안:

  1. 댓글 라우터를 별도 경로로 분리
  2. 또는 각 리소스 라우터 내에서 댓글 처리

@@ -0,0 +1,31 @@
// 유효성 검사

import { BadRequestException } from '../errors/httpException.js';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] import 경로가 잘못되었습니다.

../errors/httpException.js 경로는 존재하지 않습니다.

// 수정
import { BadRequestException } from '../exceptions/BadRequsetException.js';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants