# API Explorer 상세 설계서

> **문서 버전**: 1.0
> **작성일**: 2025-12-17
> **대상 프로젝트**: mng (Plain Laravel + Blade + Tailwind)

---

## 1. 개요

### 1.1 목적
Swagger UI의 한계를 극복하고, 개발팀의 API 관리 효율성을 높이기 위한 커스텀 API Explorer 개발

### 1.2 Swagger 대비 개선점

| 기능 | Swagger UI | API Explorer |
|------|------------|--------------|
| 검색 | 엔드포인트명만 | 풀텍스트 (설명, 파라미터 포함) |
| 그룹핑 | 태그만 | 태그 + 상태 + 메서드 + 커스텀 |
| 즐겨찾기 | ❌ | ⭐ 사용자별 북마크 |
| 요청 템플릿 | ❌ | 💾 저장/공유 가능 |
| 히스토리 | ❌ | 📋 최근 요청 + 재실행 |
| 환경 전환 | 수동 | 🔄 원클릭 전환 |

### 1.3 기술 스택
- **Backend**: Laravel 12 (mng 프로젝트)
- **Frontend**: Blade + Tailwind CSS + HTMX
- **Data Source**: OpenAPI 3.0 JSON (`api/storage/api-docs/api-docs.json`)
- **HTTP Client**: Guzzle (서버사이드 프록시)

---

## 2. 시스템 아키텍처

### 2.1 전체 구조

```
┌─────────────────────────────────────────────────────────────────┐
│                        API Explorer (mng)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────┐  │
│  │   Browser    │───>│   Laravel    │───>│   API Server     │  │
│  │   (HTMX)     │<───│   (Proxy)    │<───│   (api/)         │  │
│  └──────────────┘    └──────────────┘    └──────────────────┘  │
│         │                   │                                    │
│         │            ┌──────┴──────┐                            │
│         │            │             │                             │
│         │      ┌─────┴─────┐ ┌─────┴─────┐                      │
│         │      │  SQLite   │ │  OpenAPI  │                      │
│         │      │  (Local)  │ │   JSON    │                      │
│         │      └───────────┘ └───────────┘                      │
│         │                                                        │
│  ┌──────┴───────────────────────────────────────────────────┐  │
│  │                     Local Storage                          │  │
│  │  • 환경 설정 (현재 서버)                                    │  │
│  │  • UI 상태 (패널 크기, 필터)                               │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

### 2.2 데이터 흐름

```
1. OpenAPI 파싱
   api-docs.json ──> OpenApiParserService ──> 구조화된 API 데이터

2. API 요청 프록시
   Browser ──HTMX──> ApiExplorerController ──Guzzle──> API Server

3. 사용자 데이터 (즐겨찾기, 템플릿, 히스토리)
   Browser ──> ApiExplorerController ──> SQLite/MySQL
```

---

## 3. 디렉토리 구조

```
mng/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       └── DevTools/
│   │           └── ApiExplorerController.php
│   ├── Services/
│   │   └── ApiExplorer/
│   │       ├── OpenApiParserService.php      # OpenAPI JSON 파싱
│   │       ├── ApiRequestService.php         # API 호출 프록시
│   │       └── ApiExplorerService.php        # 비즈니스 로직 통합
│   └── Models/
│       └── DevTools/
│           ├── ApiBookmark.php               # 즐겨찾기
│           ├── ApiTemplate.php               # 요청 템플릿
│           └── ApiHistory.php                # 요청 히스토리
│
├── database/
│   └── migrations/
│       └── 2024_xx_xx_create_api_explorer_tables.php
│
├── resources/
│   └── views/
│       └── dev-tools/
│           └── api-explorer/
│               ├── index.blade.php           # 메인 레이아웃
│               ├── partials/
│               │   ├── sidebar.blade.php     # API 목록 + 검색/필터
│               │   ├── endpoint-item.blade.php  # 개별 엔드포인트 항목
│               │   ├── request-panel.blade.php  # 요청 작성 패널
│               │   ├── response-panel.blade.php # 응답 표시 패널
│               │   ├── template-modal.blade.php # 템플릿 저장/불러오기
│               │   └── history-drawer.blade.php # 히스토리 서랍
│               └── components/
│                   ├── method-badge.blade.php   # HTTP 메서드 배지
│                   ├── param-input.blade.php    # 파라미터 입력 필드
│                   ├── json-editor.blade.php    # JSON 편집기
│                   └── json-viewer.blade.php    # JSON 뷰어 (트리/Raw)
│
├── routes/
│   └── web.php                               # 라우트 추가
│
└── config/
    └── api-explorer.php                      # 설정 파일
