Skip to content

ApiHandler

Stable

ApiHandler 는 HTTP 전송 레이어를 담당하는 클래스입니다. DomainState 가 상태 추적에만 집중할 수 있도록 네트워크 통신을 위임받아 처리합니다. URL 프로토콜 결정, 기본 경로 조합, CSRF 토큰 자동 주입, HTTP 오류 처리를 내부적으로 수행합니다.

생성자

javascript
new ApiHandler(urlConfig?)
파라미터타입기본값설명
hoststring호스트 주소. 'localhost:8080', 'api.example.com' 형식. baseURL과 택일.
baseURLstring프로토콜 포함 전체 기본 URL. 'http://localhost:8080/app/api' 형식. host와 택일.
basePathstring''모든 요청에 공통으로 붙는 경로 접두사. '/api', '/v1' 등.
env'development' | 'production'프로토콜 자동 결정 기준. development이면 HTTP, production이면 HTTPS.
protocol'HTTP' | 'HTTPS'프로토콜 명시적 지정. env보다 우선 적용됩니다.
debugbooleanfalseURL 결정 과정을 콘솔에 출력합니다. 개발 환경에서 유용합니다.

URL 프로토콜 결정 순서

동일한 ApiHandler 인스턴스를 개발/운영 환경에서 모두 사용하려면 env 옵션을 환경 변수와 연결하는 것이 권장됩니다.

markdown
1. protocol 명시                → 그대로 사용 ('HTTP' | 'HTTPS')
2. env = 'production'           → HTTPS 자동 적용
3. env = 'development'          → HTTP 자동 적용
4. debug = true                 → HTTP
5. 어떤 것도 지정하지 않은 경우 → HTTPS (안전한 기본값)

사용 예시

javascript
// ── 기본 개발 환경 ───────────────────────────────────────────
const api = new ApiHandler({ host: 'localhost:8080', basePath: '/api' })
// → http://localhost:8080/api/...

// ── 운영 환경 (HTTPS 자동) ────────────────────────────────────
const api = new ApiHandler({
    host: 'api.my-service.com',
    env:  'production',
})
// → https://api.my-service.com/...

// ── 통합 문자열형 baseURL ─────────────────────────────────────
const api = new ApiHandler({ baseURL: 'http://localhost:8080/app/api' })

// ── 다중 서버 연결 ────────────────────────────────────────────
const userApi = new ApiHandler({ host: 'users.my-service.com', env: 'production' })
const authApi = new ApiHandler({ host: 'auth.my-service.com',  env: 'production' })

init() — CSRF 토큰 초기화

Spring Security, Laravel, Django 등 서버 사이드 프레임워크와 연동하는 환경에서 CSRF 방어를 활성화합니다. DOM이 준비된 시점에 1회만 호출하면 이후 모든 변이 요청(POST / PUT / PATCH / DELETE)에 X-CSRF-Token 헤더가 자동으로 주입됩니다.

javascript
api.init(config?)

init() 을 호출하지 않으면 CSRF 기능은 완전히 비활성 상태입니다. GET 요청 전용으로만 사용하는 경우 호출하지 않아도 됩니다.

CSRF 토큰 탐색 우선순위

markdown
1. config.csrfToken 직접 주입    → 가장 높은 우선순위. Vitest / SSR 환경용.
2. config.csrfSelector 지정      → 해당 CSS 선택자로 meta 태그 content 파싱.
3. 기본 선택자 자동 탐색          → 'meta[name="_csrf"]' (Spring Security 기본값)
4. config.csrfCookieName 지정    → document.cookie 파싱 (Double-Submit Cookie 패턴)
5. 모두 실패                      → 토큰 없음 상태. 변이 요청 발생 시 즉시 throw.

옵션

옵션타입설명
csrfSelectorstringCSRF 토큰 meta 태그 CSS 선택자. 기본값: 'meta[name="_csrf"]'
csrfCookieNamestringDouble-Submit Cookie 방식의 쿠키명. csrfSelector 탐색 실패 시 fallback.
csrfTokenstring토큰 직접 주입. 지정 시 DOM 탐색보다 우선 적용. Vitest / SSR 환경에서 사용.

서버 프레임워크별 연동 방법

Spring Security (기본값)

