코딩짜는 일상

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 22차 - 리엑트 api BaseUrl 관리 전략 본문

TIL/TIL 챌린지

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 22차 - 리엑트 api BaseUrl 관리 전략

Remily 2025. 12. 16. 23:57
반응형

📚 서론

저희 팀은 프로젝트를 프론트와 백엔드로 나누어

백엔드에선 DB와 통신하여 데이터를 가공하거나 api로 결과값을 전달하고

프론트에선 api의 데이터를 받아 클라이언트를 구성하도록 구조를 짰습니다.

 

하지만 로컬에서 개발할 땐 localhost:8080이 기본 도메인이 되는 한편

배포를 하면 해당 서버의 도메인으로 바뀌어야 합니다.

 

이걸 배포할 때마다 전부 찾아서 바꿔 넣는 건 매우 비효율적이죠...

 

배포할 때마다 무한 반복 작업...

 

그래서 이번 포스팅에선 api의 기본 도메인, base url을 어떻게 관리할 것인지

몇 가지 방법을 정리하여 소개하려고 합니다.

 

 

 

 

 

🎷 Config 파일로 분리해 관리

구조가 단순해서 초보자도 이해하기 좋은 방식입니다.

 

설정 관련 내용은 전부 config 파일에 모아두기 때문에

해당 파일만 읽어도 프로젝트의 중요 내용 대부분을 파악할 수 있다는 장점이 있습니다.

 

1️⃣ config.js 파일 생성

config.js파일 구성. 매우 간단하다!

 

일반적으로 src 폴더에 config 폴더를 추가 config.js 파일을 만들어 사용합니다.

 

만약 설정 내용이 많다면 apiConfig.js, orderConfig.js 처럼

파일을 세분화 하여 사용해도 됩니다.

// src/config/apiConfig.js
const apiConfig = {
  baseURL: "https://api.example.com", // or "http://localhost:8080"
};

export default apiConfig;

 

이처럼 단순한 기본 객체 형태로 만들어 사용하는데

개발할 땐 로컬 주소로 두고 쓰다가 배포할 때 해당 파일만 수정하면 됩니다.

 

아니면 조건문을 추가해 환경에 따라 알아서 바꿔 쓸 수 있도록 할 수 있습니다.

 

// src/config/apiConfig.js
const apiConfig = {
  baseURL:
    process.env.NODE_ENV === "development"
      ? "http://localhost:8080"
      : "https://api.example.com",
};

export default apiConfig;

 

 

타 컴포넌트에서 사용할 땐 간편하게 config 파일을 import 하면 됩니다.

 

// src/components/OrderList.js
import { useEffect, useState } from "react";
import axios from "axios";
import apiConfig from "../config/apiConfig";

function OrderList() {
  const [orders, setOrders] = useState([]);

  useEffect(() => {
    axios
      .get(`${apiConfig.baseURL}/orders`)
      .then((res) => {
        setOrders(res.data);
      })
      .catch((err) => {
        console.error("API Error:", err);
      });
  }, []);

  return (
    <div>
      <h2>주문 목록</h2>
      {orders.map((order) => (
        <p key={order.id}>{order.name}</p>
      ))}
    </div>
  );
}

export default OrderList;

 

2️⃣ Redux 활용

하지만 저는 새로운 컴포넌트 마다 config 파일 위치를 일일이 찾아서 import 하는 방법이 번거로워서

Redux를 사용하는 김에 store를 통해 base url을 받아올 수 있도록 했습니다.

 

redux를 활용한 config. 처음보다 구성이 더 많아졌지만 세분화를 많이 해도 redux로 전부 간편하게 호출!

 

구조는 src/app에 store.js를 두고

src/features/config에 Slice.js를 만듭니다.

 

저는 관리자 페이지, 사용자 페이지의 url이 따로여서 2개를 만들었습니다.

 

// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';

import userConfigReducer from 'features/config/userConfigSlice';

import admConfigReducer from 'features/config/admConfigSlice';

export const store = configureStore({
  reducer: {
    userConfig: userConfigReducer,

    admConfig: admConfigReducer,
  },
});

 

// src/features/config/admConfigSlice.js
import { createSlice } from '@reduxjs/toolkit';

