프론트엔드 개발자를 위한 RESTful API 핵심 가이드: 원칙부터 라이브러리&도구까지

Makee Ham·2025년 7월 27일
post-thumbnail

들어가며

프론트엔드를 시작하면서 가장 처음 마주하는 벽 중 하나가 바로 API 통신이다. 화면에 데이터를 보여주려면 백엔드 서버에서 데이터를 가져와야 하는데, 이때 RESTful API를 사용하게 된다. 처음엔 GET, POST가 뭔지, 어떻게 데이터를 주고받아야 하는지 생소한 개념이 많아 막막할 수 있다.

이에 본 포스트에서는 RESTful API의 기본 개념부터 실무에서 사용하는 라이브러리와 도구들까지 차근차근 알아보고자 한다.


1. 용어 정리: RESTful API란?

REST의 정의

RESTRepresentational State Transfer의 줄임말로, 웹에서 자원을 효율적으로 주고받기 위한 아키텍처 스타일이다. 2000년 Roy Fielding이 제안한 개념으로, 현재 웹에서 가장 널리 사용되는 API 설계 방식이다.
참고: REST는 GraphQL, SOAP, gRPC 등 다양한 API 아키텍처 스타일 중에서도 가장 많이 사용된다.

REST의 핵심 개념

  • 자원(Resource): 서버에 있는 데이터나 기능
  • 표현(Representation): 자원의 현재 상태를 나타내는 형태 (주로 JSON)
  • 상태 전이(State Transfer): 클라이언트가 서버의 자원 상태를 변경하는 것

RESTful API의 정의

RESTful API는 이런 REST 원칙을 따라 만든 웹 API를 의미한다. 간단히 말해서 프론트엔드가 백엔드와 데이터를 주고받는 표준화된 방법이라고 생각하면 된다.

RESTful API의 특징

RESTful API는 REST 원칙을 따르는 API로, 다음과 같은 특징을 가진다:

  1. 단순함: HTTP 메서드(POST, PUT, DELETE, GET, PATCH 등)만으로 모든 작업 표현
  2. 무상태성: 각 요청이 독립적이며 서버가 클라이언트 상태를 저장하지 않음
  3. 일관성: 동일한 패턴으로 API 설계
  4. 확장성: 캐싱과 계층화가 용이함
// RESTful API 예시
// 사용자 목록 조회
GET /api/users

// 특정 사용자 조회  
GET /api/users/123

// 새 사용자 생성
POST /api/users

// 사용자 수정
PUT /api/users/123

// 사용자 삭제
DELETE /api/users/123

REST vs RESTful의 차이

  • REST: 아키텍처 원칙 자체
  • RESTful: REST 원칙을 잘 따르고 있는 시스템

실제로는 완벽한 REST를 구현하기 어려워 대부분 "REST-like" 또는 "RESTful"이라고 표현한다.

API의 역할

API(Application Programming Interface)는 두 애플리케이션이 서로 소통할 수 있게 해주는 인터페이스다. 웹 개발에서는 다음과 같이 동작한다:

  1. 클라이언트(프론트엔드)가 HTTP 요청을 보냄
  2. 서버(백엔드)가 요청을 처리하고 HTTP 응답을 보냄
  3. 주고받는 데이터는 주로 JSON 형태

핵심 특징

RESTful API의 핵심은 무상태성(Stateless)에 있다. 서버는 이전 요청을 기억하지 않으며, 모든 요청은 필요한 정보를 스스로 포함해야 한다. 예를 들어 인증이 필요한 요청이라면 매번 토큰을 함께 보내야 한다.
즉, 각 요청은 독립적이며 서버는 클라이언트의 상태를 저장하지 않는다.

이런 설계 덕분에 서버의 확장성이 높아지고, 클라이언트와 서버 간 의존성이 줄어든다.


2. REST 원칙과 HTTP 메서드 활용

