Files
game-fe-agent/skills/dev-api-state/SKILL.md

3.7 KiB

name, description
name description
dev-api-state API 연동 패턴(useFetch / useAsyncData / $fetch)과 Pinia 상태관리 코드를 상황에 맞게 자동 선택·생성합니다. BFF 패턴, 에러 핸들링, 로딩 상태를 포함합니다. 다음 상황에서 반드시 사용하세요: - "이 API 연동 + 스토어 만들어줘", "Pinia store 작성해줘" - "API 데이터 페칭 어떻게 해?", "useFetch vs useAsyncData 언제 써?" - API 키 보호를 위한 BFF(server/api/) 패턴이 필요할 때

API 연동 & 상태관리 (dev-api-state)

API 스펙 → useFetch/useAsyncData 패턴 선택 → Pinia Setup Store 코드 자동 생성.

언제 사용하는가

  • 새 API 연동 코드와 Pinia 스토어를 함께 작성할 때
  • useFetch / $fetch / useAsyncData 중 적절한 패턴을 결정할 때
  • API 키를 클라이언트에 노출하지 않기 위한 BFF 패턴이 필요할 때

데이터 페칭 패턴 선택 기준

상황 권장 패턴
SSR 페이지 초기 데이터 useAsyncData + $fetch
컴포넌트 마운트 후 데이터 useFetch
사용자 액션으로 트리거 $fetch (직접 호출)
API 키 보호 필요 server/api/ BFF + useFetch
복잡한 캐싱/의존성 useAsyncData with key

작업 순서

Phase 1: API 스펙 파악

  1. 엔드포인트, 요청/응답 타입을 파악한다.
  2. 아래를 확인한다:
    • 인증 헤더 필요 여부
    • API 키 노출 위험 여부 → BFF 패턴 적용
    • 캐시 전략 (항상 최신 vs TTL)

Phase 2: 타입 정의

// types/product.ts
export interface Product {
  id: string
  name: string
  price: number
  imageUrl: string
}

export interface ProductListResponse {
  data: Product[]
  total: number
  page: number
}

Phase 3: BFF 레이어 (필요 시)

API 키 보호가 필요한 경우 server/api/ 경유:

// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
  const query = getQuery(event)
  const response = await $fetch<ProductListResponse>(
    `${process.env.API_BASE}/products`,
    {
      headers: { 'x-api-key': process.env.API_KEY! },
      query,
    }
  )
  return response
})

Phase 4: Pinia Setup Store 생성

Setup Store 패턴을 기본으로 사용한다:

// stores/product.ts
import type { Product } from '~/types/product'

export const useProductStore = defineStore('product', () => {
  const items = ref<Product[]>([])
  const isLoading = ref(false)
  const error = ref<string | null>(null)

  const fetchProducts = async (page = 1) => {
    isLoading.value = true
    error.value = null
    try {
      const { data } = await useFetch('/api/products', { query: { page } })
      items.value = data.value?.data ?? []
    } catch (e) {
      error.value = e instanceof Error ? e.message : '알 수 없는 오류'
    } finally {
      isLoading.value = false
    }
  }

  return { items, isLoading, error, fetchProducts }
})

Phase 5: 컴포넌트 연결 예시

<script setup lang="ts">
const productStore = useProductStore()
const { items, isLoading, error } = storeToRefs(productStore)

onMounted(() => productStore.fetchProducts())
</script>

<template>
  <div v-if="isLoading" aria-live="polite">로딩 중...</div>
  <div v-else-if="error" role="alert">{{ error }}</div>
  <ul v-else>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

출력 형식

## API 연동: <기능명>

### 선택된 패턴
- 페칭: useFetch | useAsyncData | $fetch
- BFF: 사용 | 미사용
- 이유: [선택 근거]

### 파일 목록
- `types/<domain>.ts`
- `server/api/...` (BFF 사용 시)
- `stores/<domain>.ts`

### 코드
[타입 → BFF → 스토어 → 컴포넌트 순]