# MNG HTMX + API 패턴 가이드 **작성일:** 2025-01-24 **목적:** MNG 프로젝트의 표준 HTMX + API 패턴 문서화 (Tenant 패턴 기반) **관련 문서:** - [LAYOUT_PATTERN.md](./LAYOUT_PATTERN.md) - 페이지 레이아웃 및 Tenant Selector 패턴 - [99_TECHNICAL_STANDARDS.md](./99_TECHNICAL_STANDARDS.md) - SAM API Rules 기반 기술 표준 --- ## 📋 목차 1. [패턴 개요](#1-패턴-개요) 2. [아키텍처 구조](#2-아키텍처-구조) 3. [구현 가이드](#3-구현-가이드) 4. [파일 구조](#4-파일-구조) 5. [체크리스트](#5-체크리스트) --- ## 1. 패턴 개요 ### 1.1 왜 HTMX + API 패턴인가? **MNG 프로젝트의 표준 아키텍처 패턴입니다.** - **일관성**: 모든 CRUD 기능이 동일한 패턴 사용 - **성능**: 페이지 전체 리로드 없이 동적 업데이트 - **유지보수성**: Blade 템플릿 + HTMX로 간단한 인터랙션 - **확장성**: API는 HTMX와 독립적으로 사용 가능 ### 1.2 기본 원칙 1. **Blade View는 화면만 담당** - 데이터 처리 로직 없음 2. **API Controller는 HTMX와 JSON 모두 지원** 3. **HTMX 요청 시 HTML partial 반환** 4. **일반 요청 시 JSON 반환** --- ## 2. 아키텍처 구조 ### 2.1 전체 흐름도 ``` [Browser] ↓ (HTMX Request with HX-Request header) [Route: web.php] ↓ (Blade View 반환) [Controller: RoleController] ↓ (view('roles.index') - 화면만) [Blade View: roles/index.blade.php] ↓ (hx-get="/api/admin/roles") [API Route: api.php] ↓ (API 엔드포인트) [Api\Admin\RoleController] ↓ (Service 호출) [RoleService] ↓ (비즈니스 로직) [Database] ↓ [RoleService] ↓ (데이터 반환) [Api\Admin\RoleController] ↓ (HTMX 요청 감지: HX-Request header) ↓ (HTML partial 렌더링) [Blade Partial: roles/partials/table.blade.php] ↓ (JSON with html) [Browser - HTMX] ↓ (DOM 업데이트: #role-table) [User sees updated table] ``` ### 2.2 컨트롤러 분리 #### Blade Controller (화면 전용) ```php // app/Http/Controllers/RoleController.php class RoleController extends Controller { public function index(): View { return view('roles.index'); // 화면만 반환 } public function create(): View { return view('roles.create'); } public function edit(int $id): View { $role = $this->roleService->getRoleById($id); return view('roles.edit', compact('role')); } } ``` #### API Controller (데이터 처리) ```php // app/Http/Controllers/Api/Admin/RoleController.php class RoleController extends Controller { public function index(Request $request): JsonResponse { $roles = $this->roleService->getRoles($request->all()); // HTMX 요청 감지 if ($request->header('HX-Request')) { $html = view('roles.partials.table', compact('roles'))->render(); return response()->json(['html' => $html]); } // 일반 API 요청 return response()->json([ 'success' => true, 'data' => $roles->items(), 'meta' => [/*...*/], ]); } public function store(StoreRoleRequest $request): JsonResponse { $role = $this->roleService->createRole($request->validated()); if ($request->header('HX-Request')) { return response()->json([ 'success' => true, 'message' => '역할이 생성되었습니다.', 'redirect' => route('roles.index'), ]); } return response()->json([ 'success' => true, 'data' => $role, ], 201); } public function destroy(Request $request, int $id): JsonResponse { $this->roleService->deleteRole($id); if ($request->header('HX-Request')) { return response()->json([ 'success' => true, 'message' => '역할이 삭제되었습니다.', 'action' => 'remove', ]); } return response()->json([ 'success' => true, 'message' => '역할이 삭제되었습니다.', ]); } } ``` --- ## 3. 구현 가이드 ### 3.1 Blade View 구조 #### index.blade.php (메인 화면) ```blade @extends('layouts.app') @section('content')

🔑 역할 관리

