From 2f30c67b939069ec5ccf980a794c7a71d262636f Mon Sep 17 00:00:00 2001 From: caadiq Date: Wed, 21 Jan 2026 13:43:26 +0900 Subject: [PATCH] =?UTF-8?q?refactor(backend):=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/utils/error.js 생성: 에러 응답 유틸리티 함수들 - reply.status() → reply.code() 통일 (auth, members, stats) - Fastify 표준 에러 응답 패턴 적용 Co-Authored-By: Claude Opus 4.5 --- backend/src/routes/auth.js | 8 ++--- backend/src/routes/members/index.js | 10 +++--- backend/src/routes/stats/index.js | 2 +- backend/src/utils/error.js | 50 +++++++++++++++++++++++++++++ docs/refactoring.md | 19 +++++++---- 5 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 backend/src/utils/error.js diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index a6bcb2c..2226f3b 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -42,7 +42,7 @@ export default async function authRoutes(fastify, opts) { const { username, password } = request.body || {}; if (!username || !password) { - return reply.status(400).send({ error: '아이디와 비밀번호를 입력해주세요.' }); + return reply.code(400).send({ error: '아이디와 비밀번호를 입력해주세요.' }); } try { @@ -52,14 +52,14 @@ export default async function authRoutes(fastify, opts) { ); if (users.length === 0) { - return reply.status(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }); + return reply.code(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }); } const user = users[0]; const isValidPassword = await bcrypt.compare(password, user.password_hash); if (!isValidPassword) { - return reply.status(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }); + return reply.code(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }); } // JWT 토큰 생성 @@ -75,7 +75,7 @@ export default async function authRoutes(fastify, opts) { }; } catch (err) { fastify.log.error(err); - return reply.status(500).send({ error: '로그인 처리 중 오류가 발생했습니다.' }); + return reply.code(500).send({ error: '로그인 처리 중 오류가 발생했습니다.' }); } }); diff --git a/backend/src/routes/members/index.js b/backend/src/routes/members/index.js index 7cba570..0d76266 100644 --- a/backend/src/routes/members/index.js +++ b/backend/src/routes/members/index.js @@ -53,7 +53,7 @@ export default async function membersRoutes(fastify, opts) { return result; } catch (err) { fastify.log.error(err); - return reply.status(500).send({ error: '멤버 목록 조회 실패' }); + return reply.code(500).send({ error: '멤버 목록 조회 실패' }); } }); @@ -88,7 +88,7 @@ export default async function membersRoutes(fastify, opts) { `, [decodeURIComponent(name)]); if (members.length === 0) { - return reply.status(404).send({ error: '멤버를 찾을 수 없습니다' }); + return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' }); } const member = members[0]; @@ -106,7 +106,7 @@ export default async function membersRoutes(fastify, opts) { }; } catch (err) { fastify.log.error(err); - return reply.status(500).send({ error: '멤버 조회 실패' }); + return reply.code(500).send({ error: '멤버 조회 실패' }); } }); @@ -140,7 +140,7 @@ export default async function membersRoutes(fastify, opts) { ); if (existing.length === 0) { - return reply.status(404).send({ error: '멤버를 찾을 수 없습니다' }); + return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' }); } const memberId = existing[0].id; @@ -218,7 +218,7 @@ export default async function membersRoutes(fastify, opts) { return { message: '멤버 정보가 수정되었습니다', id: memberId }; } catch (err) { fastify.log.error(err); - return reply.status(500).send({ error: '멤버 수정 실패: ' + err.message }); + return reply.code(500).send({ error: '멤버 수정 실패: ' + err.message }); } }); } diff --git a/backend/src/routes/stats/index.js b/backend/src/routes/stats/index.js index 72d526f..318ceaa 100644 --- a/backend/src/routes/stats/index.js +++ b/backend/src/routes/stats/index.js @@ -70,7 +70,7 @@ export default async function statsRoutes(fastify, opts) { }; } catch (err) { fastify.log.error(err); - return reply.status(500).send({ error: '통계 조회 실패' }); + return reply.code(500).send({ error: '통계 조회 실패' }); } }); } diff --git a/backend/src/utils/error.js b/backend/src/utils/error.js new file mode 100644 index 0000000..fe9b784 --- /dev/null +++ b/backend/src/utils/error.js @@ -0,0 +1,50 @@ +/** + * 에러 응답 유틸리티 + * 일관된 에러 응답 형식 제공 + */ + +/** + * 에러 응답 전송 + * @param {object} reply - Fastify reply 객체 + * @param {number} statusCode - HTTP 상태 코드 + * @param {string} message - 에러 메시지 + * @returns {object} 에러 응답 + */ +export function sendError(reply, statusCode, message) { + return reply.code(statusCode).send({ error: message }); +} + +/** + * 400 Bad Request + */ +export function badRequest(reply, message = '잘못된 요청입니다.') { + return sendError(reply, 400, message); +} + +/** + * 401 Unauthorized + */ +export function unauthorized(reply, message = '인증이 필요합니다.') { + return sendError(reply, 401, message); +} + +/** + * 404 Not Found + */ +export function notFound(reply, message = '리소스를 찾을 수 없습니다.') { + return sendError(reply, 404, message); +} + +/** + * 409 Conflict + */ +export function conflict(reply, message = '이미 존재하는 리소스입니다.') { + return sendError(reply, 409, message); +} + +/** + * 500 Internal Server Error + */ +export function serverError(reply, message = '서버 오류가 발생했습니다.') { + return sendError(reply, 500, message); +} diff --git a/docs/refactoring.md b/docs/refactoring.md index e39e201..6fc8335 100644 --- a/docs/refactoring.md +++ b/docs/refactoring.md @@ -43,13 +43,18 @@ --- -### 4단계: 에러 처리 통일 -- [ ] 에러 응답 유틸리티 생성 (`src/utils/error.js`) -- [ ] `reply.status()` vs `reply.code()` 통일 -- [ ] `console.error` → `fastify.log.error` 변경 +### 4단계: 에러 처리 통일 ✅ 완료 +- [x] 에러 응답 유틸리티 생성 (`src/utils/error.js`) +- [x] `reply.status()` → `reply.code()` 통일 +- [ ] `console.error` → `fastify.log.error` 변경 (추후) -**관련 파일:** -- 모든 라우트 파일 +**생성된 파일:** +- `src/utils/error.js` - sendError, badRequest, unauthorized, notFound, conflict, serverError + +**수정된 파일:** +- `src/routes/auth.js` - reply.status → reply.code +- `src/routes/stats/index.js` - reply.status → reply.code +- `src/routes/members/index.js` - reply.status → reply.code --- @@ -72,7 +77,7 @@ | 1단계 | 설정 통합 | ✅ 완료 | | 2단계 | N+1 쿼리 최적화 | ✅ 완료 | | 3단계 | 서비스 레이어 분리 | ✅ 완료 | -| 4단계 | 에러 처리 통일 | 대기 | +| 4단계 | 에러 처리 통일 | ✅ 완료 | | 5단계 | 중복 코드 제거 | 대기 | ---