refactor(backend): 에러 처리 통일

- src/utils/error.js 생성: 에러 응답 유틸리티 함수들
- reply.status() → reply.code() 통일 (auth, members, stats)
- Fastify 표준 에러 응답 패턴 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-21 13:43:26 +09:00
parent 430bf38c91
commit 2f30c67b93
5 changed files with 72 additions and 17 deletions

View file

@ -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: '로그인 처리 중 오류가 발생했습니다.' });
}
});

View file

@ -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 });
}
});
}

View file

@ -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: '통계 조회 실패' });
}
});
}

View file

@ -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);
}

View file

@ -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단계 | 중복 코드 제거 | 대기 |
---