@endsection @push('scripts') @endpush ``` #### partials/table.blade.php (HTMX 응답용 HTML partial) ```blade @forelse($roles as $role) @empty @endforelse
ID 이름 설명 권한 수 액션
{{ $role->id }} {{ $role->name }} {{ $role->description }} {{ $role->permissions_count }} 수정
등록된 역할이 없습니다.
@include('partials.pagination', [ 'paginator' => $roles, 'target' => '#role-table', 'includeForm' => '#filterForm' ]) ``` ### 3.2 라우트 설정 #### web.php (Blade 화면 라우트) ```php Route::middleware('auth')->group(function () { Route::prefix('roles')->name('roles.')->group(function () { Route::get('/', [RoleController::class, 'index'])->name('index'); Route::get('/create', [RoleController::class, 'create'])->name('create'); Route::get('/{id}/edit', [RoleController::class, 'edit'])->name('edit'); }); }); ``` #### api.php (API 엔드포인트) ```php Route::middleware(['web', 'auth'])->prefix('admin')->name('api.admin.')->group(function () { Route::prefix('roles')->name('roles.')->group(function () { Route::get('/', [RoleController::class, 'index'])->name('index'); Route::post('/', [RoleController::class, 'store'])->name('store'); Route::get('/{id}', [RoleController::class, 'show'])->name('show'); Route::put('/{id}', [RoleController::class, 'update'])->name('update'); Route::delete('/{id}', [RoleController::class, 'destroy'])->name('destroy'); }); }); ``` ### 3.3 HTMX 핵심 개념 #### hx-get, hx-post, hx-put, hx-delete ```html
로딩 중...
``` #### hx-trigger ```html
``` #### hx-include ```html
``` #### hx-headers ```html
``` #### hx-target, hx-swap ```html
기본값
엘리먼트 자체 교체
응답 무시
``` --- ## 4. 파일 구조 ### 4.1 표준 디렉토리 구조 ``` mng/ ├── app/ │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── RoleController.php # Blade 화면만 │ │ │ └── Api/ │ │ │ └── Admin/ │ │ │ └── RoleController.php # API 로직 │ │ └── Requests/ │ │ ├── StoreRoleRequest.php │ │ └── UpdateRoleRequest.php │ ├── Services/ │ │ └── RoleService.php # 비즈니스 로직 │ └── Models/ │ └── Role.php ├── resources/ │ └── views/ │ └── roles/ │ ├── index.blade.php # 메인 화면 │ ├── create.blade.php # 생성 화면 │ ├── edit.blade.php # 수정 화면 │ └── partials/ │ ├── table.blade.php # HTMX 응답 HTML │ └── detail.blade.php # (선택사항) └── routes/ ├── web.php # Blade 화면 라우트 └── api.php # API 엔드포인트 ``` ### 4.2 Tenant 패턴 참고 파일 **학습 및 복사 기준:** - `app/Http/Controllers/TenantController.php` → Blade 컨트롤러 패턴 - `app/Http/Controllers/Api/Admin/TenantController.php` → API 컨트롤러 패턴 - `resources/views/tenants/index.blade.php` → HTMX 메인 화면 패턴 - `resources/views/tenants/partials/table.blade.php` → HTML partial 패턴 --- ## 5. 체크리스트 ### 5.1 구현 전 확인사항 - [ ] **Tenant 패턴 파일 확인**: `tenants/` 디렉토리 구조 참고 - [ ] **Service 작성 완료**: `RoleService.php` 비즈니스 로직 구현 - [ ] **FormRequest 작성 완료**: `StoreRoleRequest`, `UpdateRoleRequest` - [ ] **Model 확인**: `Role.php` 관계 설정 확인 ### 5.2 컨트롤러 체크리스트 **Blade Controller (`app/Http/Controllers/RoleController.php`)** - [ ] `index()` → `view('roles.index')` 반환만 - [ ] `create()` → `view('roles.create')` 반환만 - [ ] `edit($id)` → Service로 데이터 조회 → `view('roles.edit', compact('role'))` **API Controller (`app/Http/Controllers/Api/Admin/RoleController.php`)** - [ ] `index()` → HTMX 요청 감지 (`$request->header('HX-Request')`) - [ ] HTMX 요청 시 → `view('roles.partials.table')->render()` → JSON 반환 - [ ] 일반 요청 시 → JSON 데이터 반환 - [ ] `store()`, `update()`, `destroy()` → HTMX 지원 - [ ] HTMX 응답 시 `redirect` 또는 `action` 포함 ### 5.3 Blade View 체크리스트 **index.blade.php** - [ ] `@extends('layouts.app')` 상속 - [ ] 필터 폼 `
` 생성 - [ ] HTMX 동적 영역 `
` 생성 - [ ] `hx-get="/api/admin/roles"` 설정 - [ ] `hx-trigger="load, filterSubmit from:body"` 설정 - [ ] `hx-include="#filterForm"` 설정 - [ ] `hx-headers` CSRF 토큰 포함 - [ ] 로딩 스피너 추가 - [ ] `@push('scripts')` HTMX 스크립트 추가 - [ ] 폼 제출 이벤트 핸들러 (`filterSubmit` 트리거) - [ ] HTMX 응답 처리 (`htmx:afterSwap`) - [ ] 삭제 확인 함수 (`confirmDelete`) **partials/table.blade.php** - [ ] `` 구조 생성 - [ ] `@forelse` 루프로 데이터 출력 - [ ] `@empty` 케이스 처리 - [ ] 액션 버튼 (수정, 삭제) - [ ] 삭제 버튼 `onclick="confirmDelete()"` 연결 - [ ] 페이지네이션 `@include('partials.pagination')` ### 5.4 라우트 체크리스트 **web.php** - [ ] `Route::middleware('auth')` 적용 - [ ] `Route::prefix('roles')->name('roles.')` 그룹 - [ ] `GET /roles` → `index()` - [ ] `GET /roles/create` → `create()` - [ ] `GET /roles/{id}/edit` → `edit()` **api.php** - [ ] `Route::middleware(['web', 'auth'])->prefix('admin')` 적용 - [ ] `Route::prefix('roles')->name('api.admin.roles.')` 그룹 - [ ] `GET /api/admin/roles` → `index()` - [ ] `POST /api/admin/roles` → `store()` - [ ] `GET /api/admin/roles/{id}` → `show()` - [ ] `PUT /api/admin/roles/{id}` → `update()` - [ ] `DELETE /api/admin/roles/{id}` → `destroy()` ### 5.5 테스트 체크리스트 - [ ] 브라우저에서 `/roles` 접근 → index 화면 로드 - [ ] HTMX 자동 로드 → 테이블 표시 - [ ] 검색 필터 동작 → 테이블 업데이트 - [ ] 삭제 버튼 → 확인 다이얼로그 → 테이블 업데이트 - [ ] 페이지네이션 동작 - [ ] 개발자 도구 Network 탭 → `HX-Request` 헤더 확인 - [ ] API 응답 JSON 구조 확인 (`{html: "..."}`) --- ## 6. 참고사항 ### 6.1 HTMX vs 전통적 방식 비교 | 항목 | 전통적 방식 | HTMX 방식 | |------|------------|-----------| | **폼 제출** | `` → 전체 페이지 리로드 | `hx-get` → 부분 업데이트 | | **데이터 로딩** | Controller에서 직접 데이터 전달 | API 호출 → HTML partial 반환 | | **삭제 동작** | `` + `@method('DELETE')` | `htmx.ajax('DELETE')` | | **검색 필터** | 페이지 리로드 + 쿼리스트링 | HTMX 트리거 → 부분 업데이트 | ### 6.2 주의사항 1. **HTMX 요청 감지 필수** ```php if ($request->header('HX-Request')) { // HTMX 전용 로직 } ``` 2. **CSRF 토큰 포함 필수** ```html hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}' ``` 3. **JSON 응답 구조 일관성** ```json { "html": "
...
", "success": true, "message": "작업 완료" } ``` 4. **Blade와 API Controller 분리** - Blade Controller: 화면만 반환 - API Controller: 데이터 처리 + HTMX/JSON 응답 --- ## 7. 마이그레이션 가이드 (Admin → MNG) ### 7.1 작업 순서 1. **DB 확인** - 테이블이 이미 존재하는지 확인 (migrations 실행 불필요) 2. **Admin 파일 참고** - Controller, Service, Model 복사/참고 3. **패턴 적용** - HTMX + API 패턴으로 변환 4. **테스트** - 브라우저에서 동작 확인 ### 7.2 마이그레이션 체크리스트 - [ ] DB 테이블 존재 확인 (`roles`, `permissions`, `role_has_permissions`) - [ ] Admin Model 참고 (`admin/app/Models/Permissions/Role.php`) - [ ] Admin Controller 참고 (비즈니스 로직 추출) - [ ] Service 작성 (Admin 로직 → MNG Service) - [ ] Blade Controller 작성 (화면 반환만) - [ ] API Controller 작성 (HTMX 패턴) - [ ] Blade View 작성 (Tenant 패턴 기반) - [ ] 라우트 등록 (web.php, api.php) - [ ] 브라우저 테스트 --- **작성자:** Claude **최종 수정일:** 2025-01-24 **버전:** 1.0 **참고:** Tenant 관리 시스템 구현 패턴 기반