```

---

## 4. 데이터베이스 스키마

### 4.1 ERD

```
┌─────────────────────┐     ┌─────────────────────┐
│   api_bookmarks     │     │   api_templates     │
├─────────────────────┤     ├─────────────────────┤
│ id (PK)             │     │ id (PK)             │
│ user_id (FK)        │     │ user_id (FK)        │
│ endpoint            │     │ endpoint            │
│ method              │     │ method              │
│ display_name        │     │ name                │
│ display_order       │     │ description         │
│ color               │     │ headers (JSON)      │
│ created_at          │     │ path_params (JSON)  │
│ updated_at          │     │ query_params (JSON) │
└─────────────────────┘     │ body (JSON)         │
                            │ is_shared           │
                            │ created_at          │
                            │ updated_at          │
                            └─────────────────────┘

┌─────────────────────┐     ┌─────────────────────┐
│   api_histories     │     │  api_environments   │
├─────────────────────┤     ├─────────────────────┤
│ id (PK)             │     │ id (PK)             │
│ user_id (FK)        │     │ user_id (FK)        │
│ endpoint            │     │ name                │
│ method              │     │ base_url            │
│ request_headers     │     │ api_key             │
│ request_body        │     │ auth_token          │
│ response_status     │     │ variables (JSON)    │
│ response_headers    │     │ is_default          │
│ response_body       │     │ created_at          │
│ duration_ms         │     │ updated_at          │
│ environment         │     └─────────────────────┘
│ created_at          │
└─────────────────────┘
```

### 4.2 마이그레이션

```php
// api_bookmarks
Schema::create('api_bookmarks', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->string('endpoint', 500);
    $table->string('method', 10);
    $table->string('display_name', 100)->nullable();
    $table->integer('display_order')->default(0);
    $table->string('color', 20)->nullable();
    $table->timestamps();

    $table->unique(['user_id', 'endpoint', 'method']);
    $table->index('user_id');
});

// api_templates
Schema::create('api_templates', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->string('endpoint', 500);
    $table->string('method', 10);
    $table->string('name', 100);
    $table->text('description')->nullable();
    $table->json('headers')->nullable();
    $table->json('path_params')->nullable();
    $table->json('query_params')->nullable();
    $table->json('body')->nullable();
    $table->boolean('is_shared')->default(false);
    $table->timestamps();

    $table->index(['user_id', 'endpoint', 'method']);
    $table->index('is_shared');
});

// api_histories
Schema::create('api_histories', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->string('endpoint', 500);
    $table->string('method', 10);
    $table->json('request_headers')->nullable();
    $table->json('request_body')->nullable();
    $table->integer('response_status');
    $table->json('response_headers')->nullable();
    $table->longText('response_body')->nullable();
    $table->integer('duration_ms');
    $table->string('environment', 50);
    $table->timestamp('created_at');

    $table->index(['user_id', 'created_at']);
    $table->index(['endpoint', 'method']);
});