Spring Security는 기본적으로 <meta name="_csrf" content="토큰값"> 형태로 HTML에 토큰을 삽입합니다. 선택자를 지정하지 않아도 자동 탐색됩니다.

html
<!-- Thymeleaf 템플릿 -->
<meta name="_csrf" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
javascript
// 선택자 지정 없이 기본값 자동 탐색
api.init({})

Spring Security — 커스텀 선택자

javascript
api.init({ csrfSelector: 'meta[name="X-CSRF-TOKEN"]' })

Laravel / Django

javascript
api.init({ csrfSelector: 'meta[name="csrf-token"]' })
javascript
api.init({ csrfCookieName: 'XSRF-TOKEN' })

CSRF 3-상태 설계

#csrfToken의미_fetch() 동작
undefinedinit() 미호출. CSRF 기능 비활성.토큰 삽입 로직 전체 건너뜀
nullinit() 호출됐으나 토큰 파싱 실패.변이 요청 발생 시 즉시 throw
string정상 파싱된 토큰 값.X-CSRF-Token 헤더 자동 주입

체이닝

javascript
const api = new ApiHandler({ host: 'localhost:8080' }).init({})

메서드

get(path, options?)

서버에서 데이터를 조회하고 DomainState 인스턴스를 반환합니다. 내부적으로 DomainState.fromJSON() 을 호출하며 isNew: false 로 설정됩니다.

javascript
const user = await api.get('/users/user_001')
// → DomainState { _isNew: false, data: { userId: 'user_001', ... } }
옵션타입기본값설명
voDomainVO스키마 검증 및 변환기 주입. 응답 데이터와 VO 스키마의 불일치 시 콘솔 경고.
strictbooleanfalsetrue이면 VO 스키마 불일치(누락 필드) 시 Error를 throw한다. vo 옵션과 함께 사용한다.
labelstring디버그 팝업에 표시될 인스턴스 레이블.
debugbooleanfalsetrue이면 BroadcastChannel 디버그 채널에 상태를 broadcast합니다.

vo + strict 옵션 — 스키마 정합성 강제

서버 응답 구조가 DomainVO 스키마와 일치하는지 검증합니다. strict: false(기본값)이면 불일치 시 콘솔 경고만 출력하고 계속 진행합니다. strict: true이면 누락 필드가 있을 때 즉시 Error를 throw하여 실행을 중단합니다.

javascript
// strict: false (기본값) — 불일치 시 콘솔 경고 후 계속 진행
const user = await api.get('/users/1', { vo: new UserVO() })

// strict: true — 누락 필드 발생 시 Error throw
try {
    const user = await api.get('/users/1', { vo: new UserVO(), strict: true })
} catch (err) {
    // err.message: '[DSM] DomainVO 스키마 엄격 검증 실패: "email" 필드가 응답 데이터에 없습니다.'
    console.error('서버 응답이 스키마와 다릅니다:', err.message)
}

strict 모드 권장 사용 시점

개발 환경에서 서버 API 변경을 즉시 감지하려면 strict: true 를 사용하세요. 운영 환경에서는 false(기본값)로 두어 예상치 못한 API 변경이 화면 전체를 중단시키지 않도록 하는 것이 일반적입니다.

스키마 검증의 상세 동작(missingKeys, extraKeys, valid 판정 기준)은 DomainVO 가이드를 참고하세요.

HTTP 오류 처리

서버가 4xx 또는 5xx 를 반환하면 { status, statusText, body } 형태의 객체가 throw됩니다. try/catch 로 반드시 처리해야 합니다.

내부 동작

ApiHandlerDomainState 인스턴스에 주입되어 save(), remove() 호출 시 실제 네트워크 요청을 수행합니다. 이 구조 덕분에 테스트 환경에서 MockApiHandler 로 교체하거나, WebSocket 기반의 커스텀 핸들러로 대체하는 것이 가능합니다.

javascript
// 테스트 환경: 실제 네트워크 없이 동작
const mockApi = {
    _fetch:       vi.fn().mockResolvedValue(null),
    getUrlConfig: () => ({ protocol: 'http://', host: 'localhost', basePath: '' }),
}
const state = DomainState.fromJSON(JSON.stringify(data), mockApi)

Released under the ISC License.