const admConfigSlice = createSlice({
  name: 'admConfig',
  initialState: {
    apiBaseUrl: "http://localhost:8080",
  },
  reducers: {},
});

export default admConfigSlice.reducer;

 

사용할 땐 useSelector를 임포트 하고

store에 adminConfigSlice를 매핑 한대로 admConfig를 불러

adminConfigSlice에 정의해둔 apiBaseUrl을 가져다 쓰면 됩니다.

 

세분화를 아무리 많이 해도 컴포넌트에서 가져다 쓸 땐

react-redux만 import 하면 되서 간편합니다!

 

// src/components/OrderList.js
import { useEffect, useState } from "react";
import axios from "axios";
import { useSelector } from 'react-redux';

function OrderList() {
  const [orders, setOrders] = useState([]);
  
  const apiBaseUrl = useSelector(state => state.admConfig.apiBaseUrl);

  useEffect(() => {
    axios
      .get(`${apiBaseUrl}/orders`)
      .then((res) => {
        setOrders(res.data);
      })
      .catch((err) => {
        console.error("API Error:", err);
      });
  }, []);

  return (
    <div>
      <h2>주문 목록</h2>
      {orders.map((order) => (
        <p key={order.id}>{order.name}</p>
      ))}
    </div>
  );
}

export default OrderList;


더 간단하죠? 😉

 

 

 

 

 

🎺 환경 변수(.env)로 관리

다음으로 소개드릴 방법은 환경 변수를 저장해두는 .env 파일을 이용한 관리입니다.

 

리액트는 빌드 시점에 .env파일을 가장 먼저 읽

모든 JS코드에서 process.env.REACT_APP_***로 된 부분을 찾아 문자열로 치환하기 때문에

로컬용, 배포용으로 .env를 만들고 api base url을 각각 다르게 저장해두면 됩니다.

 

참고로 해당 파일에 api key를 저장해 쓰기도 하므로

절대!! 깃에 올리지 않도록 조심해야 합니다!!!

 

주의! 또 주의! 😥

 

 

1️⃣ .env 파일 생성

.env는 리엑트 프로젝트 루트 최상단에!

 

.env는 리엑트 프로젝트의 최상단에 만들어야 합니다.

 

참고로 파일 이름에 따라 환경별로 나눠서 쓸 수 있는데요.

.env공통으로 사용되는 환경 변수고

.env.developmentnpm start 명령어개발용 번들을 빌드할 때만 사용되는 환경 변수고

.env.productionnpm run build 명령어배포용 번들을 빌드할 때만 사용되는 환경 변수입니다.

 

 

파일을 만들었다면 안에서 환경 변수를 정의해줘야 하는데요.

 

반드시 REACT_APP_으로 시작해야 하고

=기호 양 옆에 띄워쓰기가 있으면 안됩니다.

 

규칙을 지키지 않으면 무시되거나 잘못 인식될 수 있으니 주의하세요!

# .env.development 파일 예시
REACT_APP_API_BASE_URL=http://localhost:4000 # 개발 도메인

# .env.production 파일 예시
REACT_APP_API_BASE_URL=https://api.example.com # 배포 도메인

 

 

2️⃣ 컴포넌트에서 사용

사용은 더 간단해졌습니다.

 

import도 필요 없이 바로 process.env.변수명으로 접근하면 됩니다.

// src/components/OrderList.js
import { useEffect, useState } from "react";
import axios from "axios";

function OrderList() {
  const [orders, setOrders] = useState([]);
  
  useEffect(() => {
    axios
      .get(`${process.env.REACT_APP_API_BASE_URL}/orders`)
      .then((res) => {
        setOrders(res.data);
      })
      .catch((err) => {
        console.error("API Error:", err);
      });
  }, []);

  return (
    <div>
      <h2>주문 목록</h2>
      {orders.map((order) => (
        <p key={order.id}>{order.name}</p>
      ))}
    </div>
  );
}

export default OrderList;

 

 

 

 

 

📯 응용: axios 인스턴스 만들기

지금까지 배운 방법대로면 가장 간편한 방법은 .env를 이용하는 방법입니다.

 

하지만 baseUrl을 가져오고, axios를 이용해서 api에 데이터를 요청하고