// api_environments
Schema::create('api_environments', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->string('name', 50);
    $table->string('base_url', 500);
    $table->string('api_key', 500)->nullable();
    $table->text('auth_token')->nullable();
    $table->json('variables')->nullable();
    $table->boolean('is_default')->default(false);
    $table->timestamps();

    $table->index('user_id');
});
```

---

## 5. UI 설계

### 5.1 메인 레이아웃 (3-Panel)

```
┌────────────────────────────────────────────────────────────────────────────┐
│ 🔍 Search...          │ [로컬 ▼] │ 📋 History │ ⚙️ Settings                │
├────────────────────────────────────────────────────────────────────────────┤
│                       │                          │                          │
│   API Sidebar         │    Request Panel         │    Response Panel        │
│   (resizable)         │    (resizable)           │    (resizable)           │
│                       │                          │                          │
│ ┌───────────────────┐ │ ┌──────────────────────┐ │ ┌──────────────────────┐ │
│ │ 🔍 필터            │ │ │ POST /api/v1/login   │ │ │ Status: 200 OK ✓    │ │
│ │ [GET][POST][PUT]  │ │ │                      │ │ │ Time: 45ms           │ │
│ │ [DELETE][PATCH]   │ │ │ ┌─ Headers ─────────┐│ │ │                      │ │
│ │                   │ │ │ │ Authorization: [] ││ │ │ ┌─ Headers ─────────┐│ │
│ │ ⭐ 즐겨찾기 (3)    │ │ │ │ Content-Type: [] ││ │ │ │ content-type: ... ││ │
│ │  POST login       │ │ │ └──────────────────┘│ │ │ │ x-request-id: ... ││ │
│ │  GET users        │ │ │                      │ │ │ └──────────────────┘│ │
│ │  POST logout      │ │ │ ┌─ Path Params ────┐│ │ │                      │ │
│ │                   │ │ │ │ (none)           ││ │ │ ┌─ Body ─────────────┐│ │
│ │ 📁 Auth           │ │ │ └──────────────────┘│ │ │ │ {                  ││ │
│ │  ├ POST login     │ │ │                      │ │ │ │   "success": true, ││ │
│ │  ├ POST logout    │ │ │ ┌─ Query Params ───┐│ │ │ │   "data": {        ││ │
│ │  └ GET me         │ │ │ │ (none)           ││ │ │ │     "token": "..."  ││ │
│ │                   │ │ │ └──────────────────┘│ │ │ │   }                ││ │
│ │ 📁 Users          │ │ │                      │ │ │ │ }                  ││ │
│ │  ├ GET list       │ │ │ ┌─ Body (JSON) ────┐│ │ │ └──────────────────┘│ │
│ │  ├ GET {id}       │ │ │ │ {                ││ │ │                      │ │
│ │  ├ POST create    │ │ │ │   "user_id": "", ││ │ │ [Raw] [Pretty] [Tree]│ │
│ │  ├ PUT {id}       │ │ │ │   "user_pwd": "" ││ │ │                      │ │
│ │  └ DELETE {id}    │ │ │ │ }                ││ │ │ [📋 Copy] [💾 Save]  │ │
│ │                   │ │ │ └──────────────────┘│ │ │                      │ │
│ │ 📁 Products       │ │ │                      │ │ └──────────────────────┘ │
│ │  └ ...            │ │ │ [📋 템플릿] [▶ 실행]│ │                          │
│ └───────────────────┘ │ └──────────────────────┘ │                          │
│                       │                          │                          │
└────────────────────────────────────────────────────────────────────────────┘
```

### 5.2 컬러 스킴

```css
/* HTTP 메서드 배지 */
.method-get    { @apply bg-green-100 text-green-800; }
.method-post   { @apply bg-blue-100 text-blue-800; }
.method-put    { @apply bg-yellow-100 text-yellow-800; }
.method-patch  { @apply bg-orange-100 text-orange-800; }
.method-delete { @apply bg-red-100 text-red-800; }

