학습 목표
- REST API의 개념과 활용 방식을 이해하고 적용할 수 있다.
- axios와 json-server를 사용해 CRUD 작업을 수행할 수 있다.
- Tanstack Query를 이용해 서버 데이터의 상태를 효율적으로 관리할 수 있다.
- axios interceptor를 활용하여 요청 및 응답 로직을 재사용 가능하도록 최적화할 수 있다.
- 인증 상태에 따라 리다이렉트를 처리하는 방법을 학습한다.
REST API
클라이언트와 서버 간의 통신 규칙으로, HTTP 메서드(GET, POST, PATCH, DELETE 등)와 URL을 사용해 자원을 요청하고 응답받는 방식을 정의합니다.
API Client
API Client란 서버와 통신하기 위해 클라이언트(사용자 애플리케이션)에서 HTTP 요청을 생성하고, 서버로부터 받은 응답을 처리하는 도구 또는 라이브러리를 말합니다. 주로 서버에서 만든 api 가 api 문서(명세)대로 제대로 동작하는 지 테스트해보는 용도로 사용합니다.
json-server
JSON 파일을 기반으로 가상 REST API 서버를 생성하는 도구로, 간단한 CRUD 작업을 테스트하는 데 유용합니다.
1. jwt 인증서버 (REST API 사용법 확인)
REST API란?
- 클라이언트-서버 통신에서 HTTP 메서드와 URL을 통해 요청을 식별하고 자원을 주고받는 규칙
인증서버 API문서
- 서버 API_URL : https://moneyfulpublicpolicy.co.kr
https://moneyfulpublicpolicy.co.kr
moneyfulpublicpolicy.co.kr
회원가입
- 아이디, 비밀번호, 닉네임으로 DB에 본인의 회원정보를 저장합니다.
- RequestURL PATH → /register
- Method → POST
{
"id": "유저 아이디",
"password": "유저 비밀번호",
"nickname": "유저 닉네임"
}
{
"message": "회원가입 완료",
"success": true
}
로그인
- RequestURL PATH → /login
- Method → POST
JSON
{
"id":"유저 아이디",
"password": "유저 비밀번호"
}
Query string ⬇️ (선택)
accessToken 유효시간 조정을 위한 query string
/login?expiresIn=10m
// 유효시간을 10분인 accessToken 요청
Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFiY2FiYyIsImlhdCI6MTcwMDgxNDQyMCwiZXhwIjoxNzAwODE4MDIwfQ.8hWOHHEzDPzumnqCU7jyoi3zFhr-HNZvC7_pzBfOeuU",
"userId": "유저 아이디",
"success": true,
"avatar": "프로필 이미지",
"nickname": "유저 닉네임"
}
회원정보 확인
- RequestURL PATH → /user
- Method → GET
{
"Authorization": "Bearer AccessToken"
}
Response
{
"id": "사용자 아이디",
"nickname": "사용자 닉네임",
"avatar": null,
"success": true
}
프로필 변경
- RequestURL PATH → /profile
- Method → PATCH
Header ⬇️
(참고: Thunder Client 의 경우 header에 Content-Type을 지정하지 않으셔야 정상동작하지만, 실제 api 요청 코드에서는 Content-Type을 반드시 아래와 같이 지정해야 정상동작합니다.)
{
"Content-Type": "multipart/form-data",
"Authorization": "Bearer AccessToken"
}
Body ⬇️
FORM
{
"avatar": [이미지파일],
"nickname": "변경할 닉네임"
}
Response
{
"avatar": "변경된 이미지 URL",
"nickname": "변경된 닉네임",
"message": "프로필이 업데이트되었습니다.",
"success": true
}
- api client 를 설치합시다.API 명세대로 api 요청했을 때 제대로 동작하는 지 테스트 해볼 수 있습니다!
- vs code extension 에서 postman 을 검색해 보세요!
axios 기본 사용법
axios 를 왜 사용할까요?
1. 요청/응답 시 json 데이터형식을 기본으로 함 (vs. fetch API)
GET 요청
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
axios.get('/api/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
POST 요청
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
axios.post('/api/data', { key: 'value' })
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
-
- custom intance 와 interceptor 를 이용한 코드 재사용
- 크로스 브라우징에 유리
기본 형태
https://axios-http.com/kr/docs/api_intro
Axios API | Axios Docs
Axios API Axios API 레퍼런스 axios에 해당 config을 전송하면 요청이 가능합니다. axios(config) axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }); axios({ method: 'get', url: 'http://bit.ly/
axios-http.com
// GET 요청
axios.get(url[, config]);
axios.get("http://localhost:3000/todos");
axios.get("http://localhost:3000/todos", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
// POST 요청
axios.post(url[, data[, config]]);
axios.post("http://localhost:3000/todos", { id: Date.now(), title: "abc" });
axios.post("http://localhost:3000/todos", { id: Date.now(), title: "abc" },
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
// PATCH 요청
axios.patch(url[, data[, config]]);
axios.patch("http://localhost:3000/todos/1", { title: "abc" });
// DELETE 요청
axios.delete(url[, config]);
axios.delete("http://localhost:3000/todos/1");
axios.delete("http://localhost:3000/todos/1", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
요청 Config | Axios Docs
https://axios-http.com/kr/docs/req_config
요청 Config | Axios Docs
요청 Config 다음은 요청을 만드는 데 사용할 수 있는 config 옵션들 입니다. 오직 url만 필수입니다. method를 지정하지 않으면 GET방식이 기본값 입니다. { url: '/user', method: 'get', baseURL: 'https://some-domain.
axios-http.com
인증상태에 따른 리다이렉트 처리
const PublicRoute = () => {
// 로그인 상태면 홈 화면으로 리다이렉트하는 라우트
const isLogin = useSelector((state) => state.auth.isLogin);
return <>{isLogin ? <Navigate to="/" /> : <Outlet />}</>;
};
const PrivateRoute = () => {
// 로그아웃 상태면 로그인 화면으로 리다이렉트하는 라우트
const isLogin = useSelector((state) => state.auth.isLogin);
return <>{isLogin ? <Outlet /> : <Navigate to="/login" />}</>;
};
json브랜치 내용
- json-server CRUD
- package.json 에 json-server 실행문을 scripts 로 추가합시다.
{
...
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"json: "json-server --watch db.json --port 5000"
},
...
}
https://github.com/typicode/json-server
GitHub - typicode/json-server: Get a full fake REST API with zero coding in less than 30 seconds (seriously)
Get a full fake REST API with zero coding in less than 30 seconds (seriously) - typicode/json-server
github.com
자주 사용되는 CRUD 패턴
// GET (최신순 정렬)
const { data } = await axios.get(`baseURL/todos?_sort=createdAt&_order=desc"`);
// POST 추가
const { data } = await axios.post(`baseURL/todos`, newTodo);
// PATCH 수정
const { data } = await axios.patch(`baseURL/todos/${id}`, { title: "수정된제목" });
// DELETE 삭제
const { data } = await axios.delete(`baseURL/todos/${id}`);
⬇️ instance브랜치 내용
axios custom instance
baseURL 재사용
// api 요청 코드가 100개 있다고 가정해봅시다.
axios.get("http://localhost:3000/todos");
axios.get("http://localhost:3000/todos");
axios.get("http://localhost:3000/todos");
axios.get("http://localhost:3000/todos");
// ... x 100
// baseURL 의 포트번호가 3000 에서 3001 로 변경되면 100개를 다 찾아서 변경해야 겠죠?
const axiosInstance = axios.create({
baseURL: 'http://localhost:3000',
});
// 아래 코드에서 base url 변경이 발생하면,
// 위 axiosInstance 의 baseURL만 변경시켜주면 되겠죠?
axiosInstance.get("/todos");
axiosInstance.get("/todos");
axiosInstance.get("/todos");
axiosInstance.get("/todos");
// ... x 100
API 서버 별로 분리 시 유용
// 1. axios instance 미사용 시
// 인증서버로 요청
const { data } = await axios.post("https://moneyfulpublicpolicy.co.kr/register",
{
"id": "유저 아이디",
"password": "유저 비밀번호",
"nickname": "유저 닉네임"
});
// json 서버로 요청
const { data } = await axios.get("http://localhost:3000/todos");
// 2. axios instance 사용 시
const authApi = axios.create({
baseURL: 'https://moneyfulpublicpolicy.co.kr'
});
const jsonApi = axios.create({
baseURL: 'http://localhost:3000'
});
const { data } = authApi.post("/register", {
"id": "유저 아이디",
"password": "유저 비밀번호",
"nickname": "유저 닉네임"
});
const { data } = jsonApi.get("/todos");
axios interceptor
request header 코드 재사용
// jwt인증서버의 경우, 요청할 때 마다 헤더에 accessToken 을 넣어주는 번거로움이 있습니다.
authApi.interceptors.request.use(
(config) => {
// 헤더에 토큰 넣기
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
return config;
},
(err) => Promise.reject(err)
);
// json서버 요청의 경우, 로그인 상태일 경우에만 CRUD가 가능해야 하므로,
// accessToken을 인증서버에 보내서 인가받은 후에 요청을 보내도록 해야 합니다.
jsonApi.interceptors.request.use(
async (config) => {
const { data } = await authApi.get("/user");
if (data.success) return config;
return Promise.reject(new Error('사용자 정보 조회에 실패 했습니다.'));
},
(err) => Promise.reject(err)
);
response 에러 핸들링 용도로 사용
- 서버의 에러케이스별로 에러핸들링을 꼼꼼하게 하면 좋지만, interceptor에서는 토큰 만료의 경우만 예시로 다룹니다.
authApi.interceptors.response.use(
(response) => response,
(err) => {
alert(err.response.data.message);
if (
err.response.data.message ===
"토큰이 만료되었습니다. 다시 로그인 해주세요."
) {
// 로그아웃처리
return store.dispatch(logout());
}
return Promise.reject(err);
}
);
tanstack 랜치 내용
Tanstack Query 셋업
yarn add @tanstack/react-query @tanstack/react-query-devtools
https://tanstack.com/query/latest/docs/framework/react/installation
TanStack | High Quality Open-Source Software for Web Developers
Headless, type-safe, powerful utilities for complex workflows like Data Management, Data Visualization, Charts, Tables, and UI Components.
tanstack.com
https://tanstack.com/query/latest/docs/framework/react/devtools
TanStack | High Quality Open-Source Software for Web Developers
Headless, type-safe, powerful utilities for complex workflows like Data Management, Data Visualization, Charts, Tables, and UI Components.
tanstack.com
// queryClient 는 리덕스와 비교 시 store 에 해당하는 개념이라 할 수 있습니다.
// main.jsx
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")).render(
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<App />
</QueryClientProvider>
);
Tanstack Query - useQuery
- GET 요청으로 응답받은 데이터를 상태(캐시)로 관리합니다.
- 캐시 Context 에서 queryKey에 해당하는 값을 읽고 구독합니다.
const { data } = useQuery({ queryKey: ["todos"], queryFn: getTodos });
export const getTodos = async () => {
try {
const { data } = await jsonApi.get("/todos?_sort=createdAt&_order=desc");
return data;
}
catch (err) {
console.error(err);
}
};
https://tanstack.com/query/latest/docs/framework/react/quick-start
TanStack | High Quality Open-Source Software for Web Developers
Headless, type-safe, powerful utilities for complex workflows like Data Management, Data Visualization, Charts, Tables, and UI Components.
tanstack.com
Tanstack Query - useMutation
- GET 이외의 요청들 (POST, PATCH, DELELTE, PUT) 다룹니다.
- 서버 DB 에 데이터 수정/변경 요청을 합니다.
- DB 가 변경되면 캐시 Context 와 동기화를 하는 것이 컨벤션입니다.
const queryClient = useQueryClient();
const addMutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
// 캐시 context 에서 queryKey 가 todos에 해당하는 데이터를 새롭게 갱신하여 동기화합니다.
queryClient.invalidateQuries(["todos"]);
}
});
export const addTodo = (newTodo) => jsonApi.post("/todos", newTodo);
연습
https://github.com/rjc1704/st-10th-lecture
GitHub - rjc1704/st-10th-lecture
Contribute to rjc1704/st-10th-lecture development by creating an account on GitHub.
github.com
'sparta > REACT' 카테고리의 다른 글
[REACT] 아웃소싱 프로젝트 (0) | 2024.12.02 |
---|---|
[REACT] SUPABASE (1) | 2024.11.30 |
[REACT] 심화주차 - Axios (0) | 2024.11.24 |
[REACT] 심화주차 - JSON Server (1) | 2024.11.24 |
[REACT] 심화주차 - 비동기, 동기 (1) | 2024.11.24 |