-
Notifications
You must be signed in to change notification settings - Fork 3
API 요청 핸들러 사용하기
Eojin Choi edited this page Jun 8, 2024
·
1 revision
시즈닝은 axios
모듈을 이용해 백엔드 서버로의 REST API 요청을 처리하고 있으며,
프론트엔드에서 요청과 에러 코드 처리를 돕는 API 요청 핸들러를 분리해 사용하고 있습니다.
// @utils/api/APIService.js
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
timeout: 15000, // 요청 만료 시간을 15초로 설정합니다.
});
const getAccessToken = () => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
return accessToken;
} else {
console.log('* No Access Token... Redirecting to /login');
throw new Error('No access token');
}
};
api.interceptors.request.use(
(config) => {
try {
const accessToken = getAccessToken();
config.headers.Authorization = `Bearer ${accessToken}`;
} catch (error) {
console.error('Access token error:', error.message);
window.location.href = '/login';
throw new axios.Cancel('Operation canceled');
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
console.log('* Unauthorized... Redirecting to /login');
window.location.href = '/login';
} else if (error.response) {
console.log('* Response Error...');
}
return Promise.reject(error);
}
);
export default api;
- axios에서 제공하는 요청 인터셉터를 활용해 Access Token의 유효성을 검증합니다.
- 마찬가지로, 응답 인터셉터를 활용해 응답의 에러 코드에 따른 공통 처리 로직을 수행합니다.
- 401(: 토큰 만료) -
/login
페이지로 이동 - 기타 에러 코드 - API 요청을 수행한 컨텍스트에서 필요 시 분기 처리
- 401(: 토큰 만료) -
// @utils/loader/HomeLoader.jsx
import api from '@utils/api/APIService';
export async function HomeLoader({ request }) {
// ...
const homeResponse = await api.get(
category === 'year'
? `/article/list/year/${year}`
: `/article/list/term?term=${term}&size=20`
);
const termResponse = await api.get(`/solarTerm`);
const newNotificationResponse = await api.get(`/notification/new`);
return {
homeData: homeResponse.data,
termData: termResponse.data,
newNotificationData: newNotificationResponse.data,
};
}
- React-router에서 제공하는 loader 함수를 활용해 페이지가 렌더링되기 전에 필요한 데이터를 fetch할 수 있습니다.
// @contexts/FeedContext.js
// ...
import api from '@utils/api/APIService';
export const FeedContext = createContext();
export function useFeedContext(loaderData) {
// ...
const fetchFeedData = async () => {
// ...
try {
const feedResponse = await api.get(
`/article/friends?size=${size}&lastId=${lastFeedItemId}`
);
if (feedResponse.data.length === 0) {
setLastFeedItemId(null);
} else {
setFeedData((feedData) => [...feedData, ...feedResponse.data]);
setLastFeedItemId(feedResponse.data.at(-1).article.id);
}
} catch (error) {
console.error(error);
setLastFeedItemId(null);
}
};
// ...
}
- APIService의 응답 인터셉터에 의해 Promise가 전달되므로, try-catch 블록 안에 요청을 위치시킬 수 있습니다.
// @utils/hooks/useArticleForm.jsx
// ...
import api from '@utils/api/APIService';
export default function useArticleForm({ ... }) {
// ...
const handleSave = async () => {
if (!images.length && !contents.some((item) => item.text.trim())) {
alert('내용을 입력하세요.');
return;
}
try {
const formData = new FormData();
// ...
if (mode === 'write') {
const writeResponse = await api({
method: 'POST',
url: `/article`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
navigate(`/article/${writeResponse.data}`, { replace: true });
} else if (mode === 'edit') {
const putResponse = await api({
method: 'PUT',
url: `/article/${articleId}`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
navigate(`/article/${articleId}`, { replace: true });
}
} catch (error) {
console.error('Error details:', error);
}
};
return { ... };
}
- FormData 요청을 보내는 경우,
config
의headers
객체의 값으로'Content-Type': 'multipart/form-data'
를 전달할 수 있습니다. 이 경우 새롭게 전달된 config가 APIService instance보다 더 높은 우선순위를 가져 설정이 덮어씌워집니다.