/* 상태 코드 */
.status-2xx { @apply text-green-600; }  /* 성공 */
.status-3xx { @apply text-blue-600; }   /* 리다이렉트 */
.status-4xx { @apply text-yellow-600; } /* 클라이언트 에러 */
.status-5xx { @apply text-red-600; }    /* 서버 에러 */
```

### 5.3 반응형 동작

| 화면 크기 | 동작 |
|-----------|------|
| Desktop (≥1280px) | 3-Panel 표시 |
| Tablet (768-1279px) | 2-Panel (사이드바 접힘 가능) |
| Mobile (<768px) | 1-Panel (탭 전환) |

---

## 6. API 설계

### 6.1 라우트 정의

```php
// routes/web.php
Route::prefix('dev-tools/api-explorer')
    ->middleware(['auth'])
    ->name('api-explorer.')
    ->group(function () {
        // 메인 페이지
        Route::get('/', [ApiExplorerController::class, 'index'])->name('index');

        // API 목록 (HTMX partial)
        Route::get('/endpoints', [ApiExplorerController::class, 'endpoints'])->name('endpoints');
        Route::get('/endpoints/{operationId}', [ApiExplorerController::class, 'endpoint'])->name('endpoint');

        // API 실행 (프록시)
        Route::post('/execute', [ApiExplorerController::class, 'execute'])->name('execute');

        // 즐겨찾기
        Route::get('/bookmarks', [ApiExplorerController::class, 'bookmarks'])->name('bookmarks');
        Route::post('/bookmarks', [ApiExplorerController::class, 'addBookmark'])->name('bookmarks.add');
        Route::delete('/bookmarks/{id}', [ApiExplorerController::class, 'removeBookmark'])->name('bookmarks.remove');
        Route::put('/bookmarks/reorder', [ApiExplorerController::class, 'reorderBookmarks'])->name('bookmarks.reorder');

        // 템플릿
        Route::get('/templates', [ApiExplorerController::class, 'templates'])->name('templates');
        Route::get('/templates/{endpoint}', [ApiExplorerController::class, 'templatesForEndpoint'])->name('templates.endpoint');
        Route::post('/templates', [ApiExplorerController::class, 'saveTemplate'])->name('templates.save');
        Route::delete('/templates/{id}', [ApiExplorerController::class, 'deleteTemplate'])->name('templates.delete');

        // 히스토리
        Route::get('/history', [ApiExplorerController::class, 'history'])->name('history');
        Route::delete('/history', [ApiExplorerController::class, 'clearHistory'])->name('history.clear');
        Route::post('/history/{id}/replay', [ApiExplorerController::class, 'replayHistory'])->name('history.replay');

        // 환경
        Route::get('/environments', [ApiExplorerController::class, 'environments'])->name('environments');
        Route::post('/environments', [ApiExplorerController::class, 'saveEnvironment'])->name('environments.save');
        Route::delete('/environments/{id}', [ApiExplorerController::class, 'deleteEnvironment'])->name('environments.delete');
        Route::post('/environments/{id}/default', [ApiExplorerController::class, 'setDefaultEnvironment'])->name('environments.default');
    });
```

### 6.2 Controller 메서드 시그니처

```php
class ApiExplorerController extends Controller
{
    public function __construct(
        private OpenApiParserService $parser,
        private ApiRequestService $requester,
        private ApiExplorerService $explorer
    ) {}

    // GET /dev-tools/api-explorer
    public function index(): View

    // GET /dev-tools/api-explorer/endpoints?search=&tags[]=&methods[]=
    public function endpoints(Request $request): View  // HTMX partial

    // GET /dev-tools/api-explorer/endpoints/{operationId}
    public function endpoint(string $operationId): View  // HTMX partial

    // POST /dev-tools/api-explorer/execute
    public function execute(ExecuteApiRequest $request): JsonResponse