받은 데이터를 처리하는 과정까지 통틀어 생각해보면 어떨까요?

 

거기다 토큰을 추가하거나 공통 헤더를 추가해서 요청을 보내야 하거나

에러 코드에 따라 공통적으로 처리해야 할 작업이 있다면 axios 구문이 더욱 더 길어질 것입니다.

 

이러한 반복되는 api 관련 설정을 하나의 파일에 모아두고 사용하면 어떨까요?

api 인스턴스 구조. 처음에 설명한 config.js와 비슷하다.

 

구조는 처음에 설명했던 config.js 파일을 만들어 쓰는 방법과 유사하며,

인스턴스 파일을 api에 모아두고 쓸 컴포넌트에서 import 해서 쓰면 됩니다.

 

저는 사용자 api와 관리자 api의 base url이 다르기 때문에 2개를 만들었습니다.

 

 

1️⃣ axios 인스턴스 파일 구성

파일의 구성은 base url과 요청 시간 제한이 있는 기본 설정부

요청이 서버로 나가기 전 추가할 작업이 있는 요청 인터셉터

응답을 받은 후 에러 처리나 공통 작업이 있는 응답 인터셉터

 

이렇게 3개로 나눌 수 있습니다.

 

코드 형식은 아래 코드를 참고하시면 됩니다.

 

// src/api/userApi.js
// * 기본 설정부 *
import axios from "axios";

const api = axios.create({
  baseURL: process.env.REACT_APP_USER_API_BASE_URL,
  timeout: 5000, // 요청 제한시간
});

// * 요청 인터셉터 *
api.interceptors.request.use(
  (config) => {
    const token = "토큰";
    
    // 헤더에 토큰 추가
    if (token) config.headers.Authorization = `Bearer ${token}`;
    // 헤더에 요청 데이터 타입 설정
    config.headers.Content-Type = "application/json";
    
    return config;
  },
  (error) => Promise.reject(error)
);

// * 응답 인터셉터 *
api.interceptors.response.use(
  (response) => response,
  (error) => {
  	// http 상태코드에 따라 응답 회신
    if (error.response?.status === 401) {
      console.log("로그인이 필요합니다.");
    }
     if (error.response?.status === 403) {
      console.log("권한이 필요합니다.");
    }
    
    return Promise.reject(error);
  }
);

export default api;

 

 

 

2️⃣ 컴포넌트 사용

앞서 인스턴스에서 axios를 import 해서 객체를 만들고 설정들을 미리 등록해두었기에

사용할 때는 해당 인스턴스를 변수에 담아 import 해서 사용하면 됩니다.

// src/components/OrderList.js
import { useEffect, useState } from "react";
import api from "../api/userApi";

function OrderList() {
  const [orders, setOrders] = useState([]);
  
  useEffect(() => {
    api.get('/orders')
      .then((res) => {
        setOrders(res.data);
      })
      .catch((err) => {
        console.error("API Error:", err);
      });
  }, []);

  return (
    <div>
      <h2>주문 목록</h2>
      {orders.map((order) => (
        <p key={order.id}>{order.name}</p>
      ))}
    </div>
  );
}

export default OrderList;

 

헤더 설정과 에러 처리 설정이 추가되었지만 코드는 더 짧아졌습니다.

 

더는 api base url을 신경 쓸 필요도 없이 남은 api 경로만 써주면 됩니다.

 

 

 

 

 

🔥 결론

일단은 현재 팀 프로젝트에서 쓰는 기준으로 글을 작성하다보니

proxy와 api 호출 라이브러리는 설명이 빠졌습니다.

 

할 게 너무 많기도 했어요...

 

원래는 config, .env, proxy를 다루려고 했는데

글을 쓰려고 공부를 더 하다 보니 axios 인스턴스를 알게 되서

 

이건 꼭 써야겠다!!! 😎

 

하고 포스팅 하게 되었습니다...ㅎㅎ

 

 

이번 팀 프로젝트에 도입하기엔 늦었지만

개인 프로젝트를 하게 된다면 반드시 쓰게 될 것 같습니다.

이런 좋은 방법은 대체 어떻게들 떠올리는거람!

 

반응형