
Vue 3 (Composition API) + Vite ๊ธฐ๋ฐ ํ๋ก์ ํธ์์ Axios ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ๊ณ ,
๊ธฐ๋ฅ๋ณ๋ก API ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ชจ๋ํ ํ์ฌ ๊ด๋ฆฌํ๋ ์ค์ ์ ์ธ ๊ตฌ์กฐ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
์ด ๊ธ์์๋ axios ์ค์ ๋ถํฐ ์ธํฐ์ ํฐ ํ์ฉ, API ๋ชจ๋ ๋ถ๋ฆฌ, ์ปดํฌ๋ํธ ์ฐ๋, ํ๊ฒฝ ๋ณ์(. env) ์ฌ์ฉ ํ,
ํ์ฅ ํฌ์ธํธ, ๊ทธ๋ฆฌ๊ณ ํ ํ์ ๊ด์ ์ ์ ์ฉ ์ฌ๋ก๊น์ง ํญ๋๊ฒ ๋ค๋ฃน๋๋ค.
Composition API ๋ฌธ๋ฒ๊ณผ ๋งํฌ๋ค์ด ํ์์ผ๋ก ์์ฑ๋์์ผ๋ฉฐ, ์์ ์ฝ๋๋ Vue 3 ๊ธฐ์ค์ผ๋ก ์ค๋ช ํฉ๋๋ค.
1. Axios ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ๋ ์ด์ ์ ์ธํฐ์ ํฐ ์๋ฆฌ
Axios ์ธ์คํด์ค๋ฅผ ๋ณ๋๋ก ์์ฑํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ ๋๊ท๋ชจ Vue ํ๋ก์ ํธ์์ ๋ฐ๋ณต ์ฝ๋๋ฅผ ์ค์ด๊ณ ,
์ค์ ์ ์ผ์ํํ๊ธฐ ์ํด ๋งค์ฐ ์ ์ฉํฉ๋๋ค. Axios ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ด ์์ต๋๋ค:
- ๊ณตํต ์ค์ ์ ์ค์๊ด๋ฆฌ: baseURL, timeout, ๊ณตํต headers ๋ฑ ๊ธฐ๋ณธ ์ค์ ์ ํ ๊ณณ์์ ์ ์ํด ๋๊ณ ์ฑ ์ ์ฒด์์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ ๋ชจ๋์์ ์ผ์ผ์ด ์ค์ ์ ๋ฐ๋ณตํ์ง ์์๋ ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด API ์๋ฒ ์ฃผ์๋ฅผ axios.defaults.baseURL ๋๋ ์ธ์คํด์ค ์์ฑ์์ ์ง์ ํด ๋๋ฉด, ๊ฐ๋ณ ์์ฒญ์์ ํด๋น ์ฃผ์๋ฅผ ์๋ตํด๋ ๋ฉ๋๋ค.
- ํ๊ฒฝ๋ณ ์ค์ ๋ถ๊ธฐ: ๊ฐ๋ฐ(dev), ์คํ ์ด์ง(staging), ์ด์(prod) ํ๊ฒฝ๋ง๋ค API ์๋ฒ ์ฃผ์๋ ์ค์ ์ด ๋ค๋ฅผ ๊ฒฝ์ฐ, ๋น๋ ๋ชจ๋๋ณ๋ก ๋ค๋ฅธ. env.env ๊ฐ์ด๋ ์ค์ ์ ์ ์ฉํ ์ ์์ต๋๋ค. ํ๋์ ์ธ์คํด์ค์ ํ๊ฒฝ ๋ณ์๋ก baseURL ๋ฑ์ ์ฃผ์ ํ๋ฉด ๋ฐฐํฌ ํ๊ฒฝ์ ๋ฐ๋ผ ์๋์ผ๋ก ์ฌ๋ฐ๋ฅธ URL์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค (์์ธํ ๋ด์ฉ์ ๋ค .env ์น์ ์์ ๋ค๋ฃน๋๋ค).
- ์ธ์ฆ ๋ฐ ๊ณตํต ํค๋ ์ฒ๋ฆฌ: ์ธ์ฆ ํ ํฐ(JWT ๋ฑ)์ ๋ชจ๋ ์์ฒญ ํค๋์ ์๋ ์ฒจ๋ถํ๊ฑฐ๋, ํน์ ํค๋๋ฅผ ๊ณตํต์ผ๋ก ๋ฃ์ด์ผ ํ ๋ ์ธ์คํด์ค ๋ ๋ฒจ์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ชจ๋ ์์ฒญ์ Authorization ํค๋๋ก ํ ํฐ์ ๋ณด๋ด์ผ ํ๋ค๋ฉด, ์ธ์คํด์ค์ ์์ฒญ ์ธํฐ์ ํฐ๋ฅผ ์ถ๊ฐํ์ฌ ๋งค๋ฒ ํค๋๋ฅผ ์ค์ ํ๋ ๋ฐ๋ณต์ ์์จ ์ ์์ต๋๋ค.
- ์์ฒญ/์๋ต ์ ์ฒ๋ฆฌ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ ์ผ์ํ: Axios์ ์ธํฐ์ ํฐ(Interceptor) ๊ธฐ๋ฅ์ ํตํด ์์ฒญ์ด ๋ณด๋ด์ง๊ธฐ ์ ์, ํน์ ์๋ต์ ๋ฐ์ ํ์ ๊ณตํต ๋ก์ง์ ์ฝ์ ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํ์ฉํ๋ฉด ์์ฒญ ์ ์ ๊ณตํต ํค๋ ์ค์ ์ด๋ ๋ก๋ฉ ์ํ ํ์, ์๋ต ํ์ ๋ฐ์ดํฐ ๊ฐ๊ณต์ด๋ ์๋ฌ ๊ณตํต ์ฒ๋ฆฌ ๋ฑ์ ์ค์์์ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ๊ฐ ์ปดํฌ๋ํธ๋ง๋ค ์ค๋ณต ์ฝ๋๋ฅผ ์์ฑํ์ง ์๊ณ ๋, ์ธํฐ์ ํฐ์์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋๋ก ์ค๊ณํ ์ ์์ต๋๋ค.
- ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ: ์์ ์ฌํญ๋ค์ด ์ข ํฉ๋์ด, Axios ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ๋์ผํ ์ค์ ๊ณผ ๋ก์ง์ ์ฌ์ฌ์ฉํ ์ ์๊ณ ์ค์ ๋ณ๊ฒฝ๋ ํ ๊ณณ์์ ์ด๋ฃจ์ด์ง๋ฏ๋ก ์ ์ง๋ณด์๊ฐ ํจ์ฌ ์์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ํ ํฐ ๊ฐฑ์ ๋ก์ง์ด๋ ์๋ฌ ๋ฉ์์ง ์ฒ๋ฆฌ ๋ฐฉ์์ ์์ ํด์ผ ํ ๋, ์ธํฐ์ ํฐ ๋ก์ง๋ง ๊ณ ์น๋ฉด ์ ์ฒด์ ์ ์ฉ๋๋ฏ๋ก ์ผ๊ด์ฑ์ ์ ์งํ ์ ์์ต๋๋ค.
์ธํฐ์ ํฐ(Interceptor)์ ์๋ ์๋ฆฌ
Axios ์ธ์คํด์ค๋ฅผ ๋ง๋ค์๋ค๋ฉด,
instance.interceptors ๊ฐ์ฒด๋ฅผ ํตํด ์์ฒญ(request)๊ณผ ์๋ต(response)์ ๋ํ ์ธํฐ์ ํฐ๋ฅผ ๋ฑ๋กํ ์ ์์ต๋๋ค.
์ธํฐ์ ํฐ๋ ๋ฏธ๋ค์จ์ด์ฒ๋ผ ๋์ํ์ฌ HTTP ์์ฒญ์ด ๋๊ฐ๊ธฐ ์ ์ด๋
์๋ต์ด ๋ค์ด์จ ์งํ์ ํน์ ์ฝ๋๋ฅผ ์คํํ ์ ์๊ฒ ํด์ค๋๋ค.
๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// Axios ์ธ์คํด์ค ์์ฑ (์: src/api/index.js)
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL // ํ๊ฒฝ๋ณ์์์ API ๊ธฐ๋ณธ URL ์ค์
});
// ์์ฒญ ์ธํฐ์
ํฐ ์ถ๊ฐ
api.interceptors.request.use(
config => {
// ์์ฒญ ๋ณด๋ด๊ธฐ ์ ์ ์ํํ ์์
return config;
},
error => {
// ์์ฒญ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ฒ๋ฆฌ
return Promise.reject(error);
}
);
// ์๋ต ์ธํฐ์
ํฐ ์ถ๊ฐ
api.interceptors.response.use(
response => {
// ์๋ต ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๊ฑฐ๋ ๋ฐ๋ก ๋ฐํ
return response;
},
error => {
// ์๋ต ์๋ฌ ๊ณต๋ ์ฒ๋ฆฌ
return Promise.reject(error);
}
);
export default api;
์ ์ฝ๋์์ ์์ฒญ ์ธํฐ์ ํฐ๋ request.use๋ฅผ ํตํด ๋ฑ๋ก๋๋ฉฐ ์์ฒญ์ด ๋ณด๋ด์ง๊ธฐ ์ง์ ์ ํธ์ถ๋ฉ๋๋ค.
์ฌ๊ธฐ์ config ๊ฐ์ฒด๋ฅผ ์กฐ์ํ์ฌ ํค๋๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ฝ์ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋ ๋ฑ ์์ ์ ํ ๋ค
์์ ๋ config๋ฅผ ๋ฐํํ๋ฉด ์ค์ ๋คํธ์ํฌ ์์ฒญ์ ๋ฐ์๋ฉ๋๋ค.
์๋ต ์ธํฐ์ ํฐ๋ response.use๋ฅผ ํตํด ๋ฑ๋ก๋๊ณ ,
์๋ฒ๋ก๋ถํฐ ์๋ต์ด ๋์ฐฉํ๋ฉด then/catch๋ก ๊ฐ๋ณ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ์ธํฐ์ ํฐ๊ฐ ๋จผ์ ์คํ๋ฉ๋๋ค.
์ฒซ ๋ฒ์งธ ์ฝ๋ฐฑ์ ์ฑ๊ณต ์๋ต์ ๋ํ ์ฒ๋ฆฌ๋ฅผ, ๋ ๋ฒ์งธ ์ฝ๋ฐฑ์ ์๋ฌ ์๋ต์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค.
๐ ์ฐธ๊ณ : Axios ์ธํฐ์ ํฐ๋ ๋ฑ๋ก๋ ์์๋๋ก ์คํ๋ฉ๋๋ค.
๋ํ ์ฌ๋ฌ ๊ฐ์ ์ธํฐ์ ํฐ๋ฅผ ์ฒด์ธ์ฒ๋ผ ์ฐ๊ฒฐํ ์๋ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์์ฒญ ์ธํฐ์ ํฐ๋ฅผ 2๊ฐ ๋ฑ๋กํ๋ฉด,
๋จผ์ ๋ฑ๋ก๋ ์ธํฐ์ ํฐ๊ฐ ์คํ๋๊ณ return config ํ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ธํฐ์ ํฐ๊ฐ ์ด์ด๋ฐ์ ์ฒ๋ฆฌํฉ๋๋ค.
์๋ต ์ธํฐ์ ํฐ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฑ๋ก ์์ผ๋ก ์ ์ฉ๋ฉ๋๋ค.
์ธํฐ์ ํฐ ๋ด๋ถ์์ Promise.reject์ด๋ ์๋ฌ๋ฅผ ๋์ง๋ฉด
๊ทธ ์ดํ ์ ์ ์๋ต ์ธํฐ์ ํฐ๋ ๊ฑด๋๋ฐ๊ณ ๋ฐ๋ก ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง๋๋ค.
์ด์ ๊ฐ๊ฐ ์์ฒญ ๋จ๊ณ์ ์๋ต ๋จ๊ณ์์ ์ธํฐ์ ํฐ๋ฅผ ํ์ฉํ๋ ์ค์ ์์์ ํ์ ์์๋ณด๊ฒ ์ต๋๋ค.
2. ์์ฒญ ์ธํฐ์ ํฐ: ์ค์ ์์ ๋ฐ ๋ณด์/UX ์ฒ๋ฆฌ ์ ๋ต
์์ฒญ ์ธํฐ์ ํฐ๋ HTTP ์์ฒญ์ด ๋ณด๋ด์ง๊ธฐ ์ ์ ํธ์ถ๋๋ฏ๋ก,
๋ณด์ ๊ด๋ จ ์ฒ๋ฆฌ๋ ์ฌ์ฉ์ ๊ฒฝํ(UX) ๊ฐ์ ์์ ์ ๋ฃ๊ธฐ์ ์ ํฉํฉ๋๋ค. ์ฃผ์ ํ์ฉ ์ฌ๋ก์ ์ ๋ต์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ธ์ฆ ํ ํฐ ์๋ ์ฒจ๋ถ (๋ณด์): ์์ ์ธ๊ธํ ๋๋ก, ๋ก๊ทธ์ธ ํ ๋ฐ๊ธ๋ฐ์ ํ ํฐ(JWT ๋ฑ)์ ๋ชจ๋ ์์ฒญ ํค๋์ ๋ฃ์ด์ผ ํ๋ ๊ฒฝ์ฐ ์์ฒญ ์ธํฐ์ ํฐ์์ ์ผ๊ด ์ฒ๋ฆฌํฉ๋๋ค. ์๋ฅผ ๋ค์ด, localStorage๋ Pinia/Vuex ๋ฑ์ ์ ์ฅ๋ ํ ํฐ์ ๊บผ๋ด์ ์์ฒญ ํค๋์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ API ํจ์๋ ์ปดํฌ๋ํธ์์ ์ผ์ผ์ด ํ ํฐ์ ๋ฃ์ง ์์๋ ๋๊ณ , ํ ํฐ ๊ฐฑ์ ์์๋ ํ ๊ณณ๋ง ์์ ํ๋ฉด ๋๋ฏ๋ก ๋ณด์ ๋ฐ ํธ์์ฑ์ด ํฅ์๋ฉ๋๋ค. ๊ตฌํ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:์ ์ฝ๋์ฒ๋ผ ์ค์ ํ๋ฉด, authToken์ด ์กด์ฌํ ๋ ๋ชจ๋ ์์ฒญ์ Authorization: Bearer <token> ํค๋๊ฐ ์๋ ์ฒจ๋ถ๋ฉ๋๋ค. ํ ํฐ์ด ์๊ฑฐ๋ ๋ง๋ฃ๋ ๊ฒฝ์ฐ์๋ ํค๋๊ฐ ๋ถ์ง ์์ผ๋ฏ๋ก, ๋ฐฑ์๋์์ 401 Unauthorized ์๋ต์ ์ฃผ๋ฉด ์ดํ ์๋ต ์ธํฐ์ ํฐ์์ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
- // ์์ฒญ ์ธํฐ์ ํฐ์์ Authorization ํค๋ ์ถ๊ฐ ์์ api.interceptors.request.use(config => { const token = localStorage.getItem('authToken'); // ํ ํฐ ์ ์ฅ์์์ ๊ฐ์ ธ์ค๊ธฐ if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => Promise.reject(error));
- ์์ฒญ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ (UX): ์ฌ์ฉ์์๊ฒ ๋ก๋ฉ ์ค ํผ๋๋ฐฑ์ ์ฃผ๊ธฐ ์ํด, ์์ฒญ์ด ์์๋ ๋ ๋ก๋ฉ ์ํ๋ฅผ ํ์ํ๊ณ ์๋ต์ด ์๋ฃ๋๋ฉด ์จ๊ธฐ๋ ํจํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ ์ญ ์ํ(store)์ isLoading ๊ฐ์ ์์ฒญ ์ธํฐ์ ํฐ์์ true๋ก ์ค์ ํ๊ณ , ์๋ต ์ธํฐ์ ํฐ์์ false๋ก ๋๋๋ฆฌ๋ ์ ๋ต์ ์๋๋ค. ๋๋ ๋จ์ํ document.body์ ๋ก๋ฉ ์คํผ๋๋ฅผ ์ถ๊ฐ/์ ๊ฑฐํ๋ ์์ผ๋ก ๊ตฌํํ ์๋ ์์ต๋๋ค. ์ด๋ฅผ ํตํด API ํธ์ถ์ด ์ง์ฐ๋ ๋ ์ฌ์ฉ์๊ฐ ๋ฌด์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ๋ณด๋ค ์งํ ์ค์์ ์ ์ ์์ด UX๋ฅผ ๋์ผ ์ ์์ต๋๋ค.
- ์์ฒญ ๋ก๊ทธ & ๋๋ฒ๊น : ์ธํฐ์ ํฐ๋ฅผ ํ์ฉํด ๋ชจ๋ ์์ฒญ์ URL, ํ๋ผ๋ฏธํฐ ๋ฑ์ console.debug๋ก ์ถ๋ ฅํ๊ฑฐ๋, ๊ฐ๋ฐ ๋ชจ๋์์๋ง ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ ๋๋ฒ๊น ์ ๋์์ด ๋๋ฉฐ, ๋ฌธ์ ๋ฐ์ ์ ์ด๋ค ์์ฒญ์ด ์์๋์ง ์ถ์ ํ ์ ์์ต๋๋ค. ๋ค๋ง, ์ฝ์ ๋ก๊ทธ๋ ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ์ฌ์ฉํ๊ณ , ์ด์ ํ๊ฒฝ์์๋ ์ง๋์น ์ ๋ณด ๋ ธ์ถ์ด ์๋๋ก ์กฐ๊ฑด๋ถ๋ก ๋ฃ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- ๊ธฐํ ์์ฒญ ์ ์ฒ๋ฆฌ: ํ์์ ๋ฐ๋ผ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ์ง๋ ฌํ/์ํธํํ๊ฑฐ๋, ํน์ API ํค๋ฅผ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์ ๋ถ์ด๋ ์์ ๋ ์ธํฐ์ ํฐ์์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ชจ๋ API ์์ฒญ์ ?lang=ko ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ถ์ฌ ๋ค๊ตญ์ด ์ฒ๋ฆฌ๋ฅผ ํ๊ฑฐ๋, GraphQL๊ฐ์ด ํน์ํ ์์ฒญ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ ๋ ์์ฒญ ๋ณธ๋ฌธ์ ๋ณํํ๋ ๋ฑ์ ์ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ต๋๋ค.
๐ก Tip: ์ธํฐ์ ํฐ ๋ก์ง์ด ๋ณต์กํด์ง๋ฉด ๋ณ๋์ ์ ํธ ํจ์๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
์๋ฅผ ๋ค์ด ํ ํฐ ์ฒจ๋ถ ๋ก์ง์ attachAuthToken(config) ํจ์๋ก ๋ฝ์๋ด๊ฑฐ๋,
๋ก๋ฉ ํ์ ๋ก์ง์ ์ ์ญ LoadingService.start()/stop() ํํ๋ก ์ถ์ํํ๋ฉด ์ธํฐ์ ํฐ ๋ด๋ถ๋ฅผ ๊น๋ํ๊ฒ ์ ์งํ ์ ์์ต๋๋ค.
3. ์๋ต ์ธํฐ์ ํฐ: ์ค์ ์์ ๋ฐ ๋ณด์/UX ์ฒ๋ฆฌ ์ ๋ต
์๋ต ์ธํฐ์ ํฐ๋ ์๋ฒ๋ก๋ถํฐ ์๋ต์ด ๋์ฐฉํ ์งํ (์ปดํฌ๋ํธ์์ .then์ด๋ await๋ก ์ฒ๋ฆฌํ๊ธฐ ์ ์) ํธ์ถ๋๋ฉฐ,
๊ณตํต ์๋ฌ ์ฒ๋ฆฌ๋ ์๋ต ๋ฐ์ดํฐ ๊ฐ๊ณต, ์ฌ์ฉ์ ์๋ฆผ ๋ฑ์ ํ์ฉ๋ฉ๋๋ค.
์ค์ ์ ๋ต๊ณผ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๊ธ๋ก๋ฒ ์๋ฌ ์ฒ๋ฆฌ ๋ฐ ๋ณด์ ๋์: ๊ฐ์ฅ ํํ ํจํด์ ์ธ์ฆ ๋ง๋ฃ๋ ๊ถํ ์ค๋ฅ ๋ฑ์ ๊ณตํต ์๋ฌ๋ฅผ ์๋ต ์ธํฐ์ ํฐ์์ ์ก์๋ด๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์๋ฒ๊ฐ 401 Unauthorized ์ํ ์ฝ๋๋ฅผ ์๋ตํ๋ฉด ํ ํฐ ๋ง๋ฃ๋ก ๋ณด๊ณ ์ธํฐ์ ํฐ์์ ์๋์ผ๋ก ๋ก๊ทธ์์ ์ฒ๋ฆฌํ๊ฑฐ๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ํ ์ ์์ต๋๋ค. ์๋๋ ๊ทธ๋ฐ ์ฒ๋ฆฌ๋ฅผ ํ๋ ์ฝ๋ ์์์ ๋๋ค:์ ์ธํฐ์ ํฐ ์์๋ ๊ณตํต์ ์ธ ์๋ฌ ์ํฉ์ ๋ํด ์ ์ฒ๋ฆฌํ๋ ๋ชจ์ต์ ๋๋ค. 401์ด๋ฉด ํ ํฐ์ ์ง์ฐ๊ณ ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ณด๋ด๊ฑฐ๋, 403์ด๋ฉด ๊ถํ ์์ ์๋ด๋ฅผ ํ๊ณ , 500๋ ์ค๋ฅ์ด๋ฉด ์ฝ์์ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ์ฌ์ฉ์์๊ฒ ์ผ๋ฐ์ ์ธ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค. ์ด๋ฐ ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด ๊ฐ ์ปดํฌ๋ํธ๋ง๋ค ๋์ผํ ์๋ฌ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ๋ฐ๋ณตํ์ง ์์๋ ๋๊ณ , ๋ณด์ ์ ์ค์ํ ์กฐ์น(ํ ํฐ ๋ง๋ฃ ์ ๋ก๊ทธ์์ ๋ฑ)๋ฅผ ๋น ๋จ๋ฆฌ์ง ์๋๋ก ์ค์์์ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ํ์์ ๋ฐ๋ผ ํน์ ์๋ฌ ์ฝ๋์ ๋ํ ๋ง์ถค ์ฒ๋ฆฌ๋ ๊ฐ๋ฅํฉ๋๋ค (์: ๋ฐฑ์๋๊ฐ ์ปค์คํ ์๋ฌ ์ฝ๋๋ฅผ ๋ด๋ ค์ค ๊ฒฝ์ฐ switch๋ฌธ์ผ๋ก ๋ถ๊ธฐ ์ฒ๋ฆฌ).
api.interceptors.response.use(
response => {
// ์ ์ ์๋ต์ ๊ทธ๋๋ก ๋ฐํ
return response;
},
error => {
if (error.response) {
const status = error.response.status;
if (status === 401) {
// 401 Unauthorized: ์ธ์ฆ ๋ง๋ฃ ์ฒ๋ฆฌ
// ์: ์คํ ๋ฆฌ์ง์ ํ ํฐ ์ ๊ฑฐ ๋ฐ ๋ก๊ทธ์ธ ํ์ด์ง ์ด๋
localStorage.removeItem('authToken');
router.push('/login'); // vue-router ์ฌ์ฉ ๊ฐ์
} else if (status === 403) {
// 403 Forbidden: ๊ถํ ์์
alert('๊ถํ์ด ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํ์ธ์.');
} else if (status >= 500) {
// 5xx ์๋ฒ ์ค๋ฅ: ์ฌ์ฉ์์๊ฒ ์๋ฆผ
console.error('Server error:', status);
alert('์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
}
}
// ์์์ ์ฒ๋ฆฌ ํ์๋ ์๋ฌ๋ฅผ ๋ค์ ๋์ ธ์ฃผ์ด
// ๊ฐ๋ณ ์์ฒญ์์๋ ํ์ํ๋ฉด ์ถ๊ฐ ์ฒ๋ฆฌ ๊ฐ๋ฅ
return Promise.reject(error);
}
);
- ์๋ต ๋ฐ์ดํฐ ๊ฐ๊ณต: API ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ณณ์์ ์ผ๊ด๋ ํํ๋ก ์ฐ๊ณ ์ถ๋ค๋ฉด, ์ธํฐ์ ํฐ์์ ๊ฐ๊ณตํ์ฌ ๋ฐํํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, API๊ฐ { data: ..., errorMessage: ... } ํ์์ผ๋ก ์๋ตํ๋ฉด, ์ธํฐ์ ํฐ์์ response.data์ data ํ๋๋ง ์ถ๋ ค์ ๋ฐํํ๋๋ก ๋ง๋ค ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํด๋๋ฉด ์ค์ ์ปดํฌ๋ํธ์์๋ .data๊น์ง ์ ๊ทผํ ํ์ ์์ด ๋ฐ๋ก ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ป์ ์ ์์ด ํธ๋ฆฌํฉ๋๋ค. ๋จ, ์ผ๊ด๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ๋ ๊ฒ ์ข๊ณ , API๋ง๋ค ์๋ต ํํ๊ฐ ๋ง์ด ๋ค๋ฅด๋ฉด ์ธํฐ์ ํฐ์์ ์ผ๊ด ์ฒ๋ฆฌํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ ๊ฐ๋ณ ํธ์ถ๋ถ์์ ์ฒ๋ฆฌํ๋ ํธ์ด ๋์ ์ ์์ต๋๋ค.
- ์ฌ์ฉ์ ํผ๋๋ฐฑ ์ฒ๋ฆฌ (UX): ์๋ต ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ์ฃผ๋ ๊ฒ๋ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ฑ๊ณต ๋ฉ์์ง๊ฐ ํ์ํ API ์์ฒญ์ ๊ฒฝ์ฐ ์๋ต ์ธํฐ์ ํฐ์์ ์ํ ์ฝ๋๋ฅผ ๋ณด๊ณ ํ ์คํธ ์๋ฆผ์ ๋์ฐ๋ ์์ ๋๋ค. ํน์ 404 ๊ฐ์ ์๋ฌ์ ๋ํด์ "๋ฐ์ดํฐ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค" ๋ฑ์ ์ฌ์ฉ์์ฉ ์๋ฆผ์ ์ค์์์ ์ฒ๋ฆฌํ๋ฉด UX์ ์ผ๊ด์ฑ์ ์ค ์ ์์ต๋๋ค. ์ด๋ฐ ๊ธ๋ก๋ฒ ์๋ฆผ์ Vuetify์ Snackbar๋ Vue Toast ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฑ์ integrate ํ์ฌ ๊ตฌํํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. (์: 401/403 ์ ์ธํฐ์ ํฐ์์ vue-toastification์ toast.error(...)๋ฅผ ํธ์ถํ์ฌ ์ฌ์ฉ์์๊ฒ ํ์ ์๋ฆผ์ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์๋ ๊ฐ๋ฅํฉ๋๋ค.)
- ํ ํฐ ๊ฐฑ์ ๋ฑ ์ฌ์์ฒญ ์ฒ๋ฆฌ: ์ข ๋ ๊ณ ๊ธ ํจํด์ผ๋ก, ํ ํฐ์ด ๋ง๋ฃ๋์ด 401์ด ๋ฐ์ํ์ ๋ ๊ณง๋ฐ๋ก ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ณด๋ด๋ ๋์ ํ ํฐ ๊ฐฑ์ API๋ฅผ ํธ์ถํ์ฌ ์๋์ผ๋ก ํ ํฐ์ ์ฌ๋ฐ๊ธ๋ฐ๊ณ ์คํจํ๋ ์์ฒญ์ ์ฌ์๋ํ๋ ๋ฐฉ์์ ๊ตฌํํ ์๋ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ์๋ต ์ธํฐ์ ํฐ์์ 401์ ๊ฐ์งํ๋ฉด ๊ฐฑ์ API (/auth/refresh ๋ฑ)๋ฅผ ํธ์ถํ๊ณ , ์ ํ ํฐ์ ์ป์ ํ ์๋ ์์ฒญ์ ํ ๋ฒ ๋ ๋ณด๋ด๋๋ก ํ ์ ์์ต๋๋ค. ์ด ๋ก์ง์ ๊ตฌํ ๋์ด๋๊ฐ ์์ง๋ง, ์ฌ์ฉ์๋ ๋ชจ๋ฅด๋ ์ฌ์ด์ ์ธ์ ์ด ์ฐ์ฅ๋์ด ๋ก๊ทธ์ธ ์ ์ง ๊ฒฝํ์ด ๊ฐ์ ๋๋ ์ฅ์ ์ด ์์ต๋๋ค. ๋ค๋ง refresh ํ ํฐ๋ง์ ๋ง๋ฃ๋๋ฉด ์ต์ข ์ ์ผ๋ก๋ ๋ก๊ทธ์์์ ํด์ผ ํฉ๋๋ค. ์ด๋ฌํ ํ๋ฆ์ ๋ณด์๊ณผ UX๋ฅผ ๋ชจ๋ ์ก๊ธฐ ์ํ ์ ๋ต์ด๋ฉฐ, ํ์์ ์ ์ฑ ์ ์ ํด ๊ตฌํํ๊ฒ ๋ฉ๋๋ค.
โ ๏ธ ์ฃผ์: ์๋ต ์ธํฐ์ ํฐ์์ ์๋ฌ๋ฅผ Promise.rejectPromise.reject ํ์ง ์๊ณ ๋จน์ด๋ฒ๋ฆฌ๋ฉด(Promise๋ฅผ reject ํ์ง ์์ผ๋ฉด)
ํธ์ถํ ์ปดํฌ๋ํธ ์ธก์์๋ ํด๋น ์๋ฌ๋ฅผ ์ธ์งํ์ง ๋ชปํฉ๋๋ค.
๊ธ๋ก๋ฒ ์ธํฐ์ ํฐ์์ ์ฒ๋ฆฌํ ๋ค์๋ ๊ฐ ์ปดํฌ๋ํธ์์ ์ถ๊ฐ ์ฒ๋ฆฌ๊ฐ ํ์ํ ์ ์๋ค๋ฉด
Promise.reject(error)๋ฅผ ๋ฐํํ์ฌ ์ดํ ๋ก์ง์ผ๋ก ์๋ฌ๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค.
๋ฐ๋๋ก, ํน์ ์๋ฌ๋ฅผ ์ ์ญ์์ ์์ ํ ์ฒ๋ฆฌํ๊ณ ์ปดํฌ๋ํธ์์ ์ ๊ฒฝ ์ฐ์ง ์๋๋ก ํ๋ ค๋ฉด
๊ทธ ์๋ฌ๋ฅผ catch ํ ํ Promise.reject๋ฅผ ํ์ง ์๊ฑฐ๋, ์๋ฏธ ์๋ ๊ฐ์ resolve ํ๋๋ก ์ฒ๋ฆฌํ ์๋ ์์ต๋๋ค.
4. API ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ ์ค๊ณ (์๋น์ค/๋๋ฉ์ธ ๋จ์ ๋ถ๋ฆฌ)
Axios ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ๋ค๋ฉด, ์ด๋ฅผ ํ์ฉํ API ํธ์ถ ํจ์๋ค์ ์ ๋ฆฌํ๋ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํด์ผ ํฉ๋๋ค.
๊ท๋ชจ๊ฐ ์์ ์ฑ์์๋ ์ปดํฌ๋ํธ ๋ด์์ axios.get('...')์ ์ง์ ํธ์ถํด๋ ๋์ง๋ง,
์ฑ์ด ์ปค์ง์๋ก API ๊ด๋ฆฌ์ ์ผ์ํ์ ๋ชจ๋ํ๊ฐ ํ์ํฉ๋๋ค.
API ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ง๋ค์ด ๊ธฐ๋ฅ๋ณ๋ก ๋ถ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ด ์์ต๋๋ค:
- ๊ด๋ จ๋ API ์๋ํฌ์ธํธ๋ค์ ํ ํ์ผ์ ๋ชจ์ ๊ด๋ฆฌํ์ฌ ์ฐพ๊ธฐ ์ฝ๊ณ , ์์ ๋ ์ฉ์ดํฉ๋๋ค. (์: ์ฌ์ฉ์ ๊ด๋ จ API๋ userApi.js์, ์ํ ๊ด๋ จ API๋ productApi.js์ ์ ์)
- ์ปดํฌ๋ํธ๋ค์ API ๊ตฌํ ์์ธ๋ฅผ ์ ํ์ ์์ด ํด๋น ๋ชจ๋์ ํจ์๋ง ํธ์ถํ๋ฉด ๋๋ฏ๋ก, UI ์ฝ๋์ ๋ฐ์ดํฐ ํต์ ์ฝ๋๊ฐ ๋ถ๋ฆฌ๋ฉ๋๋ค. ์ด๋ ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ๋ฅผ ํตํด ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์๋ฅผ ๋์ฌ์ค๋๋ค.
- ํ ์์ ์ API ๋ชจ๋๋ณ๋ก ๋ด๋น์๋ฅผ ๋๋๊ธฐ ์ข์ต๋๋ค. ํ ์ฌ๋์ด userApi.js๋ฅผ ์์ ํ๋ ๋์ ๋ค๋ฅธ ์ฌ๋์ productApi.js๋ฅผ ์์ ํ๋ ํํ๋ก ๋ณ๋ ฌ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํ๊ณ , ์ถฉ๋์ด ์ค์ด๋ญ๋๋ค.
- ๋ฐฑ์๋ API ๋ช ์ธ ๋ณ๊ฒฝ์ด๋ ๊ณตํต ๋ก์ง ์์ ์, API ๋ชจ๋ ๋ด๋ถ๋ง ์์ ํ๋ฉด ๋๋ฏ๋ก ์ํฅ ๋ฒ์๊ฐ ์ ํ์ ์ ๋๋ค. ๋ํ API ํธ์ถ ํจ์๋ฅผ ์ฌ์ฌ์ฉํจ์ผ๋ก์จ ์ผ๊ด๋ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ ์์
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ด src/api/ ๋๋ ํฐ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค:
src/api/
โโโ index.js # Axios ์ธ์คํด์ค ์์ฑ ๋ฐ ์ธํฐ์
ํฐ ์ค์
โโโ auth.js # ์ธ์ฆ(Auth) ๊ด๋ จ API ํจ์ ๋ชจ๋
โโโ user.js # ์ฌ์ฉ์(User) ๊ด๋ จ API ํจ์ ๋ชจ๋
โโโ product.js # ์ํ(Product) ๊ด๋ จ API ํจ์ ๋ชจ๋
๊ฐ ๋ชจ๋ ํ์ผ์๋ ํด๋น ๋๋ฉ์ธ(์๋น์ค)์ ๊ด๋ จ๋ API ํธ์ถ ํจ์๋ค์ด ๋ค์ด์๊ณ ,
๊ณตํต์ผ๋ก index.js์์ ๋ด๋ณด๋ธ axios ์ธ์คํด์ค(api ๋ฑ)๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์์ ์ฝ๋๋ฅผ ํตํด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
- src/api/index.js: Axios ์ธ์คํด์ค ์ค์
// src/api/index.js
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // ํ๊ฒฝ๋ณ์๋ก๋ถํฐ API ์๋ฒ URL ์ค์
timeout: 5000, // ์์ฒญ ํ์์์ 5์ด (ํ์์)
});
// ์ธํฐ์
ํฐ ์ค์ (์์ ์ค๋ช
ํ ๋๋ก)
api.interceptors.request.use(...);
api.interceptors.response.use(...);
export default api;
- src/api/user.js: ์ฌ์ฉ์ API ๋ชจ๋ ์์
// src/api/user.js
import api from './index';
// ์ฌ์ฉ์ API ์๋ํฌ์ธํธ ํจ์๋ค ์ ์
export const userAPI = {
getProfile() {
return api.get('/user/profile');
},
updateProfile(data) {
return api.put('/user/profile', data);
},
uploadAvatar(file) {
// ์์: ํ์ผ ์
๋ก๋์ฉ ๋ณ๋ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ ์๋ ์์
return api.post('/user/avatar', file);
}
};
- src/api/auth.js: ์ธ์ฆ API ๋ชจ๋ ์์
// src/api/auth.js
import api from './index';
export const authAPI = {
login(credentials) {
return api.post('/auth/login', credentials);
},
logout() {
return api.post('/auth/logout');
},
refreshToken() {
return api.post('/auth/refresh');
}
};
์ ๊ตฌ์กฐ์์ index.js์ api ์ธ์คํด์ค๋ฅผ ๊ฐ ๋ชจ๋์ด import ํ์ฌ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด userAPI.getProfile()์ ํธ์ถํ๋ฉด ๋ด๋ถ์ ์ผ๋ก api.get('/user/profile')๋ก ์์ฒญ์ด ์ด๋ฃจ์ด์ง๋ฉฐ,
์ฌ๊ธฐ์๋ ์ด๋ฏธ ์ค์ ๋ baseURL์ด ์์ ๋ถ๊ณ , ์ธํฐ์ ํฐ ๋ก์ง๋ ์๋ ์ ์ฉ๋ฉ๋๋ค.
๋ฐ๋ผ์ ์ปดํฌ๋ํธ์์๋ userAPI.getProfile() ํจ์๋ง ๋ถ๋ฅด๋ฉด ๊ณง๋ฐ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ป๊ฑฐ๋ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง๊ฒ ๋ฉ๋๋ค.
๋ํ ๊ฐ ํจ์๋ axios ํธ์ถ ๊ฒฐ๊ณผ(Promise)๋ฅผ ๋ฐํํ๋ฏ๋ก,
ํธ์ถ ์ธก์์. then/. catch๋ฅผ.then/.catch ์ฌ์ฉํ๊ฑฐ๋ await์ผ๋ก ๋ฐ์ ์ ์์ต๋๋ค.
ํ์์ ๋ฐ๋ผ ์๋ต ๋ฐ์ดํฐ๋ง ๋ฐํํ๋๋ก ์ฒ๋ฆฌํ ์๋ ์์ต๋๋ค.
(์: return (await api.get('/user/profile')). data์ฒ๋ผ ๊ตฌํํ๋ฉด ์ด ํจ์๋ฅผ ํธ์ถํ๋ ์ชฝ์์๋. data๋ฅผ.data ๋ฐ๋ก ๋ฐ๊ฒ ๋ฉ๋๋ค.)
๋ณํ: ํ๋ก์ ํธ ์ฑ๊ฒฉ์ ๋ฐ๋ผ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ ์ธ๋ถํํ ์๋ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ ์์์์๋ ๋ชจ๋ API ๋ชจ๋์ด ๊ฐ์ axios ์ธ์คํด์ค๋ฅผ ์ผ์ง๋ง,
๋ง์ฝ ์ฌ๋ฌ ๊ฐ์ ๋ฐฑ์๋ ์๋น์ค๋ฅผ ํธ์ถํด์ผ ํ๋ค๋ฉด api/ ์๋์ serviceA/, serviceB/ ํด๋๋ฅผ ๋ง๋ค๊ณ
๊ฐ ํด๋๋ณ๋ก ๋ค๋ฅธ axios ์ธ์คํด์ค๋ฅผ ๊ฐ์ง ์๋ ์์ต๋๋ค. ๋๋ ํ์ผ์ด ๋ง์์ง๋ฉด modules/ ํด๋ ์๋์ ๋ชจ๋๋ค์ ๋ฃ๊ณ , config/axios.js, utils/errorHandler.js ๋ฑ์ ํ์ผ๋ก ๋๋์ด ๊ด๋ฆฌํ ์๋ ์์ต๋๋ค.
์ค์ ํ์ ์์๋ ํ๋ก์ ํธ ๊ท๋ชจ์ ํ ์ ํธ์ ๋ฐ๋ผ ๊ตฌ์กฐ๋ฅผ ์ฝ๊ฐ์ฉ ๋ค๋ฅด๊ฒ ๊ฐ์ ธ๊ฐ์ง๋ง,
ํต์ฌ์ “axios ์ธ์คํด์ค ์ค์ ํ์ผ + ๊ฐ ๋๋ฉ์ธ๋ณ API ํ์ผ”์ด๋ผ๋ ์ ์ ๋๋ค.
์ด ํจํด์ Vue ๋ฟ๋ง ์๋๋ผ Nuxt ๋ฑ ๋ค๋ฅธ Vue ๊ธฐ๋ฐ ํ๋ ์์ํฌ์์๋ ๊ถ์ฅ๋๋ ๊ตฌ์กฐ์ ๋๋ค.
๐ก Tip: API ๋ชจ๋์ ํจ์ ๋ช ์ ๋ฐฑ์๋ ์๋ํฌ์ธํธ์ ์ผ์น์ํค๊ฑฐ๋,
ํด๋ผ์ด์ธํธ์์์ ์ญํ ์ ๋๋ฌ๋ด๋๋ก ์ง๋ ๊ฒ์ด ์ข์ต๋๋ค.
์๋ฅผ ๋ค์ด getUserProfile vs getProfile ๋ค์ด๋ฐ์ ํ ์ปจ๋ฒค์ ์ ๋ฐ๋ผ ์ ํํ๋ฉด ๋๊ณ , ์ผ๊ด์ฑ์ ์ ์งํ์ธ์.
๋ํ API ํจ์์ JSDoc ์ฝ๋ฉํธ๋ฅผ ๋ถ์ฌ์ ์ด๋ค ์์ฒญ์ ๋ณด๋ด๊ณ ๋ฌด์์ ๋ฐํํ๋์ง ๋ช ์ํด ๋๋ฉด,
๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด ์ฌ์ฉํ๊ฑฐ๋ ์์ ํ ๋ ๋์์ด ๋ฉ๋๋ค.
5. ์ปดํฌ๋ํธ์์ API๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ (ํ๋ฆ ์์ธ)
API ๋๋ ํฐ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ ํ, ์ค์ Vue ์ปดํฌ๋ํธ ๋ด์์ API๋ฅผ ํธ์ถํ๋ ํ๋ฆ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
Composition API ์คํ์ผ์์์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
<!-- ์์: UserProfileView.vue -->
<template>
<div>
<h1>{{ profile.name }} ๋์ ํ๋กํ</h1>
<button @click="refresh">ํ๋กํ ์๋ก๊ณ ์นจ</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { userAPI } from '@/api/user'; // API ๋ชจ๋ ์ํฌํธ
const profile = ref(null);
// ํ๋กํ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ถ๋ฌ์ค๋ ํจ์
const fetchUserProfile = async () => {
try {
const response = await userAPI.getProfile();
// ์: ์ธํฐ์
ํฐ์์ ์๋ต ๊ฐ๊ณต์ ์ ํ๋ค๋ฉด response.data์ ์ค์ ๋ฐ์ดํฐ ์์
profile.value = response.data ?? response;
} catch (error) {
console.error('ํ๋กํ ๋ถ๋ฌ์ค๊ธฐ ์คํจ:', error);
// ์๋ฌ๋ ์ ์ญ ์ธํฐ์
ํฐ์์ ์ฒ๋ฆฌ๋์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ธฐ์๋ ๋ก๊ทธ๋ง ๋จ๊น
// ํ์ํ ์ถ๊ฐ ์ฒ๋ฆฌ๊ฐ ์์ผ๋ฉด ์ํ (์: ํน์ ์๋ฌ๋ฉ์์ง๋ฅผ ํ๋ฉด์ ํ์ ๋ฑ)
}
};
// ์ปดํฌ๋ํธ ๋ง์ดํธ ์ ์๋์ผ๋ก ํธ์ถํ์ฌ ํ๋กํ ๋ก๋
onMounted(fetchUserProfile);
// ์๋ก๊ณ ์นจ ๋ฒํผ ํด๋ฆญ ์ API ๋ค์ ํธ์ถ
const refresh = () => {
fetchUserProfile();
};
</script>
์ ์ฝ๋์์ ์ ์ ์๋ฏ์ด, ์ปดํฌ๋ํธ๋ API ๋ชจ๋๋ก๋ถํฐ ๊ฐ์ ธ์จ ํจ์ (userAPI.getProfile)๋ฅผ ํธ์ถํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. userAPI.getProfile() ์์ฒด๊ฐ Promise๋ฅผ ๋ฐํํ๋ฏ๋ก await์ผ๋ก ๋ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๊ณ ,
์๋ฌ๊ฐ throw ๋๋ฉดcatch๋ก ์ก์๋ด๋ ํ์ค ํจํด์ ๋๋ค.
์ฌ๊ธฐ์ ํต์ฌ์ ์ปดํฌ๋ํธ๊ฐ Axios๋ ๊ตฌ์ฒด์ ์ธ URL ๊ฒฝ๋ก๋ฅผ ์ง์ ๋ค๋ฃจ์ง ์๋๋ค๋ ์ ์ ๋๋ค.
์ปดํฌ๋ํธ ์ ์ฅ์์๋ ๊ทธ์ userAPI.getProfile์ด๋ผ๋ ํจ์๋ฅผ ํธ์ถํ์ ๋ฟ์ด๊ณ ,
๋ด๋ถ์์ Axios ํธ์ถ, ํค๋ ์ฒจ๋ถ, ์๋ฌ ๊ณตํต ์ฒ๋ฆฌ ๋ฑ์ด ์ผ์ด๋ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋ฉ๋๋ค.
์ด๋ฌํ ํ๋ฆ์ ์ฅ์ ์ ์ปดํฌ๋ํธ ์ฝ๋๊ฐ ๊น๋ํด์ง๊ณ UI ๋ก์ง๊ณผ ๋ฐ์ดํฐ ํต์ ๋ก์ง์ด ๋ถ๋ฆฌ๋๋ค๋ ๊ฒ์ ๋๋ค.
์ปดํฌ๋ํธ์์๋ ๊ฒฐ๊ณผ๋ฅผ ํ๋ฉด์ ์ด๋ป๊ฒ ๋ณด์ฌ์ค์ง, ์ฌ์ฉ์ ์ด๋ฒคํธ์ ์ด๋ป๊ฒ ๋์ํ ์ง๋ง ์ ๊ฒฝ ์ฐ๊ณ ,๋ฐ์ดํฐ๋ API ๋ชจ๋์ ์์ํฉ๋๋ค. ์์ปจ๋ ์ ์ปดํฌ๋ํธ์์๋ onMounted ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ,
๋ฒํผ ํด๋ฆญ ์ ๋์ผํ ํจ์๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ์๋ก๊ณ ์นจ์ ๊ตฌํํ์ง๋ง, API ๊ฒฝ๋ก๋ ํ ํฐ ์ฒ๋ฆฌ ๋ฑ์ด ์ ํ ๋๋ฌ๋์ง ์์ต๋๋ค.
๋ํ ์ด ๊ตฌ์กฐ๋ ํ ์คํธ์ ์ ์ง๋ณด์์๋ ์ ๋ฆฌํฉ๋๋ค.
API ํธ์ถ ํจ์๋ฅผ ๋ณ๋๋ก ๋ถ๋ฆฌํ์ผ๋ฏ๋ก,
๋จ์ ํ ์คํธ ์์๋ ํด๋น ํจ์๋ฅผ ๋ชจ์(mock)ํ์ฌ ์ปดํฌ๋ํธ ๋ก์ง๋ง ๊ฒ์ฆํ ์ ์์ต๋๋ค.
๋ฐ๋๋ก API ๋ชจ๋์ ํ ์คํธํ ๋๋ Axios๋ฅผ ๋ชจํน ํ์ฌ ํจ์๊ฐ ์ฌ๋ฐ๋ฅธ ์์ฒญ์ ๋ง๋๋์ง ํ์ธํ ์๋ ์์ต๋๋ค.
์ค์ ๊ฐ๋ฐ ์์๋ ์ปดํฌ๋ํธ์ API ๋ชจ๋ ์ฌ์ด์ ํ ๋จ๊ณ ๋ ์๋น์ค ๋ ์ด์ด(services/)๋ฅผ ๋ฃ์ด ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ ํ
API๋ฅผ ํธ์ถํ๋ ๊ตฌ์กฐ๋ฅผ ์ทจํ๊ธฐ๋ ํ์ง๋ง, ๊ท๋ชจ๊ฐ ํฌ์ง ์๋ค๋ฉด API ๋ชจ๋๋ง์ผ๋ก๋ ์ถฉ๋ถํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก, ์๋ฌ ์ฒ๋ฆฌ ํ๋ฆ์ ๋ํด ํ ๋ฒ ๋ ์ ๋ฆฌํ๋ฉด:
์ปดํฌ๋ํธ์์ API๋ฅผ ํธ์ถ ->
์ธํฐ์ ํฐ๊ฐ ์ค์ ๋ ๊ฒฝ์ฐ ์๋ต ์๋ฌ๋ฅผ ๋จผ์ ์ฒ๋ฆฌ ->
๊ทธ๋๋ Promise.rejectPromise.reject ๋ ์๋ฌ๊ฐ ์ปดํฌ๋ํธ์ catch๋ก ์ ๋ฌ ->
์ปดํฌ๋ํธ์์ ํ์์ ๋ฐ๋ผ ์ถ๊ฐ ์ฒ๋ฆฌ. ์ด๋ฐ ์์์ ๋๋ค.
๋ฐ๋ผ์ ์ ์ญ ์ธํฐ์ ํฐ์์ ๋๋ถ๋ถ์ ๊ณตํต ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ณ ,
์ปดํฌ๋ํธ์์๋ ํน๋ณํ ๊ฒฝ์ฐ์๋ง ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ ์์ผ๋ก ์ญํ ์ ๋ถ๋ดํ ์ ์์ต๋๋ค.
๐ ์ฐธ๊ณ : Composition API์ <script setup> ๊ตฌ๋ฌธ์์๋ async setup()์ ์ธ ์ ์๊ธฐ ๋๋ฌธ์,
์ ์์์ฒ๋ผ onMounted ์์์ async ํจ์๋ฅผ ํธ์ถํ๊ฑฐ๋, ๋๋ ์ฆ์์คํ ํจ์ ํํ๋ก ํธ์ถํฉ๋๋ค.
์๋ฅผ ๋ค์ด <script setup> (async () => { await fetchUserProfile(); })(); </script>์ฒ๋ผ ์์ฑํ ์๋ ์์ต๋๋ค.
ํ์ง๋ง ๊ฐ๋ ์ฑ์ ์ํด ์์ฒ๋ผ onMounted๋ฅผ ์ฌ์ฉํ๋ ํจํด์ด ์ผ๋ฐ์ ์ ๋๋ค.
6. ํ๊ฒฝ ๋ณ์(.env) ํ์ฉ ํ ๋ฐ ๋ณด์ ์ฃผ์์ฌํญ
์ค์ ํ๋ก์ ํธ์์๋ API ์๋ฒ URL์ด๋ ๊ฐ์ข ํค๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ ํ๊ฒฝ ๋ณ์(.env) ํ์ผ์ ๋ถ๋ฆฌํฉ๋๋ค.
Vue 3 + Vite ํ๋ก์ ํธ์ ๊ฒฝ์ฐ, Vite์ ํ๊ฒฝ ๋ณ์ ๊ท์น์ ์์๋๋ ๊ฒ์ด ์ค์ํฉ๋๋ค:
- VITE_ ํ๋ฆฌํฝ์ค: Vite์์๋ .env ํ์ผ์ ์ ์๋ ๋ณ์ ์ค ์ด๋ฆ์ด VITE_๋ก ์์ํ๋ ๋ณ์๋ง ํ๋ก ํธ์๋ ์ฑ์ ๋ ธ์ถ๋ฉ๋๋ค. ์ด๋ ์ค์๋ก ์ค์ํ ๊ฐ์ ํด๋ผ์ด์ธํธ์ ๋ ธ์ถํ์ง ์๋๋ก ํ๋ ์ฅ์น์ ๋๋ค. ์์ปจ๋ .env์ VITE_API_BASE_URL="https://api.example.com"์ด๋ผ๊ณ ๋ฃ์ผ๋ฉด, ์ฝ๋์์ import.meta.env.VITE_API_BASE_URL๋ก ์ ๊ทผ ๊ฐ๋ฅํ์ง๋ง, DB_PASSWORD="..." ๊ฐ์ด VITE_ ์์ด ์ ์ํ๋ฉด import.meta.env.DB_PASSWORD๋ undefined๋ก ๋์ ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค. ๋ฐ๋ผ์ ํ๋ก ํธ์์ ํ์ํ ํ๊ฒฝ๋ณ์๋ ํญ์ VITE_ ์ ๋์ด๋ฅผ ๋ถ์ฌ์ผ ํ๋ฉฐ, ๋น๋ฐ ๊ฐ์ ์ค์๋ก ํฌํจํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
- .env ํ์ผ ๋ถ๋ฆฌ: Vite๋ ํ๋ก์ ํธ ๋ฃจํธ์ .env.development, .env.production ๋ฑ์ ํ์ผ์ ๋๋ฉด ๋ชจ๋์ ๋ฐ๋ผ ์๋์ผ๋ก ๋ก๋ํด ์ค๋๋ค. ์๋ฅผ ๋ค์ด ๊ฐ๋ฐ ํ๊ฒฝ์์๋ .env.development์ ๊ฐ์, ํ๋ก๋์ ๋น๋์์๋ .env.production์ ๊ฐ์ ์ฐ์ ์ฌ์ฉํ๊ฒ ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํ์ฉํด์ ๊ฐ๋ฐ์ฉ API URL๊ณผ ์ด์ API URL์ ๊ตฌ๋ถํ๋ฉด, ์ฝ๋ ์์ ์์ด๋ ๋น๋ ๋ชจ๋์ ๋ฐ๋ผ ๋ค๋ฅธ ์๋ฒ์ ํต์ ํ ์ ์์ต๋๋ค. (๋ณดํต ๊น์๋ .env.production์ ์ปค๋ฐํ์ง ์๊ณ CI/CD ํ๊ฒฝ์์ ์ฃผ์ ํ๊ฑฐ๋, ์์ ํ๊ฒ ๊ด๋ฆฌํฉ๋๋ค.)
- ์ฌ์ฉ ์ (baseURL ์ค์ ): ์์ Axios ์ธ์คํด์ค ์์ฑ ์ baseURL: import.meta.env.VITE_API_BASE_URL๋ก ์ค์ ํ ๋ถ๋ถ์ ๋ ์ฌ๋ ค๋ด ์๋ค. ํ๋ก์ ํธ ๋ฃจํธ์ ์๋์ ๊ฐ์ .env ํ์ผ๋ค์ ์ค๋นํด ๋ ์ ์์ต๋๋ค:์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ฐ ๋ชจ๋์์๋ ๋ก์ปฌํธ์คํธ๋ก API ์์ฒญ์ด ๋๊ฐ๊ณ , ๋ฐฐํฌ ํ์๋ ์ด์ ๋๋ฉ์ธ์ผ๋ก ๋๊ฐ๊ฒ ๋ฉ๋๋ค.
- ์ฃผ์: ๊ฒฝ๋ก ๋์ /๋ฅผ ๋ฃ์์ง ๋ง์ง๋ ๋ฐฑ์๋ ์ค์ ์ ๋ง๊ฒ ์ผ๊ด๋๊ฒ ๊ด๋ฆฌํ์ธ์.
# .env.development
VITE_API_BASE_URL=http://localhost:3000/api
- ๋ณด์ ๊ณ ๋ ค์ฌํญ: ์์ ์ธ๊ธํ๋ฏ, VITE_๋ก ์์ํ๋ ๋ณ์๋ ํ๋ก ํธ์๋ ๋ฒ๋ค์ ๊ณ ์ค๋ํ ํฌํจ๋ฉ๋๋ค. ์ฆ, ์ฌ์ฉ์(ํด๋ผ์ด์ธํธ)๊ฐ ์น ์ฑ์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๊ทธ ๊ฐ์ ์ ์ ์๋ค๋ ๋ป์ ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ ๋๋ก ๋น๋ฐ ๊ฐ(API Secret, DB Password ๋ฑ)์ VITE_ ํ๊ฒฝ๋ณ์๋ก ๋ฃ์ผ๋ฉด ์ ๋ฉ๋๋ค. ์ด๋ฌํ ๋น๋ฐ ๊ฐ์ ๋ฐฑ์๋์์ ๋ณดํธํด์ผ ํ๋ฉฐ, ํ๋ก ํธ์๋ ๋ ธ์ถ๋์ง ์๋๋ก ํด์ผ ํฉ๋๋ค. ๊ฐ๋ น API Key ์ค์ ๊ณต๊ฐ๋์ด๋ ๋๋ ๋ถ๋ถ (์: Stripe์ Publishable Key)์ ๋ฃ์ด๋ ๋์ง๋ง, Secret Key๋ ๋ฃ์ผ๋ฉด ์ ๋ฉ๋๋ค. ๋ํ .env ํ์ผ๋ค์ git์ ์ปค๋ฐ๋์ง ์๋๋ก .gitignore์ ์ถ๊ฐํ๋ ๊ฒ์ด ๊ธฐ๋ณธ์ ๋๋ค. ๋์ .env.example์ด๋ README๋ฅผ ํตํด ์ด๋ค ํ๊ฒฝ๋ณ์๊ฐ ํ์ํ์ง๋ง ๊ณต์ ํ๊ณ , ์ค์ ๊ฐ์ ๊ฐ ๊ฐ๋ฐ์/์๋ฒ ํ๊ฒฝ์์ ์ฃผ์ ํ๋ ๋ฐฉ์์ ๋๋ค.
- ๊ธฐํ ํ:
- Vite์ import.meta.env์๋ ๊ธฐ๋ณธ ๋ด์ฅ ๋ณ์๋ค๋ ์์ต๋๋ค. import.meta.env.MODE (ํ์ฌ ๋ชจ๋), import.meta.env.BASE_URL (๊ธฐ๋ณธ ๊ฒฝ๋ก) ๋ฑ๋ ํ์ฉ ๊ฐ๋ฅํ์ง๋ง, ๋๋ถ๋ถ ํฐ ํ๋ก์ ํธ์์๋ ํ์ํ ๊ฐ๋ง ์๋ก ์ ์ํด์ ์๋๋ค.
- ํ๊ฒฝ๋ณ์ ๊ฐ์ ๋ฌธ์์ด๋ก ์ทจ๊ธ๋๋ฏ๋ก, ์ซ์๋ ๋ถ๋ฆฌ์ธ์ ๋ฃ์ผ๋ฉด ์ฝ๋์์ ํ์ฑํด์ผ ํฉ๋๋ค. ์: VITE_USE_MOCK="false"๋ผ๊ณ ๋ฃ์ด๋ import.meta.env.VITE_USE_MOCK๋ ๋ฌธ์์ด "false"๋ก ์ค๋ฏ๋ก, ์ด๋ฅผ JS์์ ๋ถ๋ฆฌ์ธ์ผ๋ก ๋ณํํด์ ์จ์ผ ํฉ๋๋ค (ํน์ "0"/"1"์ ์ฐ๊ฑฐ๋).
- Vue ํ๋ก์ ํธ์์ ํ๊ฒฝ๋ณ์ ์ค์ ์ด ์ ๋๋ก ์ ๋จนํ๋ ๊ฒฝ์ฐ, vite.config.js์ envDir๋ envPrefix ์ต์ ์ ํ์ธํด๋ณด์ธ์. ์ผ๋ฐ์ ์ผ๋ก ๊ธฐ๋ณธ ์ค์ ์ด๋ฉด ์ ๊ท์น์ด ์ ์ ์ฉ๋์ง๋ง, ๊ฐํน monorepo ๋ฑ์ ๊ตฌ์กฐ์์๋ ๊ฒฝ๋ก๋ฅผ ๋ชป ์ฐพ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์์ฝํ๋ฉด, ํ๊ฒฝ๋ณ์๋ ๋ฏผ๊ฐ์ ๋ณด์ ๋ถ๋ฆฌํ์ฌ ์ฌ์ฉํ๋,
VITE_ ์ ๋์ฌ ๊ท์น๊ณผ ๋ชจ๋๋ณ ํ์ผ์ ํ์ฉํ์ฌ ์ ์ฐํ๊ณ ์์ ํ๊ฒ ๊ด๋ฆฌํ๋ ๊ฒ์ด ํฌ์ธํธ์ ๋๋ค.
ํ๋ก ํธ์๋ ํ๊ฒฝ๋ณ์๋ ์์ ํ ๋ณด์ ์ฅ์น๋ ์๋๋ฏ๋ก ๋ ธ์ถ๋์ด๋ ๋๋ ๊ฐ๋ค๋ง ์ฌ์ฉํ๊ณ ,
๋ฐฑ์๋์ ์ฐ๊ณํด ์ง์ง ๋น๋ฐ์ ์งํค๋ ์ ๋ต์ด ํ์ํฉ๋๋ค.
7. ํ์ฅ ํฌ์ธํธ: ์๋ฌ ํธ๋ค๋ง ํ & Axios ๋ํผ ์ปดํฌ์ ๋ธ
Axios ์ธ์คํด์ค์ ์ธํฐ์ ํฐ ๊ธฐ๋ฐ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ถํ ํ์๋, ํ๋ก์ ํธ์ ๋ฐ๋ผ ์ถ๊ฐ๋ก ๊ณ ๋ คํ ๋งํ ํ์ฅ ํฌ์ธํธ๋ค์ด ์์ต๋๋ค.
์ฌ๊ธฐ์๋ ์๋ฌ ํธ๋ค๋ง ๊ณ ๋ํ์ Axios ํ์ฉ ์ปดํฌ์ ๋ธ ๋ ๊ฐ์ง๋ฅผ ์๊ฐํฉ๋๋ค.
๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ง ํ (Hook) ๋๋ ์ ํธ๋ฆฌํฐ: ์ธํฐ์ ํฐ์์ ์ฒ๋ฆฌํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ์๋ฌ ์ฒ๋ฆฌ๋ ๋ณ๋ ํจ์๋ ํ ์ผ๋ก ๋ถ๋ฆฌํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฐฑ์๋์์ ๋ด๋ ค์ค๋ ์๋ฌ response์ errorCode๋ message ํ๋๊ฐ ์๋ค๋ฉด ์ด๋ฅผ ํด์ํด ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง๋ก ๋ณํํ๋ ์์ ์ ์๊ฐํด๋ณผ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ ์ญ ์ธํฐ์ ํฐ์ ์๋ฌ ์ฒ๋ฆฌ ๋ถ๋ถ์ ์ง์ ๋ฃ์ผ๋ฉด ์ฝ๋๊ฐ ๊ธธ์ด์ง๋ฏ๋ก, handleAPIError(error) ๊ฐ์ ์ ํธ ํจ์๋ฅผ ๋ง๋ค์ด ํ์ฉํฉ๋๋ค. ์์:
// Axios ์๋ต ์ธํฐ์
ํฐ์์ ํ์ฉ
api.interceptors.response.use(
response => response,
error => {
// ๊ณตํต ์๋ฌ ํธ๋ค๋ฌ ์ฌ์ฉ
handleAPIError(error);
return Promise.reject(error);
}
);
์์ฒ๋ผ ๊ตฌํํ๋ฉด ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ ํ ๊ณณ์ ๋ชจ์์ ๊ด๋ฆฌํ ์ ์๊ณ , ํ์์ team ๊ณต์ ๋ ์ฌ์์ง๋๋ค.
๋ ํ๋์ ๋ฐฉ๋ฒ์ผ๋ก, Vue ์ปดํฌ์ง์ API์ provide/inject๋ฅผ ์ด์ฉํด ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ๋ฅผ ์ฃผ์ ํด๋๊ณ ,
์ปดํฌ๋ํธ ์ด๋์๋ ํธ์ถํ ์ ์๊ฒ ํ ์๋ ์์ต๋๋ค.
ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก๋ ์์ ๊ฐ์ ํจ์ ํํ๋, Vuex/Pinia์ ์๋ฌ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์์ ๋ ๋ง์ด ์ฌ์ฉํฉ๋๋ค.
- Axios Wrapper ์ปดํฌ์ ๋ธ: Composition API์ ๊ฐ๋ ฅํจ์ ํ์ฉํด API ํธ์ถ์ ์ ๋ดํ๋ ์ปค์คํ ์ปดํฌ์ ๋ธ(composable)์ ๋ง๋ค ์๋ ์์ต๋๋ค. ์์ปจ๋ useApiRequest()๋ผ๋ ์ปดํฌ์ ๋ธ์ ๋ง๋ค์ด ๋ด๋ถ์์ axios ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๊ณ , ๋ก๋ฉ ์ํ, ๊ฒฐ๊ณผ, ์๋ฌ ์ํ๋ฅผ ๋ชจ๋ reactiveํ๊ฒ ๊ด๋ฆฌํ๋ฉด ์ปดํฌ๋ํธ์์๋ ๋์ฑ ์ ์ธ์ ์ผ๋ก API ํธ์ถ์ ๋ค๋ฃฐ ์ ์์ต๋๋ค. ๊ฐ๋จํ ์๋ก, ํน์ ์์ฒญ์ ๋ณด๋ด๋ ์ปดํฌ์ ๋ธ:์ด ์ปดํฌ์ ๋ธ์ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ์์๋ const { loading, data, error, request } = useApiRequest(); ํ ํ request('get', '/user/profile')์ฒ๋ผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , loading์ด๋ error ์ํ๋ฅผ ํ์ฉํด UI์ ๋ก๋ฉ์ค ํ์๋ ์๋ฌ ๋ฉ์์ง๋ฅผ ํํํ ์ ์์ต๋๋ค. ์ด๋ฌํ ํจํด์ VueUse ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ useAxios๋ useFetch ์ปดํฌ์ ๋ธ๊ณผ ์ ์ฌํ๋ฐ, VueUse์์๋ ์ ๊ณตํ๋ useAxios๋ Axios๋ฅผ ๊ฐ์ธ ๋ฐ์ํ ์ํ(๋ก๋ฉ ์ฌ๋ถ, ์๋ฃ ์ฌ๋ถ ๋ฑ)๋ฅผ ์ ๊ณตํ๋ฏ๋ก ํ์ฉํ ๋งํฉ๋๋ค.
๋ํ ์ ์์์ฒ๋ผ vue-toastification ๋ฑ์ UI ํผ๋๋ฐฑ๋ ์ปดํฌ์ ๋ธ ์์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์,
์ธํฐ์ ํฐ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ ๋งคํ UI ๋ฐ์์ ์ปดํฌ์ ๋ธ์์ ํด์ค ์ ์์ต๋๋ค.
์ค์ ์ฌ๋ก๋ก, Stack Overflow ๋ฑ์ง์์๋ ์ ์ญ ์ธํฐ์ ํฐ ๋์ ์ด๋ฌํ ์ปดํฌ์ ๋ธ ๋ฐฉ์์ API ํธ์ถ์ ๊ถ์ฅํ๋ ์๊ฒฌ๋ ์์ต๋๋ค.
ํนํ ์ปดํฌ๋ํธ๋ณ๋ก ์กฐ๊ธ์ฉ ๋ค๋ฅธ ์๋ฌ ์ฒ๋ฆฌ๋ ํ์ ๋์์ด ํ์ํ๋ค๋ฉด,
์ธํฐ์ ํฐ๋ณด๋ค ์ปดํฌ์ ๋ธ/ํ ์ผ๋ก ์ ์ฐํ๊ฒ ๋์ฒํ๋ ํธ์ด ๋ ๊น๋ํ ์ ์์ต๋๋ค.
// src/api/utils/errorHandler.js
export function handleAPIError(error) {
const status = error.response?.status;
const errorCode = error.response?.data?.errorCode;
switch (errorCode) {
case 'TOKEN_EXPIRED':
// ํ ํฐ ๋ง๋ฃ ์ ์ฌ๋ฐ๊ธ ์๋
return refreshTokenAndRetry(error);
case 'VALIDATION_ERROR':
// ์ ํจ์ฑ ๊ฒ์ฆ ์ค๋ฅ ์ฒ๋ฆฌ
alert('์
๋ ฅ ๊ฐ์ ๋ค์ ํ์ธํด์ฃผ์ธ์.');
break;
default:
// ๊ธฐํ ์ค๋ฅ ๊ณตํต ์ฒ๋ฆฌ
alert('์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
return Promise.reject(error);
}
// src/composables/useApiRequest.js
import { ref } from 'vue';
import api from '@/api';
// Axios ์ธ์คํด์ค
import { useToast } from 'vue-toastification';
// ์: ํ ์คํธ ์๋ฆผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
export function useApiRequest() {
const loading = ref(false);
const data = ref(null);
const error = ref(null);
const toast = useToast();
const request = async (method, url, payload = null) => {
loading.value = true;
error.value = null;
try {
const response = await api[method](url, payload); data.value = response.data;
} catch (err) {
error.value = err;
// ์: ์๋ฌ ์ข
๋ฅ์ ๋ฐ๋ผ ์ ์ญ ํ ์คํธ ์ถ๋ ฅ
if (err.response?.status === 403) {
toast.error("์ธ์
์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.");
}
} finally {
loading.value = false;
}
};
return { loading, data, error, request };
}
๐ ๊ด๋ จ ์ฐธ๊ณ : VueUse์ useAxios ์ปดํฌ์ ๋ธ์ Axios ์ธ์คํด์ค๋ฅผ ์ธ์๋ก ๋ฐ์ ์ฌ์ฉํ ์๋ ์๊ณ ,
React Query๋ SWR์ฒ๋ผ ์์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํ๊ฑฐ๋ ์ฌ์ฌ์ฉํ๋ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ ์ ๊ณตํ๋,
๋๊ท๋ชจ ํ๋ก์ ํธ๋ผ๋ฉด ๋์ ์ ๊ฒํ ํด๋ณผ ๋ง ํฉ๋๋ค. ๋จ, ๋ฌ๋์ปค๋ธ์ ๋์ ๋น์ฉ์ด ์์ผ๋ฏ๋ก ํ์๋ค๊ณผ ์์ํ์ฌ ๊ฒฐ์ ํ์ธ์.
์ ๋ฆฌํ๋ฉด, ํ์ฅ ํฌ์ธํธ๋ ๊ธฐ๋ณธ ๊ตฌ์กฐ ์ธ์ ํ๋ก์ ํธ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ถ๊ฐ๋ก ์ค๊ณ๋๋ ๋ถ๋ถ์ ๋๋ค.
์ ์ญ ์๋ฌ ์ฒ๋ฆฌ ํ /ํจ์, API ํธ์ถ ์ปดํฌ์ ๋ธ ์ธ์๋,
์์ฒญ ์ทจ์(Cancellation) ๊ตฌํ์ด๋, ์ฌ์๋ ์ ๋ต(exponential backoff), ์๋ต ์บ์ฑ ๋ฑ๋ ํ์ฅ ํฌ์ธํธ๊ฐ ๋ ์ ์์ต๋๋ค.
์ด๋ฌํ ๊ธฐ๋ฅ๋ค์ Axios ์์ฒด ๊ธฐ๋ฅ์ด๋ ์ถ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก๋ ๊ตฌํ ๊ฐ๋ฅํ๋, ํ๋ก์ ํธ ๊ท๋ชจ์ ํ์์ ๋ง์ถฐ ์ ์ฉํ๋ฉด ๋ฉ๋๋ค.
8. ์ค๋ฌด ์ ์ฉ ์ฌ๋ก: ํ ์์ ๋ถ๋ฆฌ ๊ธฐ์ค ๋ฐ ํ
๋ง์ง๋ง์ผ๋ก, ์ค์ ์๋น์ค ๊ฐ๋ฐ ์ ๋ณธ ๊ตฌ์กฐ๋ฅผ ์ ์ฉํ๋ฉด์ ์ป์ ํ ํ์ ๊ด์ ์ ํ ๋ช ๊ฐ์ง๋ฅผ ์๊ฐํฉ๋๋ค.
- ๋ชจ๋๋ณ ๋ด๋น์ ์ง์ : API ๋๋ ํฐ๋ฆฌ๋ฅผ ๋๋ฉ์ธ๋ณ(์๋น์ค๋ณ)๋ก ๋ถ๋ฆฌํ๋ฉด, ์์ฐ์ค๋ฝ๊ฒ ํด๋น ๋ชจ๋์ ๋ด๋น์๋ฅผ ์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด user.js๋ A๋, product.js๋ B๋ ์์ผ๋ก ๋งก์์ ๊ฐ๋ฐ/์ ์ง๋ณด์ํ๋ฉด ํจ์จ์ ์ ๋๋ค. ๋ด๋น์๋ ์์ ์ ๋ชจ๋ ๋ด ๋ณ๊ฒฝ์ด ๋ค๋ฅธ ๊ณณ์ ์ํฅ ์ฃผ์ง ์๋๋ก ์ฑ ์์ง๊ณ ๊ด๋ฆฌํ๋ฉฐ, ๋ค๋ฅธ ๊ฐ๋ฐ์๋ ๊ทธ ๋ชจ๋์ ํจ์๋ฅผ ๊ฐ์ ธ๋ค ์ฌ์ฉํฉ๋๋ค. ์ด๋ ๊ฒ ์ญํ ์ ๋ถ๋ฆฌํ๋ฉด Git ์ถฉ๋๋ ์ค์ด๋ค๊ณ , ์ฝ๋ ์์ ๊ถ์ด ๋ช ํํด์ ธ ๋ฆฌ๋ทฐ๋ ์์ ๋ ์์ํฉ๋๋ค.
์ฝ๋ฉ ์ปจ๋ฒค์ ๋ฐ ๋ฌธ์ํ: ํ์๋ง๋ค API ํจ์๋ฅผ ๋ง๋ค ๋ ์ฝ๋ ์คํ์ผ์ด ๋ค๋ฅด๋ฉด ํผ๋์ด ์๊ธธ ์ ์์ต๋๋ค. ็ตฑไธ๋ ์ปจ๋ฒค์ ์ ์ ํด๋์ธ์.
- ํจ์๋ช ์ ๋์ฌ+๋ช ์ฌ (getUsers, createOrder) ํํ๋ก ์ง๊ธฐ
- ํจ์๋ ๊ฐ๋ฅํ๋ฉด ํญ์ response.data (๋๋ ๊ฐ๊ณต๋ ๋ฐ์ดํฐ)๋ง ๋ฐํํ์ฌ ์ผ๊ด๋ ๋ฐํ ํ์ ์ ์ง
- Axios ํธ์ถ ์ async/await ์ฌ์ฉ์ ๊ถ์ฅํ๊ฑฐ๋, ํ๋ก๋ฏธ์ค ์ฒด์ธ์ ์ ํธํ๋์ง ํฉ์
- ์๋ฌ ์ฒ๋ฆฌ๋ ์ธํฐ์ ํฐ์ ๋งก๊ธฐ๋, ๊ฐ๋ณ ํจ์์์๋ ํน๋ณํ ํ์๊ฐ ์์ผ๋ฉด try/catch๋ฅผ ์๋ต (๋๋ ๋ชจ๋ ํจ์ ๋ด๋ถ์์ ํ ๋ฒ ๊ฐ์ธ์ ํน์ ์๋ฌ๋ฅผ ๋ณํํด throwํ๋ ์์ผ๋ก ์ฒ๋ฆฌ)
๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ๊ท์น์ด๋ ์ฌ์ฉ ๋ฐฉ๋ฒ์ README๋ ์ํค์ ๋ฌธ์ํํ์ฌ ์ ๊ท ํ์์ด ์ฝ๊ฒ ๋ฐ๋ผ์ฌ ์ ์๊ฒ ํฉ๋๋ค.
ํนํ API ๋ชจ๋์ ๋ฐฑ์๋ ๋ช ์ธ์ ์ง์ ์ ์ผ๋ก ์ฐ๊ฒฐ๋๋ฏ๋ก,
API ๋ช ์ธ ๋ณํ์ ์ด๋ป๊ฒ ๋์ํ๋์ง(์: ๋ชจ๋ ๋ด ํจ์ ์์ + ๊ด๋ จ ์ปดํฌ๋ํธ ์ํฅ ์กฐ์ฌ) ๋ฑ์ ๋ํ ๊ฐ์ด๋๋ ์์ผ๋ฉด ์ข์ต๋๋ค.
- ์ค์ ์ฌ๋ก: ์๋ฅผ ๋ค์ด ํ์๊ฐ ์ฐธ์ฌํ๋ ํ๋ก์ ํธ์์๋, /api/ ํด๋์ 10์ฌ ๊ฐ์ ๋ชจ๋์ด ์์๊ณ (์์ฝ, ๊ฒฐ์ , ํ์, ์ํ ๋ฑ), ์ด๊ธฐ์ ํ์์ด ๊ฐ๊ฐ ๋งก์์ CRUD ํจ์๋ค์ ๊ตฌํํ์ต๋๋ค. ์ดํ ๋ค๋ฅธ ํ์ด์ง ๊ฐ๋ฐ์ ํ ๋, ์ด๋ฏธ ์์ฑ๋ API ํจ์๋ฅผ importํด ์ฐ๊ธฐ๋ง ํ๋ฉด ๋๋ฏ๋ก ๊ฐ๋ฐ ์๋๊ฐ ๋นจ๋ผ์ก์ต๋๋ค. ๋ง์ฝ ํจ์๊ฐ ์์ผ๋ฉด ๋ด๋น์์๊ฒ ์ถ๊ฐ๋ฅผ ์์ฒญํ๊ฑฐ๋, ์ง์ ์ถ๊ฐ ๊ตฌํ ํ ์ฝ๋๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๋ ์์ผ๋ก ์งํํ์ต๋๋ค. ์ด๋ ๋ด๋น์๊ฐ ์๋ ์ฌ๋์ด ๋ชจ๋์ ์์ ํ ๊ฒฝ์ฐ ๋ฐ๋์ ๋ฆฌ๋ทฐ์ด๋ก ๋ด๋น์๋ฅผ ์ง์ ํ์ฌ ๊ฒํ ๋ฐ๋๋ก ํ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ ์ฑ ์ ๊ตฌ๋ถ์ด ํ์คํด์ง๊ณ ์ฝ๋ ํ์ง๋ ์ฌ๋ผ๊ฐ์ต๋๋ค.
- ๋ค์ค ์ธ์คํด์ค ํ์ฉ: ๋ ๋ค๋ฅธ ์ฌ๋ก๋ก, ํ๋์ ํ๋ก์ ํธ์์ ๋ ๊ฐ ์ด์์ ๋ค๋ฅธ API ์๋ฒ๋ฅผ ๋ถ๋ฌ์ผ ํ์ต๋๋ค (์: ๋ฉ์ธ ์๋ฒ์ ํต๊ณ ์๋ฒ). ์ด๋ ๊ฐ๊ฐ Axios ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด api/index.js์์ export const mainAPI = axios.create(...); export const statsAPI = axios.create(...); ์์ผ๋ก ๋ด๋ณด๋๊ณ , ๋ชจ๋๋ณ๋ก ์ ์ ํ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๋๋ก ๋ถ๋ฆฌํ์ต๋๋ค. ์ด๋ฅผ ํตํด ํผ๋์ ์ค์ด๊ณ , ๊ฐ ์ธ์คํด์ค๋ณ๋ก ๋ค๋ฅธ ์ธํฐ์ ํฐ (์: ํต๊ณ API๋ ๋ณ๋ ์๋ฌ ์ฒ๋ฆฌ)๋ ์ ์ฉํ์ต๋๋ค. ํ์์๋ ์ด๋ฌํ ๊ตฌ์กฐ๋ฅผ ๋ฌธ์๋ก ๋จ๊ฒจ ์๋ก์ด API๊ฐ ์ถ๊ฐ๋ ๋ ์ด๋ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ ์ง ํ๋จ ๊ธฐ์ค์ ๊ณต์ ํ์ต๋๋ค.
- ํ์ ์ ์ ์์ : API ๋ชจ๋์ ๋ถ๋ฆฌํด๋, ๊ฒฐ๊ตญ ํ๋์ ์ฝ๋๋ฒ ์ด์ค์ด๋ฏ๋ก ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ์ ํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด auth ๋ชจ๋์์ ํ ํฐ์ ๊ฐฑ์ ํ๋ฉด user ๋ชจ๋์ ํจ์๋ค์ด ์ํฅ์ ๋ฐ์ ์ ์์ต๋๋ค. ์ด๋ด ๋๋ api/index.js ์ธํฐ์ ํฐ์์ ํ ํฐ ๋ณ๊ฒฝ์ ์ฒ๋ฆฌํ๊ณ ๋ชจ๋ ๋ชจ๋์ด ๊ทธ ๋ณ๊ฒฝ์ ๊ณต์ ํ๋๋ก ํ๊ฑฐ๋, Pinia ๋ฑ ์ ์ญ ์ํ๋ก ํ ํฐ์ ๊ด๋ฆฌํ์ฌ ๋๊ธฐํํด์ผ ํฉ๋๋ค. ํ์๋ค ๊ฐ์ ์ด๋ฌํ ์ํธ์์ฉ์ ๋ช ํํ ํฉ์ํด ๋๊ณ ๊ฐ๋ฐํ๋ฉด ๋ฐํ์ ๋ฒ๊ทธ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
์ ๋ฆฌ
Vue 3 + Composition API + Vite ํ๊ฒฝ์์ Axios๋ฅผ ํ์ฉํ API ํต์ ๊ตฌ์กฐ๋ฅผ ์ ๋ฆฌํด ๋ณด์์ต๋๋ค.
Axios ์ธ์คํด์ค์ ์ธํฐ์ ํฐ๋ฅผ ํตํ ์ผ์ํ๋ ์ค์ ,
API ๋๋ ํฐ๋ฆฌ ๋ชจ๋ํ๋ฅผ ํตํ ์ฒด๊ณ์ ์ธ ๊ด๋ฆฌ,
์ปดํฌ๋ํธ ๋ถ๋ฆฌ๋ก ์ธํ ๊ฐ๋ ์ฑ ํฅ์ ๋ฑ์ด ์ด ๊ตฌ์กฐ์ ํฐ ์ฅ์ ์ ๋๋ค.
์ฌ๊ธฐ์ ๋ํด ํ๊ฒฝ๋ณ์๋ฅผ ์ด์ฉํ ์ ์ฐํ ํ๊ฒฝ ๊ตฌ์ฑ๊ณผ, ํ์์ ํ์ฅ ๊ฐ๋ฅํ ์ค๊ณ๊น์ง ๊ณ ๋ คํ๋ฉด,
์ค๋ํ ๊ท๋ชจ์ Vue ํ๋ก ํธ์๋ ํ๋ก์ ํธ์์๋ ํจ์จ์ ์ด๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ข์ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
์ค๋ฌด์์๋ ํ๋ก์ ํธ๋ง๋ค ์ํฉ์ด ๋ค๋ฅด๊ฒ ์ง๋ง,
์ฌ๊ธฐ์ ์๊ฐํ ๊ฐ๋ ๋ค๊ณผ ํจํด (์ธํฐ์ ํฐ ํ์ฉ, ๋ชจ๋ ๊ตฌ์กฐ, ์ปดํฌ์ ๋ธ ๋ฑ)์ ๋ง์ Vue ๊ฐ๋ฐ์๋ค์ด ์ถ์ฒํ๋ ๋ฐฉ์์ด๋ฉฐ
์ค์ ๋ก๋ ๋๋ฆฌ ์ฐ์ด๊ณ ์์ต๋๋ค.
์ฒ์ ์ค๊ณ์ ์กฐ๊ธ ๊ณต์ ๋ค์ด๋ฉด ์ดํ ๊ฐ๋ฐ ์๋์ ์ฝ๋ ํ์ง์ด ํฅ์๋๋,
ํ์๋ค๊ณผ ์ถฉ๋ถํ ๋ ผ์ํ์ฌ ์ต์ ์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ จํด ๋ณด์ธ์.
'๐ป Frontend > Vue.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| Vue.js์ ์ํ ๊ด๋ฆฌ ๋๊ตฌ : Pinia (0) | 2025.06.02 |
|---|---|
| Vue.js 3 ์ปดํฌ์ง์ API ๊ธฐ๋ฐ ํ๋ก ํธ์๋ ํ๋ก์ ํธ ๊ตฌ์กฐ ์ดํด (3) | 2025.06.02 |