    // Bookmarks CRUD...
    // Templates CRUD...
    // History CRUD...
    // Environments CRUD...
}
```

### 6.3 Service 클래스

```php
// OpenApiParserService - OpenAPI JSON 파싱
class OpenApiParserService
{
    public function parse(): array;                           // 전체 스펙 파싱
    public function getEndpoints(): Collection;               // 엔드포인트 목록
    public function getEndpoint(string $operationId): ?array; // 단일 엔드포인트
    public function getTags(): array;                         // 태그 목록
    public function search(string $query): Collection;        // 검색
    public function filter(array $filters): Collection;       // 필터링
}

// ApiRequestService - API 호출 프록시
class ApiRequestService
{
    public function execute(
        string $method,
        string $url,
        array $headers = [],
        array $query = [],
        ?array $body = null
    ): ApiResponse;
}

// ApiExplorerService - 비즈니스 로직 통합
class ApiExplorerService
{
    // Bookmark operations
    public function getBookmarks(int $userId): Collection;
    public function addBookmark(int $userId, array $data): ApiBookmark;
    public function removeBookmark(int $bookmarkId): void;

    // Template operations
    public function getTemplates(int $userId, ?string $endpoint = null): Collection;
    public function saveTemplate(int $userId, array $data): ApiTemplate;
    public function deleteTemplate(int $templateId): void;

    // History operations
    public function logRequest(int $userId, array $data): ApiHistory;
    public function getHistory(int $userId, int $limit = 50): Collection;
    public function clearHistory(int $userId): void;

    // Environment operations
    public function getEnvironments(int $userId): Collection;
    public function saveEnvironment(int $userId, array $data): ApiEnvironment;
}
```

---

## 7. 핵심 기능 상세

### 7.1 스마트 검색

```php
// 검색 대상 필드
$searchFields = [
    'endpoint',      // /api/v1/users
    'summary',       // "사용자 목록 조회"
    'description',   // 상세 설명
    'operationId',   // getUserList
    'parameters.*.name',        // 파라미터명
    'parameters.*.description', // 파라미터 설명
    'tags',          // 태그
];

// 검색 알고리즘
1. 정확히 일치 → 최상위
2. 시작 부분 일치 → 높은 순위
3. 포함 → 일반 순위
4. Fuzzy 매칭 → 낮은 순위 (선택적)
```

### 7.2 필터링 옵션

```php
$filters = [
    'methods' => ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    'tags' => ['Auth', 'Users', 'Products', ...],
    'status' => ['stable', 'beta', 'deprecated'],
    'hasBody' => true|false,
    'requiresAuth' => true|false,
];
```

### 7.3 요청 템플릿 시스템

```json
// 템플릿 저장 형식
{
    "name": "로그인 테스트",
    "description": "테스트 계정으로 로그인",
    "endpoint": "/api/v1/login",
    "method": "POST",
    "headers": {
        "X-API-KEY": "{{API_KEY}}"
    },
    "body": {
        "user_id": "test",
        "user_pwd": "testpass"
    },
    "is_shared": false
}
```

### 7.4 환경 변수 시스템

```json
// 환경 설정 형식
{
    "name": "로컬",
    "base_url": "http://api.sam.kr",
    "api_key": "your-api-key",
    "auth_token": null,
    "variables": {
        "TENANT_ID": "1",
        "USER_ID": "test"
    }
}

// 변수 치환: {{VARIABLE_NAME}}
// 예: "Authorization": "Bearer {{AUTH_TOKEN}}"
```

---

## 8. HTMX 통합

### 8.1 주요 HTMX 패턴

```html
<!-- 엔드포인트 목록 갱신 -->
<div id="endpoint-list"
     hx-get="/dev-tools/api-explorer/endpoints"
     hx-trigger="load, search"
     hx-swap="innerHTML">
</div>

<!-- 검색 입력 -->
<input type="text"
       name="search"
       hx-get="/dev-tools/api-explorer/endpoints"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#endpoint-list"
       hx-include="[name='methods[]'], [name='tags[]']">

