
프론트엔드를 시작하면서 가장 처음 마주하는 벽 중 하나가 바로 API 통신이다. 화면에 데이터를 보여주려면 백엔드 서버에서 데이터를 가져와야 하는데, 이때 RESTful API를 사용하게 된다. 처음엔 GET, POST가 뭔지, 어떻게 데이터를 주고받아야 하는지 생소한 개념이 많아 막막할 수 있다.
이에 본 포스트에서는 RESTful API의 기본 개념부터 실무에서 사용하는 라이브러리와 도구들까지 차근차근 알아보고자 한다.
REST는 Representational State Transfer의 줄임말로, 웹에서 자원을 효율적으로 주고받기 위한 아키텍처 스타일이다. 2000년 Roy Fielding이 제안한 개념으로, 현재 웹에서 가장 널리 사용되는 API 설계 방식이다.
참고: REST는 GraphQL, SOAP, gRPC 등 다양한 API 아키텍처 스타일 중에서도 가장 많이 사용된다.
RESTful API는 이런 REST 원칙을 따라 만든 웹 API를 의미한다. 간단히 말해서 프론트엔드가 백엔드와 데이터를 주고받는 표준화된 방법이라고 생각하면 된다.
RESTful API는 REST 원칙을 따르는 API로, 다음과 같은 특징을 가진다:
// RESTful API 예시
// 사용자 목록 조회
GET /api/users
// 특정 사용자 조회
GET /api/users/123
// 새 사용자 생성
POST /api/users
// 사용자 수정
PUT /api/users/123
// 사용자 삭제
DELETE /api/users/123
실제로는 완벽한 REST를 구현하기 어려워 대부분 "REST-like" 또는 "RESTful"이라고 표현한다.
API(Application Programming Interface)는 두 애플리케이션이 서로 소통할 수 있게 해주는 인터페이스다. 웹 개발에서는 다음과 같이 동작한다:
RESTful API의 핵심은 무상태성(Stateless)에 있다. 서버는 이전 요청을 기억하지 않으며, 모든 요청은 필요한 정보를 스스로 포함해야 한다. 예를 들어 인증이 필요한 요청이라면 매번 토큰을 함께 보내야 한다.
즉, 각 요청은 독립적이며 서버는 클라이언트의 상태를 저장하지 않는다.
이런 설계 덕분에 서버의 확장성이 높아지고, 클라이언트와 서버 간 의존성이 줄어든다.
클라이언트와 서버가 독립적으로 진화할 수 있도록 분리된 구조다.
// 클라이언트 (ex. React 앱): 요청(request) 담당
const fetchUsers = async () => {
const response = await fetch('/api/users');
return response.json();
};
// 서버는 독립적으로 API만 제공: 응답(response) 담당
서버는 클라이언트의 상태 정보를 저장하지 않는다. 모든 요청에 필요한 정보가 포함되어야 한다.
// 잘못된 예 - 서버가 상태를 기억해야 함
GET /api/users/next // 이전 요청을 기억해야 함
// 올바른 예 - 모든 정보가 요청에 포함
GET /api/users?page=2&limit=10
요청을 통해 보내는 자료들은 캐싱이 가능해야 하며, 응답은 캐시 가능 여부를 명시해야 한다.
// HTTP 헤더로 캐싱 제어
fetch('/api/users', {
headers: {
'Cache-Control': 'max-age=3600' // 1시간 캐싱
}
});
클라이언트는 서버의 내부 구조를 알 필요가 없다.
클라이언트 → 로드밸런서 → API 게이트웨이 → 서버
참고: 로드밸런서
동일한 자원에 대해서는 하나의 URI만 사용한다.
// 일관된 인터페이스 예시
GET /api/users // 사용자 목록
POST /api/users // 사용자 생성
GET /api/users/123 // 특정 사용자 조회
PUT /api/users/123 // 특정 사용자 전체 수정
DELETE /api/users/123 // 특정 사용자 삭제
/users)URI는 명사를 사용하고, 동사는 HTTP 메서드가 담당한다. 예를 들어 DELETE /deleteUser/1 대신 DELETE /users/1처럼 설계한다.
서버가 클라이언트에게 실행 가능한 코드를 전송할 수 있다. (JavaScript 등)
GET /users → 전체 사용자 목록 조회
GET /users/1 → ID가 1인 사용자 정보 조회
POST /users → 새로운 사용자 생성
PUT /users/1 → ID가 1인 사용자 정보 전체 교체
PATCH /users/1 → ID가 1인 사용자 정보 일부 수정
DELETE /users/1 → ID가 1인 사용자 삭제
API 응답에는 상태 코드가 포함되어 요청 결과를 알 수 있다. 아래는 자주 쓰이는 상태 코드들이다:
프론트엔드에서 다루는 상태는 크게 두 가지로 나뉜다:
서버 상태는 클라이언트에서 직접 제어할 수 없고, 언제든 변할 수 있다. 이를 효율적으로 관리하기 위해 전용 라이브러리를 사용한다.
기본적으로는 fetch나 axios를 사용해 API를 호출할 수 있다:
// 기본 fetch 사용 예시
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
하지만 이런 방식은 다음과 같은 문제가 있다:
TanStack Query는 서버 상태 관리를 위한 가장 인기 있는 라이브러리다. 기존의 Redux나 Context API가 클라이언트 상태를 관리한다면, React Query는 서버에서 가져온 데이터의 상태를 관리한다.
TanStack Query를 통해 데이터 fetching, 캐싱, 동기화, 업데이트를 간편하게 처리할 수 있다.
npm install @tanstack/react-query
// App.js - React Query 설정 상세 설명
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
// QueryClient 생성 - 모든 쿼리의 설정과 캐시를 관리
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// staleTime: 데이터가 "신선한" 상태로 유지되는 시간
// 이 시간 내에는 다시 요청하지 않음 (기본값: 0)
staleTime: 1000 * 60 * 5, // 5분
// cacheTime: 컴포넌트가 언마운트된 후 캐시를 유지하는 시간
// 이 시간이 지나면 가비지 컬렉션됨 (기본값: 5분)
cacheTime: 1000 * 60 * 10, // 10분
// retry: 요청 실패 시 재시도 횟수 (기본값: 3)
retry: 1,
// refetchOnWindowFocus: 윈도우가 포커스될 때 자동 새로고침 여부
refetchOnWindowFocus: false,
// refetchOnReconnect: 네트워크 재연결 시 자동 새로고침 여부
refetchOnReconnect: true,
// refetchInterval: 주기적 새로고침 간격 (밀리초, false면 비활성화)
refetchInterval: false,
},
mutations: {
// 뮤테이션 실패 시 재시도 횟수
retry: 1,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
{/* 개발 도구 - 쿼리 상태를 시각적으로 확인 가능 */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
useQuery가 하는 일:
1. 컴포넌트가 마운트되면 자동으로 데이터 요청
2. 같은 쿼리 키를 가진 다른 컴포넌트들과 데이터 공유
3. 백그라운드에서 데이터 업데이트 확인
4. 에러 발생 시 자동 재시도
5. 로딩, 에러, 성공 상태 자동 관리
import { useQuery } from '@tanstack/react-query';
// API 함수 - 실제 서버 요청을 담당
const fetchUsers = async ({ queryKey }) => {
// queryKey는 useQuery의 첫 번째 인자로 전달된 배열
const [_key, params] = queryKey;
const url = new URL('/api/users', window.location.origin);
if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value); // value는 string이어야
});
}
const response = await fetch(url);
// 응답이 실패했을 때 에러를 던져야 React Query가 인식함
if (!response.ok) {
throw new Error(`HTTP ${response.status}: 사용자 목록을 불러올 수 없습니다`);
}
return response.json();
};
// 컴포넌트에서 사용
function UserList() {
const {
// 서버에서 받은 실제 데이터
data: users,
// 현재 로딩 중인지 여부 (최초 로딩)
isLoading,
// 백그라운드에서 새로고침 중인지 여부
isFetching,
// 에러 객체 (에러가 없으면 null)
error,
// 에러가 발생했는지 여부
isError,
// 데이터가 성공적으로 로드되었는지 여부
isSuccess,
// 수동으로 다시 요청하는 함수
refetch,
// 쿼리가 비활성화되었는지 여부
isEnabled,
// 데이터가 "stale"(오래된) 상태인지 여부
isStale,
} = useQuery({
// 쿼리 키 - 캐싱과 식별을 위한 고유한 키
// 배열 형태로, 종속성이 있으면 배열에 포함
queryKey: ['users', { page: 1, limit: 10 }],
// 실제 데이터를 가져오는 함수
queryFn: fetchUsers,
// 이 쿼리만의 설정 (전역 설정을 덮어씀)
staleTime: 1000 * 60 * 5, // 5분간 신선함 유지
cacheTime: 1000 * 60 * 10, // 10분간 캐시 유지
// 쿼리 실행 조건 (false면 실행하지 않음)
enabled: true,
// 에러 발생 시 재시도 조건
retry: (failureCount, error) => {
// 404 에러는 재시도하지 않음
if (error.message.includes('404')) return false;
// 3번까지만 재시도
return failureCount < 3;
},
// 재시도 간격 (기본값: 1초, 2초, 4초...)
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
});
// 로딩 상태 처리
if (isLoading) {
return (
<div className="loading">
<div>사용자 목록을 불러오는 중...</div>
<div>잠시만 기다려주세요</div>
</div>
);
}
// 에러 상태 처리
if (isError) {
return (
<div className="error">
<h3>오류가 발생했습니다</h3>
<p>{error.message}</p>
<button onClick={() => refetch()}>다시 시도</button>
</div>
);
}
// 성공 상태 - 데이터 렌더링
return (
<div>
<div className="header">
<h2>사용자 목록 ({users?.length || 0}명)</h2>
<button
onClick={() => refetch()}
disabled={isFetching}
>
{isFetching ? '새로고침 중...' : '새로고침'}
</button>
{isStale && <span>⚠️ 오래된 데이터를 조회하고 있습니다</span>}
</div>
<div className="user-list">
{users?.map(user => (
<div key={user.id} className="user-item">
<h4>{user.name}</h4>
<p>{user.email}</p>
<span className="role">{user.role}</span>
</div>
))}
</div>
</div>
);
}
<ReactQueryDevtools/>): 쿼리 상태를 시각적으로 확인 가능SWR(Stale-While-Revalidate)은 Vercel에서 만든 가벼운 데이터 페칭 라이브러리다.
이름에서 알 수 있듯, 캐시된 데이터를 먼저 보여주고(showing stale data―), 백그라운드에서 최신 데이터를 가져와 갱신(―while revalidating)하는 전략을 사용한다.
SWR의 핵심 철학:
1. 빠른 응답: 캐시된 데이터를 즉시 보여줌
2. 최신성 보장: 백그라운드에서 서버와 동기화
3. 단순함: 최소한의 설정으로 강력한 기능 제공
4. 자동화: 포커스, 네트워크 재연결 시 자동 업데이트
npm install swr
import useSWR from 'swr';
// fetcher 함수 - SWR에게 "어떻게 데이터를 가져올지" 알려주는 함수
const fetcher = (url) => fetch(url).then(res => {
if (!res.ok) {
throw new Error('데이터를 불러오는데 실패했습니다');
}
return res.json();
});
function UserList() {
// SWR의 기본 사용법
const {
data, // 서버에서 받은 데이터
error, // 에러 객체
isLoading, // 최초 로딩 중인지 (data와 error가 모두 undefined)
isValidating, // 백그라운드에서 재검증 중인지
mutate // 수동으로 데이터 갱신하는 함수
} = useSWR('/api/users', fetcher);
// 로딩 상태
if (isLoading) return <div>사용자 목록을 불러오는 중...</div>;
// 에러 상태
if (error) return (
<div>
<p>에러가 발생했습니다: {error.message}</p>
<button onClick={() => mutate()}>다시 시도</button>
</div>
);
return (
<div>
<div className="header">
<h2>사용자 목록</h2>
{/* 백그라운드 업데이트 상태 표시 */}
{isValidating && <span>🔄 업데이트 중...</span>}
<button onClick={() => mutate()}>새로고침</button>
</div>
<div className="user-list">
{data?.map(user => (
<div key={user.id} className="user-item">
<h4>{user.name}</h4>
<p>{user.email}</p>
<span className="role">{user.role}</span>
</div>
))}
</div>
</div>
);
}
| 특징 | TanStack Query | SWR |
|---|---|---|
| 초기 설정 | Provider 필요 | 바로 사용 가능 |
| 기능 | 풍부함 (Mutation, Pagination 등) | 단순함 |
| 번들 크기 | 상대적으로 큼 | 가벼움 |
| 학습 곡선 | 조금 더 복잡 | 쉬움 |
간단한 데이터 조회 위주라면 SWR, 복잡한 상태 관리가 필요하다면 TanStack Query를 선택하는 것이 좋다.
Swagger UI는 RESTful API를 문서화하고 테스트할 수 있는 도구다. 백엔드 개발자가 Swagger를 적용하면, API 명세서가 자동으로 생성되고 웹 UI에서 인터랙티브한 문서를 확인할 수 있다.
/swagger-ui.html)예를 들어 POST /users API를 테스트하고 싶다면:
1. 해당 섹션 클릭
2. "Try it out" 버튼 클릭
3. 요청 본문에 JSON 데이터 입력
4. "Execute" 버튼으로 실행
5. 응답 결과 확인
이를 통해 프론트엔드 코드 작성 전에 API 동작을 미리 확인할 수 있다.
Postman은 API 개발을 위한 협업 플랫폼이다. API 테스트, 문서화, 모니터링, 모킹 등 API 개발 생명주기 전반을 지원한다. HTTP 요청을 쉽게 만들어 보내고 응답을 확인할 수 있어 테스팅에 상당히 유용하다.
GET 요청 - 사용자 목록 조회
Method: GET
URL: https://api.example.com/users
Headers:
Authorization: Bearer your_token_here
POST 요청 - 새 사용자 생성
Method: POST
URL: https://api.example.com/users
Headers:
Content-Type: application/json
Authorization: Bearer your_token_here
Body (raw JSON):
{
"name": "홍길동",
"email": "hong@example.com"
}
RESTful API는 현대 웹 개발에서 핵심적인 역할을 한다. 기본 원칙을 이해하고, 적절한 도구와 라이브러리를 활용하면 효율적인 개발이 가능하다.
학습 및 실습 포인트 정리 for FE devs:
1. REST 기본 개념과 HTTP 메서드 이해
2. TanStack Query나 SWR로 데이터 페칭 경험 (프로젝트에 적합한 도구로 선택해야 함)
3. Swagger 문서 읽고 다루는 법 익히기
4. Postman으로 API 테스트 습관화
실제 프로젝트에서는 이 모든 개념들이 유기적으로 연결된다. Swagger 문서를 보고 API를 이해한 후, 적절한 데이터 페칭 라이브러리를 선택하여 구현하고, Postman으로 테스트하는 것이 일반적인 워크플로우다.
특히 팀 협업에서는 API 명세서 공유, 테스트 케이스 문서화, 에러 상황 대응 등이 매우 중요하므로, 도구들을 단순히 '사용'하는 것을 넘어서 팀 전체의 개발 효율성을 높이는 방향으로 '활용'해야 한다.
처음엔 당연히 복잡해 보이지만, 직접 API를 호출해보고, 에러를 경험하고, 해결하는 과정을 거쳐 하나씩 실습하다보면 자연스럽게 손에 익으리라 생각한다.
REST API를 제대로 이해하고 활용할 수 있다면, 어떤 백엔드와도 효과적으로 소통할 수 있는 프론트엔드 개발자가 될 수 있을 것이다.😎
멋진 글 잘 보고 갑니다.