fetch
는 브라우저 내장 함수로, 네트워크 요청을 보내는 데 사용됩니다.Fetch
의 몇 가지 단점 때문에 보완 라이브러리를 사용하는 경우가 많습니다.📌그런데 또 웃기게.. 요즘
Nextjs
에서는fetch
사용이 권장됩니다.
then
, catch
, async/await
문법 사용 가능.response.json()
또는 response.text()
로 한 번 더 변환해야 함.fetch
는 네트워크 요청의 성공 여부만을 기준으로 Promise를 resolve 또는 reject합니다.
성공 (resolve): 요청이 네트워크적으로 성공했다면, HTTP 상태 코드가 404나 500과 같은 에러 상태라 하더라도 Promise는 resolve 됩니다.
이 경우, 응답 객체의 response.ok
속성이 false
로 설정됩니다.
실패 (reject): 네트워크 장애(예: 인터넷 연결 문제)나 요청 자체가 실패한 경우에만 Promise가 reject됩니다.
📌 따라서 HTTP 상태 코드로 에러를 판단하려면, 개발자가 수동으로
response.ok
를 확인하고 적절한 처리를 해야 합니다.
설계 철학: 유연성과 낮은 수준의 제어 제공
fetch
는 네트워크 요청 및 응답 처리를 더 유연하게 제어할 수 있도록 설계되었습니다. HTTP 상태 코드를 에러로 자동 처리하지 않음으로써, 개발자가 특정 상태 코드(예: 401 Unauthorized 또는 404 Not Found)에 대해 세부적인 로직을 작성할 수 있게 합니다.출처: MDN
fetch
의 응답 객체(response
)는 메타데이터(상태 코드, 헤더 등)와 응답 본문 데이터를 포함하지만, 본문은 비동기 스트림 형태로 제공됩니다.
response.json()
으로 변환response.text()
로 변환이를 통해 대용량 데이터를 더 효율적으로 다룰 수 있으며, 필요할 때만 본문 데이터를 읽도록 설계되었습니다.
fetch
는 다음과 같은 철학에 기반해 설계되었습니다.
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => {
// HTTP 상태 코드 확인
if (!response.ok) { // false
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // 응답 데이터를 JSON으로 변환
})
.then((data) => console.log(data)) // 변환된 데이터를 사용
.catch((error) => console.error('Error:', error));
axios
는 Fetch
의 단점을 보완한 HTTP
클라이언트 라이브러리입니다.Promise
기반으로 동작하며, 브라우저와 Node.js 환경 모두에서 사용할 수 있습니다.catch
블록으로 이동.baseURL
, headers
등 반복적인 설정을 한 곳에서 관리 가능.axios
는 Fetch
와 달리 한 번의 요청으로 HTTP 상태 확인과 데이터 변환이 모두 가능합니다.
import axios from 'axios';
axios.get('https://jsonplaceholder.typicode.com/posts')
.then((response) => console.log(response.data)) // 응답 데이터를 바로 사용 가능
.catch((error) => console.error('Error:', error));
특징 | Fetch | Axios |
---|---|---|
HTTP 에러 처리 | HTTP 상태 코드를 에러로 간주하지 않음, 수동으로 response.ok 확인 필요 | HTTP 에러(404, 500 등)를 자동으로 catch 로 처리 |
응답 데이터 변환 | .json() 이나 .text() 호출 필요 | 응답 데이터가 자동으로 JSON으로 변환 |
Promise 구조 | 두 단계로 나누어 처리해야 함 (then 또는 await 로 HTTP 확인 → 데이터 변환) | 한 번의 then 또는 await 호출로 상태 확인 및 데이터 사용 가능 |
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => {
if (!response.ok) {
throw new Error('HTTP Error');
}
return response.json(); // 전체 응답 객체
})
.then((data) => console.log(data))
.catch((error) => console.error('Error:', error)); // JSON 데이터
axios.get('https://jsonplaceholder.typicode.com/posts')
.then((response) => console.log(response.data)) // 상태 확인과 데이터 변환을 동시에 처리
.catch((error) => console.error('Error:', error));
Fetch
는 별도의 설치 없이 빠르게 사용할 수 있으므로 간단한 API 호출에 적합.Axios
는 인터셉터, 요청 취소, 기본 설정 관리 기능을 제공하므로 반복적이고 복잡한 API 호출에 적합.Axios
는 Fetch
보다 CORS
에러를 처리하기 쉬움.CORS
는 궁극적으로는 결국 서버에서 처리를 해줘야 하는 경우가 더 많다!AxiosInstance
는 Axios
를 커스터마이징한 객체로, 반복적으로 설정해야 하는 요청 구성을 한 곳에서 관리할 수 있도록 도와줍니다.baseURL
, headers
, timeout
)을 통합하고 코드 중복을 줄이며 유지보수를 쉽게 할 수 있습니다.baseURL
, headers
, params
등 모든 요청에 동일하게 적용되는 설정을 한 번에 관리.import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com', // 기본 URL 설정
timeout: 5000, // 요청 제한 시간 설정 (ms)
headers: { 'Content-Type': 'application/json' }, // 공통 헤더 설정
});
export default axiosInstance;
baseURL
: 모든 요청의 기본 경로.timeout
: 요청 시간 초과 시 실패하도록 설정.headers
: 공통적으로 적용할 요청 헤더.import axiosInstance from './axiosInstance';
// 인기 영화 가져오기
export const fetchPopularMovies = async () => {
const response = await axiosInstance.get('/movie/popular', {
params: { api_key: import.meta.env.VITE_TMDB_KEY },
});
return response.data.results;
};
// 새로운 리뷰 작성하기 (POST 요청)
export const createReview = async (movieId, review) => {
const response = await axiosInstance.post(`/movie/${movieId}/reviews`, review);
return response.data;
};
API
호출마다 baseURL
과 headers
를 명시할 필요가 없습니다.URL
과 주요 로직만 작성하여 코드가 간결해졌습니다.const API_KEY = import.meta.env.VITE_TMDB_KEY;
const BASE_URL = 'https://api.themoviedb.org/3';
// 인기 영화 가져오기
export const fetchPopularMovies = async () => {
try {
const response = await fetch(`${BASE_URL}/movie/popular?api_key=${API_KEY}`, {
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data.results;
} catch (error) {
console.error('Fetch Error:', error.message);
throw error; // 필요하면 에러를 다시 던질 수도 있음
}
};
// 현재 상영 중인 영화 가져오기
export const fetchLatestMovies = async () => {
try {
const response = await fetch(`${BASE_URL}/movie/now_playing?api_key=${API_KEY}`, {
headers: {
'Content-Type': 'application/json',
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data.results;
} catch (error) {
console.error('Fetch Error:', error.message);
throw error;
}
};
// 새로운 리뷰 작성하기 (POST 요청)
export const createReview = async (movieId, review) => {
try {
const response = await fetch(`${BASE_URL}/movie/${movieId}/reviews?api_key=${API_KEY}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(review),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch POST Error:', error.message);
throw error;
}
};
BASE_URL
과 api_key
가 각 함수마다 반복적으로 포함됩니다.response.ok
를 확인하는 로직을 모든 요청에 반복적으로 작성해야 합니다.response.json()
으로 변환해야 하므로 코드가 장황해집니다.import axios from 'axios';
const API_KEY = import.meta.env.VITE_TMDB_KEY;
const BASE_URL = 'https://api.themoviedb.org/3';
// 인기 영화 가져오기
export const fetchPopularMovies = async () => {
try {
const response = await axios.get(`${BASE_URL}/movie/popular`, {
params: { api_key: API_KEY },
headers: { 'Content-Type': 'application/json' },
});
return response.data.results;
} catch (error) {
console.error('Axios Error:', error.message);
throw error;
}
};
// 현재 상영 중인 영화 가져오기
export const fetchLatestMovies = async () => {
try {
const response = await axios.get(`${BASE_URL}/movie/now_playing`, {
params: { api_key: API_KEY },
headers: { 'Content-Type': 'application/json' },
});
return response.data.results;
} catch (error) {
console.error('Axios Error:', error.message);
throw error;
}
};
// 새로운 리뷰 작성하기 (POST 요청)
export const createReview = async (movieId, review) => {
try {
const response = await axios.post(
`${BASE_URL}/movie/${movieId}/reviews`,
review,
{
params: { api_key: API_KEY },
headers: { 'Content-Type': 'application/json' },
}
);
return response.data;
} catch (error) {
console.error('Axios POST Error:', error.message);
throw error;
}
};
response.data
로 바로 접근 가능.catch
블록으로 전달합니다.BASE_URL
과 api_key
가 여전히 각 요청마다 반복적으로 사용됩니다.Interceptor
는 Axios
에서 제공하는 기능으로, 요청(Request)이나 응답(Response)이 처리되기 전/후에 특정 작업을 수행할 수 있도록 도와줍니다.AxiosInstance
와 결합하여 사용되며, API
호출의 공통 작업을 한 곳에서 처리합니다.const API_KEY = import.meta.env.VITE_TMDB_KEY;
const BASE_URL = 'https://api.themoviedb.org/3';
const token = localStorage.getItem('authToken');
// 인기 영화 가져오기
export const fetchPopularMovies = async () => {
const response = await fetch(`${BASE_URL}/movie/popular`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
return data.results;
};
// 현재 상영 중인 영화 가져오기
export const fetchLatestMovies = async () => {
const response = await fetch(`${BASE_URL}/movie/now_playing`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
return data.results;
};
// 새로운 리뷰 작성하기 (POST 요청)
export const createReview = async (movieId, review) => {
const response = await fetch(`${BASE_URL}/movie/${movieId}/reviews`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(review),
});
const data = await response.json();
return data;
};
Authorization
헤더를 추가해야 함.// axiosInstance.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'https://api.themoviedb.org/3',
headers: { 'Content-Type': 'application/json' },
});
axiosInstance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
export default axiosInstance;
// API 호출 코드
import axiosInstance from './axiosInstance';
export const fetchPopularMovies = async () => {
const response = await axiosInstance.get('/movie/popular');
return response.data.results;
};
export const fetchLatestMovies = async () => {
const response = await axiosInstance.get('/movie/now_playing');
return response.data.results;
};
export const createReview = async (movieId, review) => {
const response = await axiosInstance.post(`/movie/${movieId}/reviews`, review);
return response.data;
};
Authorization
헤더를 한 곳에서 관리.const API_KEY = import.meta.env.VITE_TMDB_KEY;
const BASE_URL = 'https://api.themoviedb.org/3';
// 인기 영화 가져오기
export const fetchPopularMovies = async () => {
const response = await fetch(`${BASE_URL}/movie/popular?api_key=${API_KEY}&lang=en®ion=US`);
const data = await response.json();
return data.results;
};
// 현재 상영 중인 영화 가져오기
export const fetchLatestMovies = async () => {
const response = await fetch(`${BASE_URL}/movie/now_playing?api_key=${API_KEY}&lang=en®ion=US`);
const data = await response.json();
return data.results;
};
// 새로운 리뷰 작성하기 (POST 요청)
export const createReview = async (movieId, review) => {
const response = await fetch(`${BASE_URL}/movie/${movieId}/reviews?api_key=${API_KEY}&lang=en®ion=US`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(review),
});
const data = await response.json();
return data;
};
lang
, region
)를 추가해야 함.// axiosInstance.js
axiosInstance.interceptors.request.use(
(config) => {
config.params = { ...config.params, api_key: import.meta.env.VITE_TMDB_KEY, lang: 'en', region: 'US' };
return config;
},
(error) => Promise.reject(error)
);
export default axiosInstance;
// API 호출 코드
export const fetchPopularMovies = async () => {
const response = await axiosInstance.get('/movie/popular');
return response.data.results;
};
export const fetchLatestMovies = async () => {
const response = await axiosInstance.get('/movie/now_playing');
return response.data.results;
};
export const createReview = async (movieId, review) => {
const response = await axiosInstance.post(`/movie/${movieId}/reviews`, review);
return response.data;
};
Authorization
헤더를 한 곳에서 관리.🔥🔥🔥🔥🔥 백엔드 개발자가 자꾸 응답 데이터를
result key
에 담아서 보낸다고 가정해봅시다. 🔥🔥🔥🔥🔥
data : […]
원하는 값이 들어 있는게 아니고,data : { result : […] }
이런식으로 보낸다고 가정해봅시다.
export const fetchPopularMovies = async () => {
const response = await axios.get('https://api.example.com/movie/popular');
// 응답 데이터 전처리
if (response.data?.result) {
return response.data.result; // 서버에서 result 키에 데이터를 담아 보낸 경우
}
return response.data; // 데이터를 직접 반환
};
export const fetchLatestMovies = async () => {
const response = await axios.get('https://api.example.com/movie/latest');
// 응답 데이터 전처리
if (response.data?.result) {
return response.data.result;
}
return response.data;
};
response.data?.result
를 확인하고 전처리해야 함.// axiosInstance.js
axiosInstance.interceptors.response.use(
(response) => {
if (response.data?.result) {
return response.data.result; // 서버가 데이터를 result 키에 담아 보내는 경우
}
return response.data; // 데이터를 직접 반환
},
(error) => Promise.reject(error)
);
export default axiosInstance;
// API 호출 코드 api.js
export const fetchPopularMovies = async () => {
const response = await axiosInstance.get('/movie/popular');
return response; // 이미 전처리된 데이터 반환
};
export const fetchLatestMovies = async () => {
const response = await axiosInstance.get('/movie/latest');
return response;
};
export const fetchPopularMovies = async () => {
try {
const response = await axios.get('https://api.example.com/movie/popular');
return response.data;
} catch (error) {
// 상태 코드별 에러 처리
if (error.response?.status === 401) {
alert('Authentication failed. Please log in again.');
} else if (error.response?.status === 404) {
console.error('Resource not found.');
} else {
console.error('An unexpected error occurred:', error.message);
}
throw error; // 에러를 다시 던짐
}
};
export const fetchLatestMovies = async () => {
try {
const response = await axios.get('https://api.example.com/movie/latest');
return response.data;
} catch (error) {
// 상태 코드별 에러 처리
if (error.response?.status === 401) {
alert('Authentication failed. Please log in again.');
} else if (error.response?.status === 404) {
console.error('Resource not found.');
} else {
console.error('An unexpected error occurred:', error.message);
}
throw error;
}
};
// axiosInstance.js
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
// 상태 코드별 에러 처리
if (error.response?.status === 401) {
alert('Authentication failed. Please log in again.');
} else if (error.response?.status === 404) {
naviagte.push('/not-found')
console.error('Resource not found.');
} else {
console.error('An unexpected error occurred:', error.message);
}
return Promise.reject(error); // 에러를 다시 던져 호출한 함수에서 추가 처리 가능
}
);
export default axiosInstance;
// API 호출 코드
export const fetchPopularMovies = async () => {
try {
const response = await axiosInstance.get('/movie/popular');
return response.data;
} catch (error) {
// 인터셉터에서 처리되지 않은 에러 추가 처리
console.error('Error fetching popular movies:', error.message);
// 빈 배열 반환 또는 재정의된 에러 처리
return [];
}
};
export const fetchLatestMovies = async () => {
try {
const response = await axiosInstance.get('/movie/latest');
return response.data;
} catch (error) {
console.error('Error fetching latest movies:', error.message);
// 기본값 반환
return {};
}
};
Unauthorized
에러가 발생했을 때, 리프레시 토큰으로 인증 갱신 후 요청을 재시도합니다.axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
try {
const refreshToken = localStorage.getItem('refreshToken');
const { data } = await axios.post('/auth/refresh-token', { refreshToken });
// 새 토큰 저장
localStorage.setItem('authToken', data.token); // 갱신된 토큰을 가지고 있죠?
// 원래 요청 재시도
error.config.headers.Authorization = `Bearer ${data.token}`;
return axiosInstance.request(error.config);
} catch (refreshError) {
console.error('Token Refresh Error:', refreshError.message);
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
Interceptor
는 전역 작업에 적합하며, 요청별 세부 로직은 try-catch
로 처리해야 함.오늘은 리액트 심화주차 발제와 함께 강의가 주어졌다.
매니저님, 튜터님 모두 이번주차는 정말 어렵다고 하셔서 강의를 봤는데 정말 어려웠다.
(생전 처음보고 듣는 단어들이 많았다.)
주어진 강의를 약 40% 정도 들은것 같은데 주말에 더욱 공부해야겠다.
axios
에 대해 오늘 처음 알게됐는데 custom instance
와 interceptors
를 잘 사용하면 너무 좋을것 같다.
우선 원리와 사용법을 익혀두고 프로젝트를 하면서 익숙해져보자!!
보통 그러지 않는편인데 오늘 챌린지반 강의가 너무 좋았다.
위 자료는 내가 따로 공부하고싶어서 튜터님께서 만들어주신 강의를 가져왔다.
이것보다 더 머리에 쏙쏙 들어오고 이해가 잘되는 문서나 강의가 없기에 내가 보기 편하게 정리했다.
또 이번 심화주차에서 아웃소싱프로젝트때까지 팀장을 맡게 됐는데,
팀원들이 편하게 개발 할 수 있도록 최선을 다해보겠다. 화이팅!!!