<!-- 엔드포인트 선택 -->
<button hx-get="/dev-tools/api-explorer/endpoints/{{ $operationId }}"
        hx-target="#request-panel"
        hx-swap="innerHTML">
</button>

<!-- API 실행 -->
<form hx-post="/dev-tools/api-explorer/execute"
      hx-target="#response-panel"
      hx-indicator="#loading-spinner">
</form>

<!-- 즐겨찾기 토글 -->
<button hx-post="/dev-tools/api-explorer/bookmarks"
        hx-vals='{"endpoint": "{{ $endpoint }}", "method": "{{ $method }}"}'
        hx-swap="outerHTML">
    ⭐
</button>
```

### 8.2 OOB (Out-of-Band) 업데이트

```html
<!-- 응답 후 히스토리 자동 갱신 -->
<div id="history-count" hx-swap-oob="true">
    {{ $historyCount }}
</div>
```

---

## 9. 보안 고려사항

### 9.1 접근 제어
- mng 프로젝트 로그인 필수 (`auth` 미들웨어)
- 개발 환경에서만 접근 가능 (선택적)
- API Key/Token은 서버사이드에서만 관리

### 9.2 민감 정보 처리
- 환경 설정의 API Key는 암호화 저장
- 히스토리에서 민감 헤더 마스킹 옵션
- 공유 템플릿에서 인증 정보 자동 제외

### 9.3 프록시 보안
- 허용된 base_url만 프록시 가능 (화이트리스트)
- 요청 크기 제한 (body 최대 1MB)
- 타임아웃 설정 (30초)

---

## 10. 구현 로드맵

### Phase 1: 기본 구조 (3-4일)
- [ ] 디렉토리 구조 생성
- [ ] 마이그레이션 파일 작성
- [ ] OpenApiParserService 구현
- [ ] 기본 UI 레이아웃 (3-Panel)
- [ ] 엔드포인트 목록 표시

### Phase 2: 검색/필터/요청 (3일)
- [ ] 풀텍스트 검색 구현
- [ ] 메서드/태그 필터링
- [ ] 요청 패널 UI
- [ ] ApiRequestService (프록시)
- [ ] 응답 표시 (JSON Viewer)

### Phase 3: 사용자 데이터 (3일)
- [ ] 즐겨찾기 CRUD
- [ ] 템플릿 저장/불러오기
- [ ] 히스토리 기록/재실행
- [ ] 드래그&드롭 정렬

### Phase 4: 환경/고급 기능 (2-3일)
- [ ] 환경 관리 UI
- [ ] 변수 치환 시스템
- [ ] 키보드 단축키
- [ ] UI 폴리싱
- [ ] 반응형 최적화

### Phase 5: 테스트/배포 (2일)
- [ ] 기능 테스트
- [ ] 성능 최적화
- [ ] 문서화
- [ ] 배포

**예상 총 기간: 13-15일**

---

## 11. 향후 확장 가능성

### 11.1 추가 기능 후보
- **Mock 서버**: 테스트용 가짜 응답 생성
- **API 비교**: 두 환경 간 응답 비교
- **자동 테스트**: 저장된 템플릿 일괄 실행
- **변경 감지**: OpenAPI 스펙 변경 알림
- **문서 생성**: Markdown/PDF 문서 자동 생성

### 11.2 통합 가능성
- **CI/CD**: API 테스트 자동화
- **Slack/Teams**: 알림 연동
- **Postman**: 컬렉션 import/export

---

## 12. 참고 자료

### 12.1 유사 도구
- [Scalar](https://scalar.com/) - 현대적 API 문서
- [Stoplight](https://stoplight.io/) - API 설계 도구
- [Insomnia](https://insomnia.rest/) - API 클라이언트

### 12.2 기술 문서
- [OpenAPI 3.0 Specification](https://spec.openapis.org/oas/v3.0.3)
- [HTMX Documentation](https://htmx.org/docs/)
- [Laravel HTTP Client](https://laravel.com/docs/http-client)
