카테고리 정보를 불러오는 중입니다.

[Next.js] Next.js 에서 fetch 공통 API 설정하기

#next#next.js#fetch#넥스트#프론트엔드#api통신
← Next.js 목록

Next.js에서 fetch로 공통 API 설정하기

React에서 Axios로 API를 호출할 때는 코드가 짧았는데, Next.js 프로젝트에서 fetch로 작성한 설정을 보면 React에서와는 다르게 조금 손이 많이 가는 것 처럼 보입니다.

사실 Next.js에서도 Axios를 사용할 수 있습니다. fetch를 선택하면 응답을 읽고 HTTP 오류를 확인하는 부분을 직접 작성하게 됩니다.

 

1. 공통 API 설정이 필요한 이유

화면마다 API 주소를 직접 적으면 서버 주소가 바뀔 때 여러 파일을 수정해야 합니다. 요청할 때마다 성공 여부를 확인하고 JSON으로 변환하는 코드도 반복됩니다. 이 부분을 공통 함수로 만들어 두면 사용하는 쪽에서는 API 경로와 요청 옵션만 넘기면 됩니다.

2. 기본 코드 작성하기

예를 들어 src/lib/api.ts 파일을 만들고 아래 코드를 넣습니다. 파일 위치는 프로젝트 구조에 맞게 정하면 됩니다.

// 환경변수의 API 주소 사용.
// 설정이 없으면 예제의 로컬 주소 사용.
const configuredApiUrl =
  process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000/api";

// 주소 끝의 / 제거.
// /api/ + /posts가 /api//posts로 합쳐지는 것을 방지.
export const API_URL = configuredApiUrl.replace(/\/+$/, "");

// 여러 화면에서 함께 사용하는 API 요청 함수.
// T: 받을 데이터의 타입.
// path: API 경로.
// options: 요청 방식, 헤더, 본문 등.
export async function apiFetch<T>(
  path: string,
  options: RequestInit = {},
): Promise<T> {
  // 기본 주소와 경로를 합쳐 요청.
  const response = await fetch(`${API_URL}${path}`, options);

  // 상태 코드가 200~299가 아니면 오류 발생.
  if (!response.ok) {
    throw new Error(`요청 실패 (${response.status})`);
  }

  // 응답 본문을 JSON으로 읽어 반환.
  // as T는 타입 표시이며, 실제 데이터 검사는 하지 않음.
  return (await response.json()) as T;
}

이 함수는 JSON을 반환하는 API에 사용하는 예제입니다. 로그인이나 특정 서비스의 처리 없이, 주소 설정과 요청·응답 처리만 담았습니다.

3. API 주소를 정하는 부분

const configuredApiUrl =
  process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api';

NEXT_PUBLIC_API_URL이 설정되어 있으면 그 주소를 사용합니다. 값이 없으면 예제의 기본 주소인 http://localhost:4000/api를 사용합니다. 포트 4000과 /api 경로는 예시이므로 실제 API 서버 주소에 맞춰 주세요.

??는 왼쪽 값이 null이나 undefined일 때 오른쪽 값을 선택하는 문법입니다. 빈 문자열은 그대로 사용하므로, 환경변수는 비워 두기보다 사용할 주소를 정확하게 지정하는 편이 좋습니다.

Next.js에서 NEXT_PUBLIC_ 접두사는 브라우저에서도 사용할 환경변수에 붙입니다. 해당 값은 브라우저용 코드에 포함되고 일반적으로 빌드할 때 확정됩니다. 배포 후 API 주소를 바꿨다면 브라우저에 전달할 코드도 다시 빌드해야 합니다. Next.js 환경변수 문서

export const API_URL = configuredApiUrl.replace(/\/+$/, '');

이 줄은 API 주소 끝의 슬래시를 제거합니다. 기본 주소가 /api/로 끝나고 요청 경로가 /posts로 시작하면 중간에 슬래시가 두 개 들어가므로, 주소를 미리 정리해 두는 것입니다. 이 예제에서는 요청 경로를 /로 시작하도록 작성합니다.

4. apiFetch 함수는 어떤 일을 할까?

export async function apiFetch<T>(
  path: string,
  options: RequestInit = {},
): Promise<T>

path는 /posts처럼 호출할 API 경로입니다. options는 요청 방식, 헤더, 본문 등을 받습니다. RequestInit은 fetch 옵션을 나타내는 타입이고, = {}는 옵션을 생략하면 빈 객체를 사용한다는 뜻입니다.

