diff --git a/packages/FE/src/main.tsx b/packages/FE/src/main.tsx index f2f91d7c..6b8a8349 100644 --- a/packages/FE/src/main.tsx +++ b/packages/FE/src/main.tsx @@ -4,11 +4,16 @@ import { BrowserRouter } from 'react-router-dom'; import './index.css'; import App from '@/app'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient(); createRoot(document.getElementById('root')!).render( - + + + , ); diff --git a/packages/FE/src/shared/api/index.ts b/packages/FE/src/shared/api/index.ts new file mode 100644 index 00000000..e1189941 --- /dev/null +++ b/packages/FE/src/shared/api/index.ts @@ -0,0 +1,57 @@ +// TODO: env 파일로 관리하기 +const BASE_URL = 'http://localhost:3000/'; + +interface FetchOptions extends Omit { + headers?: Record; + body?: unknown; +} + +export const apiClient = { + get: (endPoint: string, options: FetchOptions = {}) => + sendRequest(endPoint, { ...options, method: 'GET' }), + post: (endPoint: string, options: FetchOptions = {}) => + sendRequest(endPoint, { ...options, method: 'POST' }), + put: (endPoint: string, options: FetchOptions = {}) => + sendRequest(endPoint, { ...options, method: 'PUT' }), + delete: (endPoint: string, options: FetchOptions = {}) => + sendRequest(endPoint, { ...options, method: 'DELETE' }), +}; + +async function sendRequest(endPoint: string, options: FetchOptions = {}, timeout: number = 10000) { + const { headers, body, ...restOptions } = options; + + const abortController = new AbortController(); + const timeoutId = setTimeout(() => { + abortController.abort(); + }, timeout); + + try { + const response = await fetch(`${BASE_URL}${endPoint}`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}`, + ...headers, + }, + // axios처럼 객체를 body로 받을 수 있도록 설정 + ...(body !== undefined && + body !== null && { + body: typeof body === 'object' ? JSON.stringify(body) : (body as BodyInit), + }), + signal: abortController.signal, + ...restOptions, + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage || 'API 요청 실패'); + } + return response.json(); + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('요청 시간이 초과되었습니다.'); + } + throw error; + } finally { + clearTimeout(timeoutId); + } +} diff --git a/packages/FE/tsconfig.json b/packages/FE/tsconfig.json index e21e04e1..fa9f0d6e 100644 --- a/packages/FE/tsconfig.json +++ b/packages/FE/tsconfig.json @@ -5,7 +5,7 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - "types": ["vite-plugin-svgr/client"], + "types": ["vite-plugin-svgr/client", "node"], /* Bundler mode */ "moduleResolution": "Bundler",