REST의 6가지 핵심 원칙

1. 클라이언트-서버 구조

클라이언트와 서버가 독립적으로 진화할 수 있도록 분리된 구조다.

// 클라이언트 (ex. React 앱): 요청(request) 담당
const fetchUsers = async () => {
  const response = await fetch('/api/users');
  return response.json();
};

// 서버는 독립적으로 API만 제공: 응답(response) 담당

2. 무상태성(Stateless)

서버는 클라이언트의 상태 정보를 저장하지 않는다. 모든 요청에 필요한 정보가 포함되어야 한다.

// 잘못된 예 - 서버가 상태를 기억해야 함
GET /api/users/next  // 이전 요청을 기억해야 함

// 올바른 예 - 모든 정보가 요청에 포함
GET /api/users?page=2&limit=10

3. 캐시 가능성(Cacheable)

요청을 통해 보내는 자료들은 캐싱이 가능해야 하며, 응답은 캐시 가능 여부를 명시해야 한다.

// HTTP 헤더로 캐싱 제어
fetch('/api/users', {
  headers: {
    'Cache-Control': 'max-age=3600'  // 1시간 캐싱
  }
});

4. 계층화 시스템(Layered System)

클라이언트는 서버의 내부 구조를 알 필요가 없다.

클라이언트 → 로드밸런서 → API 게이트웨이 → 서버

참고: 로드밸런서

5. 일관된 인터페이스(Uniform Interface)

동일한 자원에 대해서는 하나의 URI만 사용한다.

// 일관된 인터페이스 예시
GET    /api/users      // 사용자 목록
POST   /api/users      // 사용자 생성
GET    /api/users/123  // 특정 사용자 조회
PUT    /api/users/123  // 특정 사용자 전체 수정
DELETE /api/users/123  // 특정 사용자 삭제
  • 자원(Resource): URI로 식별함 + 명사 형태 (예: /users)
  • 행위(Action): HTTP 메서드로 표현함 + 동사 형태 (예: GET, POST)

URI는 명사를 사용하고, 동사는 HTTP 메서드가 담당한다. 예를 들어 DELETE /deleteUser/1 대신 DELETE /users/1처럼 설계한다.

6. 코드 온 디맨드 (선택적)

서버가 클라이언트에게 실행 가능한 코드를 전송할 수 있다. (JavaScript 등)

HTTP 메서드별 역할

GET - 조회

GET /users        → 전체 사용자 목록 조회
GET /users/1      → ID가 1인 사용자 정보 조회
  • 데이터를 가져올 때(조회할 때) 사용
  • 서버 상태를 변경하지 않음
  • 브라우저에 캐시 가능

POST - 생성

POST /users       → 새로운 사용자 생성
  • 새로운 리소스를 만들 때 사용
  • 요청 본문(body)에 생성할 데이터를 포함

PUT - 전체 수정

PUT /users/1      → ID가 1인 사용자 정보 전체 교체
  • 기존 리소스를 완전히 교체
  • 모든 필드를 새로운 값으로 덮어씀 (∴ 모든 필드를 포함해야 함)

PATCH - 부분 수정

PATCH /users/1    → ID가 1인 사용자 정보 일부 수정
  • 리소스의 일부만 변경
  • 예: 이메일만 수정하고 싶을 때

DELETE - 삭제

DELETE /users/1   → ID가 1인 사용자 삭제
  • 리소스를 삭제할 때 사용
  • 삭제 성공 후 일반적으로 response.status는 204 No Content 반환

HTTP 상태 코드

API 응답에는 상태 코드가 포함되어 요청 결과를 알 수 있다. 아래는 자주 쓰이는 상태 코드들이다:

2xx 성공

  • 200 OK: 요청 성공
  • 201 Created: 자원 생성 성공
  • 204 No Content: 성공했지만 반환할 내용 없음

