refactor: API 서버 구조 개선 및 표준화
- 통합 에러 처리 시스템 구축: * utils/errorHandler.js: ApiError 클래스 및 에러 미들웨어 * 데이터베이스, 유효성 검사, 권한 에러 표준화 * 비동기 함수 래퍼 (asyncHandler) 추가 - 응답 포맷터 시스템 구축: * utils/responseFormatter.js: 일관된 API 응답 형식 * 성공, 페이지네이션, 인증, 파일업로드 등 전용 포맷터 * Express 응답 확장 미들웨어 - 유효성 검사 시스템 구축: * utils/validator.js: 스키마 기반 유효성 검사 * 필수 필드, 타입, 길이, 형식 검사 함수들 * 일반적인 스키마 정의 (사용자, 프로젝트, 작업보고서 등) - 코드 정리 및 표준화: * 삭제된 테이블 참조 제거 (work_report_audit_log 등) * 대문자 테이블명을 소문자로 통일 (Users -> users) * authController.js에 새로운 유틸리티 적용 예시 - 미들웨어 통합: * index.js에 에러 핸들러 및 응답 포맷터 적용 * 헬스체크 엔드포인트 개선
This commit is contained in:
188
api.hyungi.net/utils/responseFormatter.js
Normal file
188
api.hyungi.net/utils/responseFormatter.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// utils/responseFormatter.js - 통합 응답 포맷터
|
||||
|
||||
/**
|
||||
* 성공 응답 포맷터
|
||||
*/
|
||||
const successResponse = (data = null, message = '요청이 성공적으로 처리되었습니다.', meta = null) => {
|
||||
const response = {
|
||||
success: true,
|
||||
message,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
if (data !== null) {
|
||||
response.data = data;
|
||||
}
|
||||
|
||||
if (meta) {
|
||||
response.meta = meta;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* 페이지네이션 응답 포맷터
|
||||
*/
|
||||
const paginatedResponse = (data, totalCount, page = 1, limit = 10, message = '데이터 조회 성공') => {
|
||||
const totalPages = Math.ceil(totalCount / limit);
|
||||
|
||||
return successResponse(data, message, {
|
||||
pagination: {
|
||||
currentPage: parseInt(page),
|
||||
totalPages,
|
||||
totalCount: parseInt(totalCount),
|
||||
limit: parseInt(limit),
|
||||
hasNextPage: page < totalPages,
|
||||
hasPrevPage: page > 1
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 리스트 응답 포맷터
|
||||
*/
|
||||
const listResponse = (items, message = '목록 조회 성공') => {
|
||||
return successResponse(items, message, {
|
||||
count: items.length
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 생성 응답 포맷터
|
||||
*/
|
||||
const createdResponse = (data, message = '데이터가 성공적으로 생성되었습니다.') => {
|
||||
return successResponse(data, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* 업데이트 응답 포맷터
|
||||
*/
|
||||
const updatedResponse = (data = null, message = '데이터가 성공적으로 업데이트되었습니다.') => {
|
||||
return successResponse(data, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* 삭제 응답 포맷터
|
||||
*/
|
||||
const deletedResponse = (message = '데이터가 성공적으로 삭제되었습니다.') => {
|
||||
return successResponse(null, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* 통계 응답 포맷터
|
||||
*/
|
||||
const statsResponse = (stats, period = null, message = '통계 조회 성공') => {
|
||||
const meta = {};
|
||||
if (period) {
|
||||
meta.period = period;
|
||||
}
|
||||
meta.generatedAt = new Date().toISOString();
|
||||
|
||||
return successResponse(stats, message, meta);
|
||||
};
|
||||
|
||||
/**
|
||||
* 인증 응답 포맷터
|
||||
*/
|
||||
const authResponse = (user, token, redirectUrl = null, message = '로그인 성공') => {
|
||||
const data = {
|
||||
user,
|
||||
token
|
||||
};
|
||||
|
||||
if (redirectUrl) {
|
||||
data.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
return successResponse(data, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* 파일 업로드 응답 포맷터
|
||||
*/
|
||||
const uploadResponse = (fileInfo, message = '파일 업로드 성공') => {
|
||||
return successResponse({
|
||||
filename: fileInfo.filename,
|
||||
originalName: fileInfo.originalname,
|
||||
size: fileInfo.size,
|
||||
mimetype: fileInfo.mimetype,
|
||||
path: fileInfo.path,
|
||||
uploadedAt: new Date().toISOString()
|
||||
}, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* 헬스체크 응답 포맷터
|
||||
*/
|
||||
const healthResponse = (status = 'healthy', services = {}) => {
|
||||
return successResponse({
|
||||
status,
|
||||
services,
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
version: process.version
|
||||
}, `서버 상태: ${status}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Express 응답 확장 미들웨어
|
||||
*/
|
||||
const responseMiddleware = (req, res, next) => {
|
||||
// 성공 응답 헬퍼들을 res 객체에 추가
|
||||
res.success = (data, message, meta) => {
|
||||
return res.json(successResponse(data, message, meta));
|
||||
};
|
||||
|
||||
res.paginated = (data, totalCount, page, limit, message) => {
|
||||
return res.json(paginatedResponse(data, totalCount, page, limit, message));
|
||||
};
|
||||
|
||||
res.list = (items, message) => {
|
||||
return res.json(listResponse(items, message));
|
||||
};
|
||||
|
||||
res.created = (data, message) => {
|
||||
return res.status(201).json(createdResponse(data, message));
|
||||
};
|
||||
|
||||
res.updated = (data, message) => {
|
||||
return res.json(updatedResponse(data, message));
|
||||
};
|
||||
|
||||
res.deleted = (message) => {
|
||||
return res.json(deletedResponse(message));
|
||||
};
|
||||
|
||||
res.stats = (stats, period, message) => {
|
||||
return res.json(statsResponse(stats, period, message));
|
||||
};
|
||||
|
||||
res.auth = (user, token, redirectUrl, message) => {
|
||||
return res.json(authResponse(user, token, redirectUrl, message));
|
||||
};
|
||||
|
||||
res.upload = (fileInfo, message) => {
|
||||
return res.json(uploadResponse(fileInfo, message));
|
||||
};
|
||||
|
||||
res.health = (status, services) => {
|
||||
return res.json(healthResponse(status, services));
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
successResponse,
|
||||
paginatedResponse,
|
||||
listResponse,
|
||||
createdResponse,
|
||||
updatedResponse,
|
||||
deletedResponse,
|
||||
statsResponse,
|
||||
authResponse,
|
||||
uploadResponse,
|
||||
healthResponse,
|
||||
responseMiddleware
|
||||
};
|
||||
Reference in New Issue
Block a user