diff --git a/composables/useApi.ts b/composables/useApi.ts index 05d2b6a..ce66a77 100644 --- a/composables/useApi.ts +++ b/composables/useApi.ts @@ -1,36 +1,36 @@ -import { useRuntimeConfig } from "#imports"; - -export const useApi = async ( - path: string, - options: { - method?: "get" | "post" | "put" | "delete"; - body?: any; - query?: Record; - headers?: HeadersInit; - credentials?: RequestCredentials; - } = {} -): Promise => { - const config = useRuntimeConfig(); - const method = options.method ? options.method.toUpperCase() : "GET"; - - try { - const response = await $fetch( - `${config.public.apiBase}${config.public.contextPath}${path}`, - { - method: method as any, - body: options.body, - query: options.query, - credentials: options.credentials || "include", // 쿠키 자동 전송 - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - } - ); - - return response; - } catch (error) { - console.error("API 호출 실패:", error); - throw error; - } +/** + * API 호출을 위한 편의 함수 + * + * @template T - 응답 데이터의 타입 + * @param path - API 엔드포인트 경로 (예: '/users', '/users/1') + * @param opts - 요청 옵션 (method, body, headers 등) + * @returns Promise - API 응답 데이터 + * + * @example + * // GET 요청 + * const users = await useApi('/users') + * + * // POST 요청 + * const newUser = await useApi('/users', { + * method: 'POST', + * body: { name: 'John', email: 'john@example.com' } + * }) + * + * // PUT 요청 + * const updatedUser = await useApi('/users/1', { + * method: 'PUT', + * body: { name: 'John Updated' } + * }) + * + * // DELETE 요청 + * await useApi('/users/1', { method: 'DELETE' }) + * + * // FormData 업로드 + * const formData = new FormData() + * formData.append('file', file) + * await useApi('/upload', { method: 'POST', body: formData }) + */ +export const useApi = (path: string, opts?: any): Promise => { + const { $api } = useNuxtApp(); + return ($api as any)(path, opts); }; diff --git a/plugins/api.ts b/plugins/api.ts new file mode 100644 index 0000000..79093ca --- /dev/null +++ b/plugins/api.ts @@ -0,0 +1,44 @@ +export default defineNuxtPlugin(() => { + const config = useRuntimeConfig(); + const baseURL = `${config.public.apiBase}${config.public.contextPath}`; + + const api = $fetch.create({ + baseURL, + credentials: "include", + onRequest({ request, options }) { + // 1) GET/HEAD가 아니면 body만 넣기 (GET에 body 금지) + const method = (options.method ?? "GET").toUpperCase(); + if (method === "GET" || method === "HEAD") { + delete (options as any).body; + } + + // 2) FormData면 Content-Type 자동 지정 금지 + const isFormData = + typeof FormData !== "undefined" && options.body instanceof FormData; + options.headers = { + ...(isFormData ? {} : { "Content-Type": "application/json" }), + ...(options.headers || {}), + }; + + // 3) SSR 쿠키 포워딩 + if (import.meta.server) { + const cookie = useRequestHeaders(["cookie"])?.cookie; + // request가 절대 URL이면 호스트 비교 + const reqUrl = typeof request === "string" ? request : String(request); + const isBackendApi = + !reqUrl.startsWith("http") || // 상대경로면 내 API + reqUrl.startsWith(baseURL); // 혹은 baseURL과 동일 + + if (cookie && isBackendApi) { + options.headers = { ...(options.headers || {}), cookie } as any; + } + } + }, + onResponseError({ response }) { + // 공통 로깅 + console.error("[API ERROR]", response.status, response._data); + }, + }); + + return { provide: { api } }; +});