4xx 클라이언트 오류

  • 400 Bad Request: 잘못된 요청
  • 401 Unauthorized: 인증 필요
  • 403 Forbidden: 권한 없음
  • 404 Not Found: 자원을 찾을 수 없음

5xx 서버 오류

  • 500 Internal Server Error: 서버 내부 오류
  • 502 Bad Gateway: 게이트웨이 오류
  • 503 Service Unavailable: 서비스 이용 불가

3. 데이터 페칭 라이브러리 활용법

서버 상태 관리의 필요성

프론트엔드에서 다루는 상태는 크게 두 가지로 나뉜다:

  • 클라이언트 상태: 프론트엔드에서 직접 제어하는 상태 (UI 상태, 폼 입력값 등)
  • 서버 상태: 백엔드에서 가져오는 데이터 (사용자 정보, 게시글 목록 등)

서버 상태는 클라이언트에서 직접 제어할 수 없고, 언제든 변할 수 있다. 이를 효율적으로 관리하기 위해 전용 라이브러리를 사용한다.

기존 방식의 문제점

기본적으로는 fetchaxios를 사용해 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);
    });
}, []);

하지만 이런 방식은 다음과 같은 문제가 있다:

  • 매번 로딩/에러 상태를 관리해야 함
  • 캐싱 기능이 없어 같은 데이터를 중복 요청함
  • 데이터 동기화(백그라운드 상의 데이터 업데이트)가 어려움
  • 에러 재시도 로직(에러 핸들링) 직접 구현해야 함

데이터 페칭 라이브러리가 해결하는 문제들

  1. 캐싱: 같은 데이터를 여러 컴포넌트에서 사용해도 한 번만 요청
  2. 백그라운드 업데이트: 사용자의 별도 조작 없이도 데이터를 최신으로 유지
  3. 중복 요청 제거: 동일한 요청이 동시에 여러 번 발생하지 않도록 방지
  4. 에러 핸들링: 자동 재시도, 에러 상태 관리
  5. 로딩 상태: 복잡한 로딩 상태를 간단하게 관리
  6. 메모리 관리: 사용하지 않는 데이터는 자동으로 정리
  7. Optimistic Updates: 서버 응답을 기다리지 않고 UI를 먼저 업데이트

TanStack Query (구 React Query)

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

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>
  );
}

주요 기능

  • 자동 캐싱: 같은 queryKey로 여러 번 호출해도 한 번만 요청이 들어감
  • 백그라운드 업데이트: 화면에 데이터를 보여주면서 뒤에서 최신 데이터로 갱신
  • Mutation 지원: POST, PUT, DELETE 같은 서버 리소스 변경 작업 처리
  • 개발자 도구 (<ReactQueryDevtools/>): 쿼리 상태를 시각적으로 확인 가능

SWR

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 vs SWR

특징TanStack QuerySWR
초기 설정Provider 필요바로 사용 가능
기능풍부함 (Mutation, Pagination 등)단순함
번들 크기상대적으로 큼가벼움
학습 곡선조금 더 복잡쉬움

간단한 데이터 조회 위주라면 SWR, 복잡한 상태 관리가 필요하다면 TanStack Query를 선택하는 것이 좋다.


4. 도구 활용 방법

Swagger UI (OpenAPI)

Swagger UI는 RESTful API를 문서화하고 테스트할 수 있는 도구다. 백엔드 개발자가 Swagger를 적용하면, API 명세서가 자동으로 생성되고 웹 UI에서 인터랙티브한 문서를 확인할 수 있다.

주요 기능

  • API 문서 자동 생성: 엔드포인트, 파라미터, 응답 스키마 등을 한눈에 확인
  • Try it out: 브라우저에서 직접 API 호출 테스트 가능
  • 스키마 확인: 요청/응답 데이터 구조를 명확히 파악

