
HTTP 요청 라이브러리는 웹 애플리케이션이 서버와 통신할 때 사용되는 도구로, HTTP 프로토콜을 통해 데이터를 주고받는 작업을 간편하게 처리할 수 있도록 도와준다. 이러한 라이브러리는 HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 서버로 요청을 보내고, 서버로부터 응답을 받아오는 기능을 제공한다. 주로 프론트엔드와 백엔드 간의 데이터 통신을 위해 사용된다.
비동기 통신의 편리성: HTTP 요청 라이브러리는 비동기 통신을 쉽게 구현할 수 있도록 도와준다. 이를 통해 사용자가 웹 애플리케이션을 사용하는 동안 서버와 통신하는 동안에도 인터페이스가 응답성을 유지할 수 있게 된다.
코드 간결화: HTTP 요청 라이브러리를 사용하면 복잡한 XMLHttpRequest 객체를 직접 다루지 않아도 된다. 간단한 함수 호출로 HTTP 요청을 처리할 수 있어 코드가 훨씬 간결하고 가독성이 높아진다.
에러 처리: 라이브러리는 HTTP 요청 중 발생할 수 있는 다양한 에러를 효과적으로 처리할 수 있는 메커니즘을 제공한다. 이를 통해 개발자는 더 안정적인 애플리케이션을 개발할 수 있다.
다양한 기능 지원: 요청 및 응답 인터셉터, 자동 JSON 데이터 변환, 타임아웃 설정, 요청 취소 등의 기능을 제공하여 개발자가 필요로 하는 다양한 요구사항을 쉽게 구현할 수 있다.
Axios는 2016년에 출시된 Promise 기반의 HTTP 클라이언트로, 브라우저와 Node.js 환경 모두에서 사용할 수 있다. 요청 및 응답 인터셉터, 자동 JSON 데이터 변환, XSRF 보호 등 다양한 기능을 제공했다.
주요 기능:
장점:
단점:
axios.get('https://api.example.com/data')
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
Fetch API는 2015년에 출시된 브라우저 내장 API로, 별도의 설치 없이 사용할 수 있다. Promise 기반으로 동작하며, 사용법이 간단하지만 기본 기능 외의 고급 기능은 추가 구현이 필요했다.
주요 기능:
장점:
단점:
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
React Query는 2020년에 출시된 React 애플리케이션에서 서버 상태를 관리하기 위한 라이브러리다. 데이터 페칭, 캐싱, 동기화 등을 쉽게 처리할 수 있으며, HTTP 요청 자체보다는 데이터 관리와 상태 동기화에 중점을 두었다.
주요 기능:
장점:
단점:
import { useQuery } from 'react-query';
function App() {
const { data, error, isLoading } = useQuery('fetchData', () =>
fetch('https://api.example.com/data').then(res => res.json())
);
if (isLoading) return <span>Loading...</span>;
if (error) return <span>Error: {error.message}</span>;
return <div>{JSON.stringify(data)}</div>;
}
Axios는 요청과 응답을 가로채서 처리할 수 있는 인터셉터 기능을 제공한다. 이를 통해 요청을 보내기 전에, 응답을 받기 전에 특정 작업을 수행할 수 있다. 예를 들어, 요청에 공통 헤더를 추가하거나 응답 에러를 처리하는 데 유용하다.
axios.interceptors.request.use(
function (config) {
// 요청이 전달되기 전에 수행할 작업
config.headers.Authorization = `Bearer ${token}`;
return config;
},
function (error) {
// 요청 오류가 발생했을 때 수행할 작업
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
// 응답 데이터 가공
return response;
},
function (error) {
// 응답 오류가 발생했을 때 수행할 작업
if (error.response.status === 401) {
// 예를 들어, 인증 오류 처리
window.location.href = '/login';
}
return Promise.reject(error);
}
);
Axios는 기본 설정을 가진 인스턴스를 생성하여 여러 곳에서 재사용할 수 있다. 이를 통해 일관된 설정으로 여러 요청을 보낼 수 있으며, 각 인스턴스마다 다른 설정을 가질 수 있다.
const apiClient = axios.create({
baseURL: 'https://example.com/api',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' }
});
const handlePutRequest = async () => {
setIsLoading(true);
const requestData = {};
try {
const response = await apiClient.put(
`/resource`,
requestData
);
alert('완료되었습니다.');
setReleasePatch(response);
} catch (error) {
console.error('POST 요청 중 오류 발생:', error);
alert('등록을 실패했습니다.');
}
setIsLoading(false);
};
Axios는 요청을 취소할 수 있는 기능을 제공한다. 이를 통해 불필요한 요청을 중단하거나 사용자가 페이지를 이동할 때 미완료된 요청을 취소할 수 있다.
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const handlePutRequest = async () => {
setIsLoading(true);
const requestData = {};
try {
const response = await axios.put(
`https://example.com/api/resource`,
requestData,
{ cancelToken: source.token }
);
alert('등록을 완료했습니다.');
setReleasePatch(response);
} catch (error) {
if (axios.isCancel(error)) {
alert('등록을 취소했습니다.');
} else {
console.error('POST 요청 중 오류 발생:', error);
alert('등록을 실패했습니다.');
}
}
setIsLoading(false);
};
// 요청 취소 예시
source.cancel('Operation canceled by the user.');
응답 데이터를 변환하는 방법을 정의하여 서버에서 받은 데이터를 원하는 형식으로 가공할 수 있다.
const handlePutRequest = async () => {
setIsLoading(true);
const requestData = {};
try {
const response = await axios.put(
`https://example.com/api/resource`,
requestData,
{
transformResponse: [function (data) {
// JSON 문자열을 객체로 변환
let parsedData = JSON.parse(data);
// 데이터 변환 작업 수행
parsedData.customField = 'Custom Value';
return parsedData;
}]
}
);
alert('완료되었습니다.');
setReleasePatch(response);
} catch (error) {
console.error('POST 요청 중 오류 발생:', error);
alert('등록을 실패했습니다.');
}
setIsLoading(false);
};
Axios는 요청과 응답에 대한 디버깅과 로깅을 지원한다. 이를 통해 요청이 어떻게 이루어졌는지, 응답이 어떻게 돌아왔는지 쉽게 추적할 수 있다.
axios.interceptors.request.use(request => {
console.log('Starting Request', request);
return request;
});
axios.interceptors.response.use(response => {
console.log('Response:', response);
return response;
});
const handlePutRequest = async () => {
setIsLoading(true);
const requestData = {};
try {
const response = await axios.put(
`https://example.com/api/resource`,
requestData
);
alert('완료되었습니다.');
setReleasePatch(response);
} catch (error) {
console.error('POST 요청 중 오류 발생:', error);
alert('등록을 실패했습니다.');
}
setIsLoading(false);
};
Axios는 브라우저 환경에서 CSRF 공격을 방지하는 기능을 기본으로 지원한다.
CSRF(Cross-Site Request Forgery): 사용자가 인식하지 못한 상태에서 원치 않는 요청을 서버에 보내는 공격
axios.defaults.xsrfCookieName = 'CSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
Fetch API는 스트리밍을 지원하여 대규모 데이터 처리에 유리하다. Fetch API를 사용하면 응답 본문을 스트림으로 처리할 수 있어 데이터를 점진적으로 읽고 처리할 수 있다.
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("Hello, "));
controller.enqueue(new TextEncoder().encode("world!"));
controller.close();
}
});
fetch('https://example.com/api', {
method: 'POST',
body: stream
}).then(response => {
console.log('Request completed');
});
fetch('https://example.com/largefile')
.then(response => {
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
function push() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
controller.enqueue(value);
push();
});
}
push();
}
});
})
.then(stream => new Response(stream))
.then(response => response.text())
.then(text => {
console.log(text);
});
Fetch 요청을 취소하기 위해 AbortController를 사용할 수 있다. 이는 긴 시간 동안 실행되는 요청을 중단하거나 사용자가 페이지를 이동할 때 사용된다.
const controller = new AbortController();
const signal = controller.signal;
fetch('https://example.com/longtask', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
});
// 요청을 취소하려면:
controller.abort();
Fetch API는 요청의 캐싱 동작을 제어할 수 있는 cache 옵션을 제공한다. 이를 통해 브라우저의 캐시 동작을 세밀하게 조정할 수 있다.
fetch('https://example.com/api', { cache: 'no-store' })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
Fetch API는 요청이 리디렉션을 어떻게 처리할지 제어할 수 있는 redirect 옵션을 제공한다. 이를 통해 리디렉션 동작을 세밀하게 제어할 수 있다.
fetch('https://example.com/api', { redirect: 'manual' })
.then(response => {
if (response.type === 'opaqueredirect') {
console.log('Redirect detected');
}
return response;
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
Fetch API의 Request와 Response 객체는 한 번만 소비될 수 있다. 하지만 clone() 메서드를 사용하면 동일한 데이터를 여러 번 처리할 수 있다.
fetch('https://example.com/api')
.then(response => {
const responseClone = response.clone();
response.json().then(data => console.log('Original response:', data));
responseClone.text().then(text => console.log('Cloned response:', text));
})
.catch(error => console.error('Fetch error:', error));
React Query는 데이터를 쉽게 페칭할 수 있도록 도와준다. useQuery 훅을 사용하면 간단하게 비동기 요청을 관리할 수 있다.
import { useQuery } from 'react-query';
// 서버에서 사용자 데이터를 가져오는 비동기 함수
const fetchUser = async () => {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
function UserProfile() {
// useQuery 훅을 사용하여 데이터 페칭
const { data, error, isLoading } = useQuery('user', fetchUser);
// 데이터 로딩 중일 때 표시할 내용
if (isLoading) {
return <span>Loading...</span>;
}
// 에러 발생 시 표시할 내용
if (error) {
return <span>Error: {error.message}</span>;
}
// 데이터 로드 완료 후 표시할 내용
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
export default UserProfile;
Mutation은 서버에 데이터를 변경하는 작업을 처리한다. 주로 POST, PUT, DELETE 요청에 사용된다.
import { useMutation, useQueryClient } from 'react-query';
function AddTodo() {
const queryClient = useQueryClient();
// useMutation 훅을 사용하여 데이터 변이 처리
const mutation = useMutation(newTodo => {
return fetch('/api/todo', {
method: 'POST',
body: JSON.stringify(newTodo),
});
}, {
onSuccess: () => {
// 변이가 성공하면 todos 쿼리를 무효화하여 다시 페칭
queryClient.invalidateQueries('todos');
},
});
return (
<button onClick={() => mutation.mutate({ id: Date.now(), text: 'New Todo' })}>
Add Todo
</button>
);
}
쿼리 무효화(Query Invalidation)는 특정 데이터를 갱신하기 위해 기존 데이터를 무효화할 때 사용된다. 이는 데이터가 변경된 후 자동으로 다시 페칭되도록 한다.
import { useQuery, useQueryClient } from 'react-query';
const fetchTodos = async () => {
const response = await fetch('/api/todos');
return response.json();
};
function TodoList() {
const queryClient = useQueryClient();
const { data: todos } = useQuery('todos', fetchTodos);
const handleAddTodo = async () => {
await fetch('/api/add-todo', { method: 'POST' });
// todos 쿼리를 무효화하여 다시 페칭
queryClient.invalidateQueries('todos');
};
return (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<button onClick={handleAddTodo}>Add Todo</button>
</div>
);
}
Optimistic updates는 서버 응답을 기다리지 않고 UI를 즉시 업데이트하는 기능, 사용자가 즉각적인 피드백을 받을 수 있도록 하여 사용자 경험을 향상시킨다.
import { useMutation, useQueryClient } from 'react-query';
function Todos() {
const queryClient = useQueryClient();
const mutation = useMutation(newTodo => {
return fetch('/api/todo', {
method: 'POST',
body: JSON.stringify(newTodo),
});
}, {
onMutate: async newTodo => {
await queryClient.cancelQueries('todos');
const previousTodos = queryClient.getQueryData('todos');
// 낙관적 업데이트로 UI를 즉시 업데이트
queryClient.setQueryData('todos', old => [...old, newTodo]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
// 에러 발생 시 이전 데이터로 롤백
queryClient.setQueryData('todos', context.previousTodos);
},
onSettled: () => {
// 변이 완료 후 todos 쿼리를 무효화하여 다시 페칭
queryClient.invalidateQueries('todos');
},
});
return (
<div>
<button onClick={() => mutation.mutate({ id: Date.now(), text: 'New Todo' })}>
Add Todo
</button>
</div>
);
}
React Query는 무한 스크롤 및 페이징을 지원한다. 이를 통해 사용자는 데이터를 부분적으로 로드하고, 스크롤이 끝에 도달할 때 추가 데이터를 로드할 수 있다.
import { useInfiniteQuery } from 'react-query';
const fetchProjects = async ({ pageParam = 0 }) => {
const response = await fetch(`/api/projects?cursor=${pageParam}`);
return response.json();
};
function Projects() {
const {
data,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
});
return (
<div>
{data.pages.map((page, i) => (
<Fragment key={i}>
{page.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage}
>
Load More
</button>
</div>
);
}
React Query는 데이터가 필요하기 전에 미리 데이터를 가져오는 기능을 제공한다. 이는 사용자가 특정 페이지로 이동하기 전에 데이터를 미리 로드하여 성능을 향상시킬 수 있다.
import { useQueryClient } from 'react-query';
function App() {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery('todos', fetchTodos);
};
return (
<div>
<button onMouseEnter={handleMouseEnter}>
Pre-fetch Todos
</button>
</div>
);
}
React Query는 기본적으로 여러 쿼리를 하나의 네트워크 요청으로 배치할 수 있다. 성능 최적화에 도움이 된다.
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
retry: false,
staleTime: 1000 * 60 * 5,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
);
}
React Query는 Fetch API를 사용하여 스트리밍 데이터를 처리할 수 있다. 이를 통해 대규모 데이터 처리에 유리한 스트리밍을 사용할 수 있다.
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("Hello, "));
controller.enqueue(new TextEncoder().encode("world!"));
controller.close();
}
});
fetch('https://example.com/api', {
method: 'POST',
body: stream
}).then(response => {
console.log('Request completed');
});
fetch('https://example.com/largefile')
.then(response => {
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
function push() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
controller.enqueue(value);
push();
});
}
push();
}
});
})
.then(stream => new Response(stream))
.then(response => response.text())
.then(text => {
console.log(text);
});
npm install axios-retry
import axios from 'axios';
import axiosRetry from 'axios-retry';
// axios-retry 설정
axiosRetry(axios, { retries: 3 });
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error fetching data:', error);
});
npm install axios-mock-adapter
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
// MockAdapter 설정
const mock = new MockAdapter(axios);
// 모킹된 응답 설정
mock.onGet('/api/data').reply(200, {
data: 'mocked data'
});
// 실제 요청 테스트
axios.get('/api/data')
.then(response => {
console.log(response.data); // 'mocked data'
})
.catch(error => {
console.error('Error fetching data:', error);
});
npm install axios-auth-refresh
import axios from 'axios';
// 기본 Axios 설정
const instance = axios.create({
baseURL: 'https://api.example.com',
headers: {
'Content-Type': 'application/json'
}
});
// 토큰 갱신 함수
const refreshAuthLogic = failedRequest => instance.post('/auth/refresh-token', {
token: localStorage.getItem('refreshToken')
}).then(tokenRefreshResponse => {
// 새로운 액세스 토큰을 로컬 저장소에 저장
localStorage.setItem('accessToken', tokenRefreshResponse.data.accessToken);
// 실패한 요청의 헤더를 갱신된 토큰으로 업데이트
failedRequest.response.config.headers['Authorization'] = 'Bearer ' + tokenRefreshResponse.data.accessToken;
return Promise.resolve();
});
// 요청 인터셉터
instance.interceptors.request.use(config => {
// 요청을 보내기 전에 로컬 저장소에서 액세스 토큰을 가져와 헤더에 추가
const token = localStorage.getItem('accessToken');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, error => {
return Promise.reject(error);
});
// 응답 인터셉터
instance.interceptors.response.use(response => {
return response;
}, error => {
const originalRequest = error.config;
// 401 에러 처리
if (error.response && error.response.status === 401) {
// 만료된 토큰이므로 리프레시 토큰으로 갱신 시도
return refreshAuthLogic(originalRequest)
.then(() => {
originalRequest.headers['Authorization'] = 'Bearer ' + localStorage.getItem('accessToken');
return axios(originalRequest);
});
}
return Promise.reject(error);
});
export default instance;
createAuthRefreshInterceptor가 401 응답을 감지하고 refreshAuthLogic 함수를 호출하여 토큰을 갱신import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
// 기본 Axios 설정
const instance = axios.create({
baseURL: 'https://api.example.com',
headers: {
'Content-Type': 'application/json'
}
});
// 토큰 갱신 함수
const refreshAuthLogic = failedRequest => instance.post('/auth/refresh-token', {
token: localStorage.getItem('refreshToken')
}).then(tokenRefreshResponse => {
// 새로운 액세스 토큰을 로컬 저장소에 저장
localStorage.setItem('accessToken', tokenRefreshResponse.data.accessToken);
// 실패한 요청의 헤더를 갱신된 토큰으로 업데이트
failedRequest.response.config.headers['Authorization'] = 'Bearer ' + tokenRefreshResponse.data.accessToken;
return Promise.resolve();
});
// 인터셉터 설정
createAuthRefreshInterceptor(instance, refreshAuthLogic);
// 요청 인터셉터
instance.interceptors.request.use(config => {
// 요청을 보내기 전에 로컬 저장소에서 액세스 토큰을 가져와 헤더에 추가
const token = localStorage.getItem('accessToken');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, error => {
return Promise.reject(error);
});
export default instance;
npm install axios-middleware
import axios from 'axios';
import { AxiosMiddleware, getDefaultMiddleware } from 'axios-middleware';
// Axios 인스턴스 생성
const client = axios.create();
const middleware = new AxiosMiddleware(client, getDefaultMiddleware());
// 미들웨어 추가
middleware.use({
onRequest(config) {
console.log('Sending request to', config.url);
return config;
},
onResponse(response) {
console.log('Received response from', response.config.url);
return response;
},
onError(error) {
console.error('Error:', error);
return Promise.reject(error);
}
});
// 요청 보내기
client.get('https://api.example.com/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error fetching data:', error);
});
HTTP 요청 라이브러리는 프론트엔드 개발자가 서버와의 통신을 효율적으로 관리할 수 있도록 돕는 필수 도구다. 블로그에서 다룬 Axios, Fetch API, React Query와 같은 라이브러리는 각각 고유의 장점과 기능을 제공하여 다양한 요구사항을 충족시킬 수 있다.
Axios는 다양한 기능과 강력한 인터셉터 시스템을 제공하고, 브라우저와 Node.js 환경 모두에서 사용할 수 있다.
Fetch API는 브라우저 내장 API로, 간단하고 가볍지만, 고급 기능이 부족할 수 있다.
React Query는 서버 상태 관리와 데이터 동기화에 특화된 강력한 라이브러리로, React 애플리케이션에서의 사용에 최적화되어 있다.
각 라이브러리의 장점과 단점을 이해하고, 프로젝트의 요구사항에 맞는 라이브러리를 선택하는 것이 중요하다. 이를 통해 더욱 효율적이고 유지보수하기 쉬운 애플리케이션을 개발할 수 있다. HTTP 요청 라이브러리를 적절히 활용하면 데이터 통신의 복잡성을 줄이고, 더 나은 사용자 경험을 제공할 수 있다.
Axios 공식 문서
Fetch API 문서 - MDN Web Docs
MDN Web Docs - HTTP Redirections
JavaScript Promises