<T>는 응답 데이터의 타입을 호출할 때 지정하기 위한 문법입니다. Promise<T>는 비동기 요청이 끝나면 T 형태의 결과를 받는다는 의미입니다. 아래 사용 예제를 보면 조금 더 쉽게 이해할 수 있습니다.

const response = await fetch(`${API_URL}${path}`, options);

기본 주소와 경로를 합쳐 요청합니다. await는 응답 객체가 준비될 때까지 기다립니다. 요청 방식을 따로 지정하지 않으면 GET 요청입니다.

if (!response.ok) {
  throw new Error(`요청 실패 (${response.status})`);
}

response.ok는 응답 상태 코드가 200~299이면 true입니다. fetch는 404나 500 응답을 받았다고 자동으로 오류를 던지지 않으므로, 여기서 실패 여부를 확인합니다. 네트워크 연결처럼 요청 자체에 문제가 생기면 fetch 단계에서 오류가 발생합니다. MDN Response.ok · MDN fetch 설명

return (await response.json()) as T;

응답 본문을 JSON으로 읽고 결과를 반환합니다. 마지막의 as T는 TypeScript에 결과 타입을 알려 줍니다. 서버가 실제로 그 형태의 데이터를 보냈는지 검사하는 기능은 아닙니다. MDN Response.json · TypeScript 타입 단언 설명

5. 다른 파일에서 사용하기

글 목록을 조회한다고 가정해 보겠습니다. 아래 경로와 데이터 구조는 사용법을 보여 주는 예시입니다.

// 다른 파일에 작성된 공통 함수 가져오기.
import { apiFetch } from "./api";

// 글 한 개의 데이터 형태.
type Post = {
  id: number;
  title: string;
};

// /posts에 GET 요청.
// Post[]: 글 객체의 배열을 받을 것으로 지정.
// method를 생략하면 기본값은 GET.
const posts = await apiFetch<Post[]>("/posts");

Post[]는 Post 객체의 배열이라는 뜻입니다. API 주소를 합치거나 response.json을 호출하는 부분은 공통 함수에서 처리하므로, 여기서는 경로와 결과 타입만 지정합니다. import 경로는 파일 위치에 맞춰 변경해 주세요.

POST 요청은 두 번째 인자에 옵션을 넣습니다.

const post = await apiFetch<Post>("/posts", {
  // 데이터를 보내는 요청 방식.
  method: "POST",

  // 보내는 본문이 JSON임을 서버에 알림.
  headers: {
    "Content-Type": "application/json",
  },

  // 객체를 JSON 문자열로 변환해 전송.
  body: JSON.stringify({ title: "새 글" }),
});

method는 요청 방식, headers는 요청에 붙일 정보, body는 보낼 데이터입니다. 여기서는 JSON 본문을 보내므로 Content-Type을 지정하고 객체를 JSON 문자열로 바꿉니다.

오류 메시지를 화면에 보여 주려면 호출하는 쪽에서 try/catch로 받습니다.

try {
  const posts = await apiFetch<Post[]>('/posts');
  // 받은 목록으로 화면을 갱신합니다.
} catch (error) {
  const message =
    error instanceof Error ? error.message : '요청에 실패했습니다.';
  // message를 화면의 오류 안내에 사용합니다.
}

이 기본 함수는 매번 JSON 본문을 읽습니다. 본문이 없는 204 응답이나 파일 다운로드처럼 다른 형식의 응답을 쓰게 되면, 그때 응답 처리 부분을 맞춰 주세요.

6. Axios와 비교하면?

Axios로 같은 조회 요청을 작성하면 다음처럼 쓸 수 있습니다. 이 예제에서는 axios를 설치하고 가져온 상태라고 가정합니다.

// 공통 API 주소 설정.
const api = axios.create({ baseURL: API_URL });

// /posts에 GET 요청.
const response = await api.get<Post[]>("/posts");

// Axios가 변환한 응답 본문 꺼내기.
const posts = response.data;

Axios는 기본 주소를 baseURL로 설정하고, 기본 응답 변환을 거친 데이터를 response.data에서 꺼내 사용합니다. 기본 설정에서는 HTTP 실패도 오류로 처리합니다.

fetch에서는 그 부분을 직접 작성해 공통 함수로 묶은 셈입니다. Axios 인스턴스 문서 · Axios 오류 처리 문서

처음부터 설정을 길게 만들 필요는 없습니다. API 주소와 요청·응답 처리부터 모아 두고, 여러 화면에서 같은 처리가 반복되기 시작할 때 공통 함수에 추가하면 됩니다.

 


언제나 잘못된 부분과 더 나는 개선에 대한 답변은 감사드립니다.

댓글

0개

댓글을 불러오는 중입니다.