활용 방법

  1. 백엔드에서 제공하는 Swagger UI URL 접속 (보통 /swagger-ui.html)
  2. 필요한 API 엔드포인트 찾기
  3. 파라미터와 예시 확인
  4. "Try it out" 버튼으로 직접 테스트

예를 들어 POST /users API를 테스트하고 싶다면:
1. 해당 섹션 클릭
2. "Try it out" 버튼 클릭
3. 요청 본문에 JSON 데이터 입력
4. "Execute" 버튼으로 실행
5. 응답 결과 확인

이를 통해 프론트엔드 코드 작성 전에 API 동작을 미리 확인할 수 있다.

Postman

Postman은 API 개발을 위한 협업 플랫폼이다. API 테스트, 문서화, 모니터링, 모킹 등 API 개발 생명주기 전반을 지원한다. HTTP 요청을 쉽게 만들어 보내고 응답을 확인할 수 있어 테스팅에 상당히 유용하다.

기본 사용법

  1. 메서드와 URL 설정: GET, POST 등 선택하고 API URL 입력
  2. 헤더 설정: Authorization, Content-Type 등 필요한 헤더 추가
  3. 본문 설정: POST나 PUT 요청시 JSON 데이터 입력
  4. Send 클릭: 요청 보내고 응답 확인

실제 사용 예시

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"
}

유용한 기능

  • API 테스트: 다양한 HTTP 요청 테스트
  • 컬렉션: 관련된 API들을 폴더별로 묶어서 관리(연관 API 요청들을 그룹화)
  • 환경 변수: 개발/스테이징/프로덕션 환경별로 다른 URL 설정 가능
  • 코드 생성: 테스트한 요청을 JavaScript, Python 등 코드로 변환
  • 자동화: 테스트 스크립트와 워크플로우 자동화
  • 팀 공유: 컬렉션을 팀원들과 공유, 협업 용이
  • 문서화: 자동 API 문서 생성

프론트엔드 개발에서의 활용

  1. API 동작 확인: 백엔드 API가 제대로 작동하는지 먼저 테스트
  2. 에러 디버깅: 프론트엔드에서 문제가 생겼을 때 API 자체의 문제인지 확인
  3. 코드 생성: Postman에서 테스트한 요청을 fetch나 axios 코드로 변환

나가며

RESTful API는 현대 웹 개발에서 핵심적인 역할을 한다. 기본 원칙을 이해하고, 적절한 도구와 라이브러리를 활용하면 효율적인 개발이 가능하다.

학습 및 실습 포인트 정리 for FE devs:
1. REST 기본 개념과 HTTP 메서드 이해
2. TanStack Query나 SWR로 데이터 페칭 경험 (프로젝트에 적합한 도구로 선택해야 함)
3. Swagger 문서 읽고 다루는 법 익히기
4. Postman으로 API 테스트 습관화

실제 프로젝트에서는 이 모든 개념들이 유기적으로 연결된다. Swagger 문서를 보고 API를 이해한 후, 적절한 데이터 페칭 라이브러리를 선택하여 구현하고, Postman으로 테스트하는 것이 일반적인 워크플로우다.
특히 팀 협업에서는 API 명세서 공유, 테스트 케이스 문서화, 에러 상황 대응 등이 매우 중요하므로, 도구들을 단순히 '사용'하는 것을 넘어서 팀 전체의 개발 효율성을 높이는 방향으로 '활용'해야 한다.

처음엔 당연히 복잡해 보이지만, 직접 API를 호출해보고, 에러를 경험하고, 해결하는 과정을 거쳐 하나씩 실습하다보면 자연스럽게 손에 익으리라 생각한다.

REST API를 제대로 이해하고 활용할 수 있다면, 어떤 백엔드와도 효과적으로 소통할 수 있는 프론트엔드 개발자가 될 수 있을 것이다.😎

profile
사랑! 용기! 희망!

2개의 댓글

comment-user-thumbnail
2025년 7월 27일

멋진 글 잘 보고 갑니다.

1개의 답글