현재 진행 중인 '항공사 HR SaaS 프로젝트(Control Tower)'의 프론트엔드 로직을 고도화하고, 팀원들과의 효율적인 협업을 위해 API 명세서를 재정비하는 시간을 가졌다.
특히 슈퍼 관리자와 일반 직원의 테마를 분리하는 작업과, AI를 활용해 프론트엔드 코드에서 백엔드 명세를 역추출하여 단기간에 문서를 완성한 경험을 정리해 본다.
1. 멀티 테마(Theme) 적용: 역할에 따른 UI 분리 전략
SaaS의 특성상 사용자가 '플랫폼 관리자(Super Admin)'**인지 '입점 항공사 직원(User/Admin)'인지에 따라 UI 경험이 달라야 했다. 기존에는 모든 페이지에 일괄적인 스타일이 적용되어 역할 구분이 모호하다는 이슈가 있었다.
- 해결: 유저 권한에 따라 테마를 동적으로 분기 처리
- Control Tower (슈퍼 관리자): 플랫폼 고유의 디자인 시스템과 브랜드 컬러 적용 (관리/공용 영역)
- Airline (항공사 직원): 각 항공사의 브랜드 컬러(Primary Color)를 반영한 커스텀 테마 적용
- 구현: 라우팅 진입 시 또는 유저 권한(Authority) 체크 단계에서 ThemeProvider가 적절한 테마 값을 주입하도록 로직을 리팩토링했다.
2. UX 개선: Modal에서 Full Page로의 전환
기존에 모달(Modal)로 구현했던 '서비스 도입 신청(Service Registration)' 기능을 별도의 풀 페이지(Full Page)로 변경했다.
- 이유: 신청 폼에 입력해야 할 정보가 많아짐에 따라 모달 창 내에서는 시각적으로 답답함을 주었고, 모바일 환경에서의 반응형 대응이 까다로웠다.
- 변경: /register 라우트를 신설하고 독립적인 페이지 컴포넌트로 분리했다. 이를 통해 사용자는 더 넓은 화면에서 집중해서 정보를 입력할 수 있게 되었고, 코드의 가독성 또한 개선되었다.
3. 프론트엔드 코드로 백엔드 API 명세 역설계 (Prompt Engineering)
프론트엔드 구현을 먼저 마친 상태에서, 본격적인 백엔드 개발에 착수하기 전 체계적인 RESTful API 설계가 필요했다. 이를 위해 처음부터 명세서를 새로 작성하는 대신, 이미 비즈니스 로직과 요구사항이 구체화된 '프론트엔드 코드를 진실의 원천(Single Source of Truth)'으로 삼아 AI를 활용해 백엔드 API 명세를 도출하는 전략을 택했다.
단순히 "API 정리해줘"라고 하는 대신, DDD(도메인 주도 설계) 관점과 **사용자 권한(Role)**을 명확히 분리하도록 구체적인 프롬프트를 설계하여 axios/fetch 호출부와 interface 정의를 분석시켰다.
💡 사용한 프롬프트 (Prompt)
Markdown
@Frontend 폴더 전체를 심층 분석하여, 현재 프론트엔드 로직에서 호출 중인 '백엔드 API 명세서'를 마크다운 표 형식으로 정리해주세요.
[분석 목표]
1. 프론트엔드 코드(axios, fetch, services, hooks 등)를 기반으로 스펙 역설계
2. 핵심 1 (DDD 그룹화): 단순 나열이 아닌 도메인(인사, 근태, 게시판 등) 기준으로 그룹화
3. 핵심 2 (권한 분석): 폴더 구조(admin/my)와 로직(Route Guard)을 통해 사용자 권한(Role) 추론
[상세 가이드라인]
- 도메인 분류: 파일명/폴더명이 다르더라도 같은 Entity를 다루면 하나의 도메인으로 통합
- 사용자 추론: pages/admin은 관리자, pages/my는 직원, 로직 내 user.role === 'ADMIN' 확인
- 데이터 추론: UI 바인딩 변수명(res.data.vacationDays)을 통해 타입 유추
[출력 형식]
## [도메인 명]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|:---|:---|:---:|:---|:---|:---|:---|
| 로그인 | 전체 | POST | /api/auth/login | {id, pw} | {token} | JWT 발급 |📊 결과: 역설계된 API 명세서 (Time-Saving)
이 과정을 통해 누락된 API를 찾고, 백엔드 팀과 싱크를 맞추는 데 드는 시간을 획기적으로 단축할 수 있었다.
- 정량적 성과: 통상적으로 백엔드 팀과 소통하며 문서를 기다렸다면 최소 1주일은 걸렸을 작업을, AI와 협업하여 프론트엔드 개발자 혼자서 단 이틀 만에 초안 작성부터 팀 공유까지 완료했다.
- 효율성 극대화: 내가 작성한 코드가 곧 문서가 되는 구조를 만들어, 문서 현행화에 드는 리소스를 줄이고 개발 본질에 집중할 수 있었다.
아래는 AI 분석을 통해 도출된 최종 명세서의 일부이다.
👉 API 명세서 전체 보기 (클릭하여 펼치기)
[1. 인증 및 계정 (Auth & Account)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 로그인 | 슈퍼, 전체 | POST | /api/auth/login |
{ userId, password, rememberMe } |
{ accessToken, role, name } |
userId는 사번 또는 관리자ID 사용 |
| 이메일 인증 발송 | Guest | POST | /api/auth/email/send |
{ email } |
{ sent: true } |
회원가입 Step 1. 인증번호 발송 |
| 아이디 중복 확인 | Guest | POST | /api/auth/check-emp-id |
{ userId } |
{ isValid: true, name } |
회원가입 Step 2. 사번 실명 확인 |
| 회원가입 요청 | Guest | POST | /api/auth/register |
{ userId, pw, name, airline... } |
{ status: 'PENDING' } |
관리자 승인 필요 |
| 계정 활성화 | 관리자 | POST | /api/auth/activate |
{ password, termsAgreed } |
{ success: true } |
최초 관리자 계정 활성화 |
| 서비스 가입 신청 | Guest | POST | /api/service-registrations |
FormData |
{ regId } |
신규 항공사 서비스 이용 신청 |
| 로그아웃 | 전체 | POST | /api/auth/logout |
{ refreshToken } |
{ success: true } |
Refresh Token 무효화 |
| 토큰 재발급 | 전체 | POST | /api/auth/refresh |
{ refreshToken } |
{ accessToken, refreshToken } |
Access Token 갱신 |
| 아이디 찾기 | Guest | POST | /api/auth/find-id |
{ name, email } |
{ userId, joinedDate } |
가려진 아이디 반환 |
| 비번 재설정 요청 | Guest | POST | /api/auth/reset-password |
{ userId, email } |
{ success: true } |
이메일 인증 코드 발송 |
| 코드 검증/변경 | Guest | POST | /api/auth/verify-password |
{ code, newPassword } |
{ success: true } |
새 비밀번호로 변경 |
[2. 인사 관리 (HR & Personnel)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 직원 상세 조회 | 전체 | GET | /api/employees/{id} |
- | { basicInfo, historyList } |
발령/업무 이력 포함 |
| 직원 목록 조회 | 관리자 | GET | /api/employees |
?dept={name}&search={key} |
{ content: [...], total } |
아바타 UI용 컬러 포함 |
| 부서 목록 조회 | 관리자 | GET | /api/departments |
?search={keyword} |
{ id, name, manager, stats } |
type: 운항/객실/정비 구분 |
| 부서 생성 | 관리자 | POST | /api/departments |
{ name, type, description } |
{ id } |
부서장 지정 로직 확인 필요 |
| 직원 정보 수정 | 관리자 | PATCH | /api/employees/{userId} |
{ dept, position, status } |
- | - |
| 직원 삭제(퇴사) | 관리자 | DELETE | /api/employees/{userId} |
- | { success: true } |
- |
| 부서 정보 수정 | 관리자 | PATCH | /api/departments/{deptId} |
{ name, managerId } |
- | - |
| 부서 삭제 | 관리자 | DELETE | /api/departments/{deptId} |
- | { success: true } |
- |
[3. 운항 및 스케줄 (Flight & Operations)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 전체 일정 조회 | 관리자 | GET | /api/flights |
?date={d}&dep={c}&arr={c} |
{ flights: [...] } |
isAssignedToMe 플래그 포함 |
| 직원 배정 하기 | 관리자 | GET | /api/flights/admin |
?month={yyyy-MM} |
{ schedules: [...] } |
기장 및 팀원 배정 상태 확인 |
| 크루 상세 정보 | 전체 | GET | /api/crew/{empId} |
- | { name, rank, leaveHistory } |
비행 자격 및 휴가 이력 포함 |
| 나의 일정 조회 | 직원 | GET | /api/flights/me |
?yearMonth={yyyy-MM} |
{ fights: [...] } |
로그인한 승무원 배정 스케줄 |
| 일정 상세 | 전체 | GET | /api/flights/{flightId} |
- | { flight, crew } |
상세 정보 및 크루 명단 |
| 일정 배정 | 관리자 | POST | /api/flights |
{ flightNum, aircraft... } |
{ flightId } |
신규 스케줄 등록 |
| 일정 취소/삭제 | 관리자 | DELETE | /api/flights/{flightId} |
Param: flightId |
{ success: true } |
운항 취소 또는 삭제 |
| 일정 수정 | 관리자 | PATCH | /api/flights/{flightId} |
{ time, aircraft, status } |
{ success: true } |
시간/기종/상태 변경 |
[4. 근태 및 휴가 (Attendance & Leave)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 내 근태 현황 | 직원 | GET | /api/attendance/my |
?month={yyyy-MM} |
{ stats, daily } |
달력 뷰 데이터 |
| 휴가 잔여 조회 | 직원 | GET | /api/leaves/summary |
- | { total, used, unused } |
- |
| 휴가 신청 | 직원 | POST | /api/leaves |
{ type, dates, reason } |
{ success: true } |
- |
| 휴가 신청 목록 | 관리자 | GET | /api/leaves/approvals |
?filter={status} |
{ requests: [...] } |
- |
| 휴가 승인/반려 | 관리자 | PATCH | /api/leaves/{id}/status |
{ status } |
{ success: true } |
- |
| 근태 정정 목록 | 관리자 | GET | /api/attendance/corrections |
?status={WAITING} |
{ data, meta } |
정정 요청 목록 (페이징) |
| 근태 정정 신청 | 직원 | POST | /api/attendance/corrections |
{ date, reason, time } |
{ correctionId } |
지각/결근 소명 신청 |
| 휴가 신청 상세 | 관리자 | GET | /api/leaves/{leaveId} |
- | { leave, employee } |
신청자 잔여 연차 확인 |
| 근태 정정 상세 | 관리자 | GET | /api/attendance/corrections/{id} |
- | { correction, original } |
기존 기록과 비교 |
| 근태 정정 승인 | 관리자 | PATCH | /api/attendance/corrections/{id} |
{ status } |
{ success: true } |
승인 시 근태 기록 자동 정정 |
| 근태 상세 조회 | 직원 | GET | /api/attendance/records/{date} |
- | { checkIn, checkOut } |
상세 출퇴근/근무지 조회 |
| 출퇴근 체크 | 직원 | POST | /api/attendance/commute |
{ userId, password } |
{ status, time } |
즉시 기록 생성 |
| 휴가 신청 취소 | 직원 | DELETE | /api/leaves/{leaveId} |
- | { success: true } |
'대기' 상태일 때만 가능 |
[5. 게시판 및 Q&A (Board & Communication)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 게시글 목록 | 전체 | GET | /api/boards |
?category={c}&page={p} |
{ posts, meta } |
탭 필터링 |
| 게시글 작성 | 관리자 | POST | /api/boards |
{ category, title, content } |
{ boardId } |
공지는 관리자만 작성 |
| Q&A 목록 | 전체 | GET | /api/qna |
?search={k} |
{ data, meta } |
비밀글 여부 포함 |
| Q&A 작성 | 직원 | POST | /api/qna |
{ title, content, isSecret } |
{ qnaId } |
비밀글 설정 가능 |
| 게시글 상세 | 전체 | GET | /api/boards/{id} |
- | { post, comments } |
댓글 목록 포함 |
| 게시글 수정 | 관리자 | PUT | /api/boards/{id} |
{ title, content } |
{ success: true } |
- |
| 게시글 삭제 | 관리자 | DELETE | /api/boards/{id} |
- | { success: true } |
댓글도 함께 삭제 |
| Q&A 상세 | 전체 | GET | /api/qna/{id} |
- | { question, answers } |
비밀글 권한 체크(403) |
| Q&A 수정 | 본인 | PATCH | /api/qna/{id} |
{ title, content } |
{ success: true } |
본인만 가능 |
| Q&A 삭제 | 본인/관리자 | DELETE | /api/qna/{id} |
- | { success: true } |
- |
| 답변 등록 | 관리자 | POST | /api/qna/{id}/answers |
{ content } |
{ answerId } |
- |
[6. 건강 관리 (Health & Safety)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 건강 대시보드 | 직원 | GET | /api/health/my |
- | { score, alert } |
개인 건강 점수 |
| 스트레스 설문 | 직원 | POST | /api/health/survey |
{ answers } |
{ result } |
결과 즉시 반환 |
| 건강 정보 제출 | 직원 | POST | /api/health/documents |
FormData |
{ success: true } |
증빙 서류 제출 |
| 직원 건강 관리 | 관리자 | GET | /api/health/employees |
?dept={d} |
{ data, meta } |
부서별 리스트 |
| 프로그램 목록 | 전체 | GET | /api/health/programs |
?category={c} |
{ data: [{id, name}] } |
상담/운동/휴식 |
| 프로그램 상세 | 전체 | GET | /api/health/programs/{id} |
- | { guide, schedule } |
- |
| 프로그램 신청 | 직원 | POST | /api/health/applications |
{ programId, date } |
{ applicationId } |
- |
| 내 신청 내역 | 직원 | GET | /api/health/applications/my |
- | { data, meta } |
진행 상태 조회 |
| 프로그램 승인 | 관리자 | PATCH | /api/health/applications/{id} |
{ status, managerId } |
{ success: true } |
담당자 배정 포함 |
| 직원 건강 상세 | 관리자 | GET | /api/health/employees/{id} |
- | { vitals, history } |
검진 결과 통합 조회 |
[7. 슈퍼 관리자 및 설정 (Super Admin & Settings)]
| 기능 | 사용자 | Method | Endpoint | Request | Response | 상세 설명 |
|---|---|---|---|---|---|---|
| 테넌트 목록 | 슈퍼 | GET | /api/super-admin/tenants |
?search={name} |
{ data, meta } |
입점사 상태 조회 |
| 가입 신청 목록 | 슈퍼 | GET | /api/super-admin/registrations |
- | { data, meta } |
신규 신청 내역 |
| 가입 승인/반려 | 슈퍼 | PATCH | /api/super-admin/registrations/{id} |
{ status } |
{ success: true } |
승인 시 Tenant 생성 |
| 테넌트 상세 | 슈퍼 | GET | /api/super-admin/tenants/{id} |
- | { tenant, logs } |
활동 로그 포함 |
| 대시보드 | 슈퍼 | GET | /api/super-admin/dashboard |
- | { stats } |
전체 현황 요약 |
| 내 프로필 조회 | 전체 | GET | /api/users/me |
- | { profile, preferences } |
- |
| 보안/설정 조회 | 전체 | GET | /api/users/me/security |
- | { theme, language } |
- |
| 공통코드 목록 | 관리자 | GET | /api/admin/common-codes |
?group={code} |
{ data } |
- |
| 공통코드 수정 | 관리자 | PATCH | /api/admin/common-codes/{code} |
{ name, status } |
{ success: true } |
- |
| 내 프로필 수정 | 전체 | PUT | /api/users/me |
FormData |
{ avatarUrl } |
- |
💡 회고: 나무가 아닌 숲을 보게 되다
오늘은 단순히 기능을 구현하는 것을 넘어, '코드 분석 자동화'를 통해 협업의 효율을 높였다는 점에서 의미가 컸다.
1. RESTful API에 대한 재정립
명세서를 정리하며 리소스 네이밍 규칙에 대해 다시 배우게 되었다. 예를 들어 api/program과 같이 단수형을 쓰던 것을 api/programs와 같이 복수형(Collection)으로 명명해야 한다는 표준을 익혔고, Request/Response의 구조를 명확히 이해하게 되었다.
2. 프론트엔드와 전체 시스템의 흐름
이전에는 화면 구현에만 집중했다면, API를 직접 명세하고 정리하면서 세세한 유스케이스(Use Case)와 데이터의 흐름이 눈에 들어오기 시작했다. 프론트엔드가 단순히 화면을 그리는 역할이 아니라, 전체 서비스의 흐름을 관장하는 중요한 포지션임을 다시 한번 깨달았다.
3. AI 협업을 통한 '1인 개발'의 한계 극복
이번 작업을 통해 "혼자서도 할 수 있다"는 자신감을 얻었다. 방대한 API를 혼자 정리하는 것은 엄두가 안 나는 일이었지만, AI를 적절히 활용하니 단 이틀 만에 끝낼 수 있었다. 개발자가 도구를 어떻게 쓰느냐에 따라 생산성이 몇 배가 될 수 있음을 체감했다.
'Projects > Team Projects' 카테고리의 다른 글
| [Control Tower] 1차 개발 회고: 협업의 가치와 성장을 기록하다 (0) | 2026.02.10 |
|---|---|
| [Control Tower] 개발 효율은 AI로, 팀 성장은 믿음으로 (Cursor, DDD, Git 회고) (0) | 2026.01.23 |
| [Control Tower] Git 충돌 해결부터 사이드바 권한 분리까지 (0) | 2026.01.21 |
| [Control Tower] Cursor와 Figma를 활용한 효율적인 UI/UX 프로토타이핑 (0) | 2026.01.13 |
| [Control Tower] Day 4: 깃허브 이슈 & PR, 노션에 '무료로' 자동 동기화하기 (삽질기 포함) (0) | 2026.01.09 |