웹 애플리케이션을 개발하다 보면, 프론트엔드와 백엔드를 연동하는 과정은 필수적인 단계입니다. 이번 글에서는 실제 서버와 프론트엔드 코드를 연동하는 과정에 대해 자세히 알아보겠습니다. 이 과정은 초보 개발자나 처음 서버와 프론트를 연결하는 분들에게 유용할 것입니다.
Axios는 HTTP 요청을 다루는 라이브러리로, RESTful API와 통신하는 프론트엔드 개발에 필수적인 도구입니다. 이 글에서는 Axios Instance를 활용해 API 통신을 효율적으로 설정하는 방법을 살펴보고, Interceptor를 통해 인증 토큰 처리까지 구현하는 과정을 알아보겠습니다.
1. API Instance 생성하기 (Axios Instance)
API 통신은 대부분 HTTP 클라이언트를 사용하여 이루어집니다. 저는 axios를 활용하여 API 요청을 처리하는 instance를 만들어 보겠습니다. API instance는 여러 요청에 대해 공통적인 설정과 전처리를 적용할 수 있는 유용한 도구입니다.
Axios Instance?
이것은 서버와 통신하는 라이브러리인 axios를 인스턴스화 하여 생성한 객체입니다. 이 객체는 HTTP 요청을 보내고 응답받는데 사용됩니다. Instance로 (기본 통신 객체)를 설정하고 별도의 객체를 만들어 독립적인 설정도 가능합니다.
결론적으로, 이 Axios Instance를 활용하면, 여러개의 서로 다른 설정을 가진 인스턴스를 만들어 재사용이 가능합니다.
장점
- API의 엔드포인트가 여러가지 일단 각각에 대한 base URL을 설정해두면 편하게 접근이 가능합니다,
- interceptor, header 등 인스턴스에 딱 한번 정의하여 중복을 피합니다.
- 코드의 모듈성과 재사용성을 부여합니다.
기능 확장
Axios Instance interceptor를 활용하면 Axios 요청과 응답을 가로채서 수정을 할 수 있습니다.
Instance를 사용하여 기본 설정을 정의하고, interceptors를 사용하여 특정 요청에 대한 인증, 로깅등 추가적인 처리를 구현하는것이 전체적인 틀입니다!
interceptor는 그렇다면 무엇일까?
interceptor는 axios 라이브러리에서 제공하는 기능입니다.
굳이 인터셉트를 해야하는 이유가 궁금할 수 있습니다.
그러나 이를 통해 요청을 보내기 전, 혹은 응답이 반환되기전 특정 작업을 수행 할 수 있는 장점이 있기에 이를 활용합니다.
문법
interceptor는 두개를 대표적으로 활용합니다 (request,response) 관련 메서드 2개입니다.
axios.interceptors.request.use
axios.interceptors.response.use
각 메서드는 두개의 콜백 함수를 인수로 받습니다 (첫번쨰 콜백 : 성공 , 두번째 콜백 : 실패)
1. 첫 번째 콜백함수는 요청이나 응답이 성공할경우 호출
2. 두 번째 콜백함수는 요청이나 응답이 성공할경우 호출
아래는 제가 실제 프로젝트에서 쓰고있는 응답 및 요청 인터셉터 , 마지막 토큰 갱신함수입니다.
요청 인터셉터의 동작 흐름
// 요청 인터셉터
apiClient.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');
if (accessToken) {
config.headers['ACCESS-AUTH-KEY'] = `BEARER ${accessToken}`;
}
if (refreshToken) {
config.headers['REFRESH-AUTH-KEY'] = `BEARER ${refreshToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
1. apiClient.interceptors.request.use
Axios 요청 인터셉터를 설정하는 메서드입니다. 두 개의 콜백 함수를 받습니다:
• 첫 번째 콜백: 요청을 수정하거나 설정을 추가하는 로직을 정의.
첫번째 콜백함수가 이해가 안 갈 수 있기에 정리합니다.
# config 객체는 무엇인가?
매개변수로 활용되는 config는 axios의 요청의 설정 정보를 담고 있는 객체입니다. 이 객체에는 요청 메서드, URL, 헤더, 데이터 등이 포함됩니다.
2. 엑세스 토큰 및 리프레시 토큰 가져오기
const accessToken = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');
• 브라우저의 localStorage에서 token(엑세스 토큰)과 refreshToken(리프레시 토큰)을 가져옵니다.
• 인증이 필요한 요청에 대해 이 토큰들을 사용해 요청 헤더를 설정합니다.
3. 헤더 설정
-> 이 부분은 프로젝트 마다 백엔드 명세에 따라 다릅니다.
config.headers['ACCESS-AUTH-KEY'] = `BEARER ${accessToken}`;
config.headers['REFRESH-AUTH-KEY'] = `BEARER ${refreshToken}`;
• ACCESS-AUTH-KEY: 엑세스 토큰을 인증 헤더에 추가. 이 헤더는 보통 백엔드에서 요청을 인증하기 위해 사용됩니다.
• REFRESH-AUTH-KEY: 리프레시 토큰을 별도의 헤더에 추가하여, 토큰 갱신이 필요한 경우 백엔드에서 이를 처리하도록 만듭니다.
4. return config
수정된 요청 설정(config) 객체를 반환합니다. 이 반환된 객체는 Axios가 실제 HTTP 요청을 보낼 때 사용됩니다.
5. 에러 처리 (두번째 콜백함수) <서버와 통신이 실패할 경우 발생.>
(error) => Promise.reject(error)
• 요청 설정 도중 에러가 발생할 경우, 이를 Promise.reject로 반환하여 호출한 쪽에서 에러를 처리할 수 있게 합니다.
응답 인터셉터의 동작 흐름
// 응답 인터셉터
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (
error.response &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
try {
const newAccessToken = await refreshAccessToken();
originalRequest.headers['ACCESS-AUTH-KEY'] = `BEARER ${newAccessToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
console.error('토큰 갱신 실패:', refreshError);
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
1. 성공 응답 처리 (첫 번째 콜백함수)
(response) => response
• 응답이 성공한 경우, 서버에서 받은 데이터를 그대로 반환합니다.
• 예를 들어, 서버가 200 OK 상태 코드와 함께 데이터를 반환하면, 이 데이터는 호출한 함수에서 사용할 수 있도록 그대로 전달됩니다.
200이 우리가 원하는 시그널입니다!
2. 에러 응답 처리 (두번째 콜백함수)
async (error) => {
const originalRequest = error.config;
if (
error.response &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
try {
const newAccessToken = await refreshAccessToken();
originalRequest.headers['ACCESS-AUTH-KEY'] = `BEARER ${newAccessToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
console.error('토큰 갱신 실패:', refreshError);
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
코드가 너무 깁니다. (그러나 차근차근 보면 이길 수 있습니다!)
#1. error handling with error.config 객체
async (error) => {
const originalRequest = error.config;
}
먼저 에러에 관한 비동기 함수의 매개변수를 error로 하여금 상태 코드와 여러 메시지를 유도합니다.
(바로 200이 뜨면 그것은 기적이겠지요)
즉 error.config는 에러가 발생한 원래 요청의 설정 정보를 담고 있습니다.
이를 활용해 엑세스 토큰 갱신 후 동일한 요청을 다시 보내기 위해 사용합니다.
#2. 401 error 따로 처리
이것은 저희 선험의 근거 코드입니다. (계속 401이 떠서 에러를 무한요류가나서 무한로딩 방지.. 따로 빼버렸습니다.)
401 에러는?
엑세스 토큰이 만료되었거나 유효하지 않은 경우 등 권한이 없을때 발생하는 에러입니다.
• _retry 플래그:
• 동일한 요청이 무한히 반복되지 않도록 방지하기 위한 사용자 정의 속성입니다.
• 요청을 한 번만 재시도하도록 설정합니다.
#3, 리프레시 토큰으로 엑세스 토큰 갱신
const newAccessToken = await refreshAccessToken();
originalRequest.headers['ACCESS-AUTH-KEY'] = `BEARER ${newAccessToken}`;
return apiClient(originalRequest);
• refreshAccessToken 함수는 기존의 리프레시 토큰을 사용해 서버에서 새로운 엑세스 토큰을 발급받는 작업을 수행합니다.
• 발급받은 새 엑세스 토큰을 원래 요청의 헤더에 추가한 뒤, 동일한 요청을 다시 실행합니다.
#4. 갱신 실패 처리
catch (refreshError) {
console.error('토큰 갱신 실패:', refreshError);
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
}
리프레시 토큰이 만료되었거나, 서버에서 갱신 작업이 실패한 경우:
1. 토큰 제거: 로컬 스토리지에서 엑세스 토큰과 리프레시 토큰을 삭제합니다.
2. 로그아웃 처리: 사용자 세션을 종료하고, 로그인 페이지로 리다이렉트합니다.
6. 다른 에러 throw
자바에서 throw와 비슷한 녀석입니다.
return Promise.reject(error);
• 401 이외의 에러는 인터셉터에서 별도로 처리하지 않고, 호출한 곳으로 그대로 전달합니다.
• 예: 404 Not Found, 500 Internal Server Error 등
Promise.reject는 호출 시 즉시 실패 상태의 Promise를 반환하며, 이 Promise는 .catch()에서 처리되거나 상위 호출 스택에서 처리됩니다.
Promise.reject(new Error('Something went wrong!'))
.catch((error) => console.error(error.message));
// 출력: Something went wrong!
'FrontEnd Develop' 카테고리의 다른 글
⏿ 실제 백엔드와 프론트엔드 통신 #4. Axios Instance에서 셋의 비동기적 공존 (0) | 2025.01.24 |
---|---|
⏿ 실제 백엔드와 프론트엔드 통신 #3. Axios Instance와 Refresh, Access 토큰 함수 (0) | 2025.01.24 |
⏿실제 백엔드와 프론트엔드 통신 #1. 인증과 권한부여 <토큰> (0) | 2025.01.24 |
Data Fetching : User의 능동성 여부와 useEffect의 올바른 공존 (0) | 2025.01.22 |
🌐 Context로 상태와 함수를 글로벌하게 관리하며 성능 개선하기 (0) | 2025.01.21 |