API Client

소개

fetch를 조금 더 유틸성있게 변경했다.

ApiClient.ts

typescript
// lib/ApiClient.ts type FetchOptions = RequestInit & { params?: Record<string, string | number>; isMultipart?: boolean; // FormData 여부 }; const BASE_URL = process.env.NEXT_PUBLIC_API_URL || ""; async function http<T>(path: string, options: FetchOptions = {}, requireAuth = false): Promise<T> { const { params, isMultipart, ...restOptions } = options; // URL 파라미터(Query String) 처리 const url = new URL(`${BASE_URL}${path}`); if (params) { Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value.toString()), ); } // 헤더 설정 const headers = new Headers(restOptions.headers); // 인증 처리 if (requireAuth) { // 예시: TokenManager에서 토큰을 가져오는 방식 const token = JwtTokenManager.getAccessToken(); if (token) { headers.set("Authorization", `Bearer ${token}`); } } // FormData가 아닐 때만 Content-Type을 JSON으로 설정 // (FormData일 때는 브라우저가 자동으로 boundary를 포함한 multipart/form-data를 설정해줌) if (!isMultipart && !(restOptions.body instanceof FormData)) { if (!headers.has("Content-Type")) { headers.set("Content-Type", "application/json"); } } const config = { ...restOptions, headers, }; const response = await fetch(url.toString(), config); if (!response.ok) { // 에러 응답 처리 const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || "API 호출 중 오류가 발생했습니다."); } // 204 No Content 대비 if (response.status === 204) return {} as T; return response.json(); } /** * 전역에서 사용할 API 클라이언트 객체 */ export const api = { // 일반 GET/POST (JSON) get: <T>(path: string, options?: FetchOptions) => http<T>(path, { ...options, method: "GET" }), post: <T>(path: string, body?: any, options?: FetchOptions) => http<T>(path, { ...options, method: "POST", body: JSON.stringify(body) }), // 인증용 (Bearer Token 자동 삽입) // 클라이언트 측에서는 매개변수로 토큰을 받거나, TokenManager에서 꺼내 쓸 수 있습니다. auth: { get: <T>(path: string, options?: FetchOptions) => http<T>(path, { ...options, method: "GET" }, true), post: <T>(path: string, body?: any, options?: FetchOptions) => http<T>(path, { ...options, method: "POST", body: JSON.stringify(body) }, true), // 파일 업로드 (FormData) upload: <T>(path: string, formData: FormData, options?: FetchOptions) => http<T>(path, { ...options, method: "POST", body: formData, isMultipart: true }, true), }, };