Mini Project 5 03

React 앱 백엔드 연결: VITE_API_BASE_URL과 서비스 정규화

backend를 붙인다고 하면 화면을 전부 새로 만드는 것처럼 느껴진다. 하지만 실제 변화는 많은 경우 service layer의 base URL과 endpoint 계약에서 먼저 보인다.

근거 · Frontend service/API 주소 전환 구현

React 앱 백엔드 연결: VITE_API_BASE_URL과 서비스 정규화 대표 이미지

1장: 백엔드 연결은 UI 재구축이 아니다

솔라는 프로젝트 폴더를 뒤적이다가 무심코 열어본 문서 한 줄에 시선이 멈췄다.

5차 frontend는 기존 화면 흐름을 유지하되 VITE_API_BASE_URL과 service 함수 정규화를 통해 실제 Spring Boot backend를 바라보게 바뀐다.

‘기존 화면 흐름을 유지하되… 실제 백엔드를 바라보게 바뀐다.’

솔라는 문장을 몇 번이고 다시 읽었다. 머릿속이 복잡해졌다. 지금까지 솔라에게 ‘백엔드 연결’이란, 프론트엔드 프로젝트에 지각 변동을 일으키는 거대한 사건처럼 느껴졌다. 데이터를 주고받는 방식이 완전히 달라지니, 화면을 구성하는 컴포넌트부터 상태 관리 로직까지 전부 새로 짜야 할 것만 같았다. 그런데 화면 흐름을 유지한다니. 마치 집의 수도관 전체를 교체하면서, 아무 일 없다는 듯 평소처럼 물을 쓰라는 말처럼 들렸다.

“언니, 이거 좀 이상하지 않아?”

솔라가 모니터 한구석을 가리키며 거실에 있던 루나를 불렀다.

“뭐가?”

“이 프로젝트 설명 말이야. 화면은 그대로 두면서 백엔드만 실제 서버로 바꾼대. 이게 가능한가? 백엔드를 붙이는 건 거의 앱을 새로 만드는 거나 다름없는 대공사 아니었어?”

솔라의 목소리에는 당연한 사실을 부정당한 듯한 억울함마저 섞여 있었다. 가짜 서버(json-server)를 쓸 때와 실제 서버(Spring Boot)를 쓸 때는 분명히 다른 점이 많을 터였다. 솔라는 이미 화면 여기저기를 뜯어고치고, 데이터를 불러오는 코드마다 fetch 주소를 바꾸고, 그에 맞춰 화면 컴포넌트의 구조를 변경하는 복잡한 수술 과정을 상상하고 있었다.

루나는 솔라가 가리키는 문장을 잠시 들여다보더니, 대답 대신 솔라의 컴퓨터에서 새 파일 탐색기 창을 열었다. 그리고는 프로젝트의 최상위 폴더에 있는 .env 라는 이름의 파일을 클릭했다. 파일 내용은 아주 간단했다.

VITE_API_BASE_URL=http://localhost:8080

“솔라, 만약 백엔드를 연결하는 과정에서 바뀌는 코드가 이 파일의 딱 한 줄뿐이라면 어떨 것 같아?”

“에이, 말도 안 돼. 이 한 줄로 어떻게 그 많은 API 요청들을 다 처리해? 책 목록 가져오는 화면, 책 상세 정보 보는 화면, 전부 주소가 다를 텐데.”

솔라는 루나의 말이 터무니없는 가정이라고 생각하며 손을 내저었다.

“그럼 이렇게 생각해보자. 우리 집 주방에 있는 정수기를 떠올려봐. 우리는 필터가 어디 제품인지, 수도관이 벽 어디로 연결되어 있는지 신경 쓰지 않고 그냥 컵을 대고 버튼을 눌러서 물을 마시잖아.”

“그렇지.”

“React 컴포넌트, 즉 우리가 보는 화면이 바로 그 ‘컵을 대고 버튼을 누르는 사람’이야. 화면은 그냥 ‘책 데이터 주세요’라는 요청을 보낼 뿐이지. 그 요청이 어느 서버로 가는지, 어떤 경로를 거쳐서 오는지까진 알 필요가 없어.”

루나는 말을 이으며, 화면을 그리는 코드와 데이터를 가져오는 코드가 담긴 폴더를 각각 가리켰다.

“화면을 그리는 부분(UI)과 데이터를 가져오는 부분(서비스 레이어)은 서로 다른 관심사를 가지고 있어. 백엔드 서버 주소가 바뀌는 건 ‘데이터를 가져오는 부분’의 일이지, ‘화면을 그리는 부분’의 일이 아니야. 아까 본 .env 파일의 저 주소는 데이터 길의 시작점을 알려주는 이정표 같은 거고.”

그제야 솔라의 머릿속에 흩어져 있던 조각들이 맞춰지기 시작했다. 자신이 상상했던 ‘대공사’는 UI와 데이터 통신을 한 덩어리로 생각했기 때문에 벌어진 오해였다. 화면은 그저 약속된 형태의 데이터가 오기만을 기다릴 뿐, 그 데이터가 옆집에서 오든, 먼 나라에서 오든 상관하지 않는다. 백엔드 연결은 수도관 전체를 교체하는 공사가 아니라, 정수기에 연결된 수원지를 바꾸는 작업에 더 가까웠다. 정수기 자체(서비스 레이어)가 바뀐 수원지(새로운 백엔드 주소)에 맞춰 물을 잘 끌어오기만 하면, 물을 마시는 사람(UI)은 아무런 변화를 느낄 필요가 없는 것이다.

“아…! 그러니까 화면 코드는 거의 그대로 두고, 데이터가 오는 길만 살짝 바꿔주면 되는 거구나. 나는 백엔드를 붙인다고 하면 당연히 화면부터 다 뜯어고쳐야 하는 줄 알았네.”

솔라는 자신이 얼마나 큰 착각을 하고 있었는지 깨달으며 허탈하게 웃었다. 백엔드 연결이 UI 재구축이 아니라는 사실을 받아들이자, 복잡하게 얽혀 있던 문제가 한결 단순해 보였다. 하지만 곧 새로운 궁금증이 고개를 들었다.

“그런데 언니, 궁금한 게 있어. 이정표가 http://localhost:8080이라고만 되어 있는데, 여기서 어떻게 책 목록을 가져오는 http://localhost:8080/books 주소로 찾아가는 거야? 서비스 파일이 알아서 길을 만들어주는 건가?”

2장: VITE_API_BASE_URL의 역할과 bookService의 정규화

솔라의 궁금증은 꼬리를 물고 이어졌다. 이정표(VITE_API_BASE_URL)가 어떻게 수많은 목적지 중 정확히 ‘책방’으로 안내하는지는 여전히 수수께끼였다. 그녀는 직접 답을 찾아보기로 마음먹었다. 이정표가 사용되는 곳을 찾아가면 실마리가 보일 터였다. 프로젝트 검색창에 VITE_API_BASE_URL을 입력하자 몇 개의 파일이 나타났고, 그중 Frontend/src/bookService.js라는 파일 경로가 솔라의 눈길을 끌었다. 망설임 없이 파일을 열었다.

파일 상단에는 익숙한 환경 변수를 가져오는 코드가 있었다. 하지만 바로 아래, 솔라의 고개를 갸웃하게 만드는 몇 줄의 코드가 더 있었다. 변수를 그대로 사용하지 않고, 무언가 변형을 가하고 있었다. 마치 우체부가 주소 스티커를 받아서 그대로 붙이는 게 아니라, 거기에 추가로 동 호수를 적거나 잉크 번짐을 닦아내는 듯한 모습이었다. 코드는 대략 이런 일을 하고 있었다. ‘받아온 주소 끝에 슬래시(/)가 있으면 떼어낸다. 그리고 만약 주소가 /books로 끝나지 않으면, /books를 덧붙인다.’

“이건 또 뭐야… 그냥 http://localhost:8080/books라고 처음부터 다 쓰면 깔끔할 텐데, 왜 이렇게 번거롭게 빙빙 돌리는 거지?”

솔라는 혼잣말을 중얼거렸다. 그녀의 생각에, VITE_API_BASE_URL은 그 자체로 완전한 API 주소여야 했다. http://localhost:8080/books처럼 말이다. 그러면 이런 복잡한 처리 없이 바로 가져다 쓰면 될 일이었다. 혹은, VITE_라는 접두사가 붙었으니 뭔가 마법 같은 일이 일어나 자동으로 /books가 붙는다고 생각했는데, 현실은 지저분한 문자열 처리 코드의 연속일 뿐이었다.

그때, 솔라의 혼잣말을 들은 루나가 다가와 모니터를 함께 들여다봤다.

“우편번호 스티커를 만드는 중이구나.”

“우편번호? 그냥 API 주소 가져오는 코드 아니야? 이상하게 복잡하기만 하고.”

솔라가 투덜거리자, 루나는 메모장을 하나 열어 두 개의 칸을 만들었다. 왼쪽 칸에는 ‘.env 파일 값’, 오른쪽 칸에는 ‘bookService의 최종 주소’라고 적었다.

“자, 솔라. 네가 이 bookService 코드를 만든 사람이라고 상상해봐. 다른 개발자가 .env 파일에 API 주소를 어떻게 적어 넣을지 예측할 수 있을까? 네 가지 경우를 시험해보자.”

루나는 첫 번째 시나리오를 제시했다.

“만약 어떤 사람이 .env 파일에 VITE_API_BASE_URL=http://localhost:8080 라고 깔끔하게 적었다면, 이 코드를 거친 최종 주소는 뭐가 될까?”

솔라는 코드를 눈으로 따라갔다. 끝에 슬래시가 없으니 첫 번째 조건은 통과. /books로 끝나지 않으니 /books가 붙는다. “http://localhost:8080/books가 되겠네.”

“좋아. 그럼 두 번째. 다른 사람이 실수로 끝에 슬래시를 넣어서 http://localhost:8080/ 라고 적었다면?”

“음… 코드가 끝의 슬래시를 떼어내니까 http://localhost:8080가 되고, 거기에 /books가 붙어서… 똑같네. http://localhost:8080/books.”

“세 번째. 아주 친절한 개발자가 아예 /books까지 다 적어서 http://localhost:8080/books 라고 입력했다면?”

솔라는 다시 코드를 훑었다. 끝에 슬래시가 없고, /books로 끝나므로 두 번째 조건문도 실행되지 않는다. “아무 일도 일어나지 않고 그냥 그대로 http://localhost:8080/books가 되네.”

“마지막. /books 뒤에 슬래시를 붙이는 습관이 있는 사람이라면? http://localhost:8080/books/

“슬래시가 떨어져 나가고… 어? 이것도 결국 http://localhost:8080/books가 되네!”

네 가지 시나리오를 모두 확인하고 나서야 솔라는 이 코드의 진짜 의도를 깨달았다. 이것은 복잡하거나 번거로운 코드가 아니었다. 어떤 식으로 주소가 들어오든, 이 서비스가 필요한 단 하나의 올바른 형태로 주소를 ‘정규화(Normalization)’ 해주는 튼튼한 방어막이었다. VITE_API_BASE_URL은 완전한 엔드포인트가 아니라, 여러 서비스에서 유연하게 사용할 수 있는 ‘기본 주소’의 역할만 했던 것이다. 그리고 각 서비스는 그 기본 주소를 가져다가 자신에게 필요한 형태로 ‘가공’해서 사용했다.

“알겠다! 마법이 아니었어. bookService/books라는 목적지를 명확히 알고, 어떤 상황에서도 그 목적지로 가는 길을 만들어내는 거였구나. 마치 내비게이션에 ‘서울시청’이라고 치면, 내가 지금 어디에 있든 최적의 경로를 찾아주는 것처럼.”

솔라는 자신이 발견한 ‘기본 주소 & 경로 정규화’라는 생각의 도구를 손에 쥔 것 같아 뿌듯했다. 이로써 하나의 이정표로 특정 목적지를 찾아가는 비밀이 풀렸다. 하지만 이내 새로운 의문이 피어올랐다.

“잠깐만. 이 Frontend/src/bookService.js 파일은 온통 /books로 가는 길만 만들어주잖아. 그럼 만약에 내가 ‘찜한 책’ 목록을 가져오기 위해 /favorites라는 다른 길로 가고 싶으면 어떻게 해야 하지? 이정표를 하나 더 세워야 하나?”

3장: 동일한 VITE_API_BASE_URL로 여러 엔드포인트 연결하기

솔라가 bookService.js 파일의 ‘정규화’ 로직을 깨달은 뒤에도, 메모장 위에 그려진 시나리오 표는 그대로 남아있었다. 어떤 식으로 입력해도 http://localhost:8080/books라는 하나의 결과로 수렴되던 깔끔한 표였다. 하지만 솔라의 마지막 질문은 그 표에 없는 새로운 길에 대한 것이었다. /favorites.

루나는 말없이 펜을 들어 표의 맨 오른쪽에 새로운 열을 하나 그렸다. 그리고 헤더에 이렇게 적었다.

favoriteService 최종 주소?

새로운 빈칸들은 마치 풀리지 않은 숙제처럼 보였다. 솔라는 루나가 만든 빈칸들을 보며 자신이 던진 질문의 무게를 느꼈다.

“언니, 그건 좀 이상한데.”

솔라가 입을 열었다. 그녀는 bookService.js의 코드를 떠올렸다. 그 코드는 어떤 입력값이 들어오든 /books를 보장하는 강력한 규칙이었다. 그 규칙이 이 프로젝트의 유일한 약속처럼 느껴졌다.

bookService가 쓰던 저 정규화 코드는 무조건 /books를 붙이잖아. 만약 favoriteService가 그 코드를 그대로 가져다 쓴다면, 결국 .../books/favorites 같은 이상한 주소로 요청을 보내게 될 텐데? 이건 우리가 원하는 /favorites가 아니잖아.”

솔라의 지적은 타당했다. 하나의 정해진 규칙을 모든 서비스가 따라야 한다면, 유연성은 사라진다. VITE_API_BASE_URL/books가 이미 포함되어 있거나, bookService처럼 자동으로 /books를 덧붙이는 로직이 전역적으로 적용된다면, 다른 경로를 사용하는 것은 불가능해 보였다. 새로운 길을 만들려면 새로운 이정표(VITE_FAVORITES_API_URL 같은)를 세워야 한다는 생각에서 벗어날 수 없었다.

“좋은 지적이야. bookService의 규칙은 bookService를 위한 것이지, 모두를 위한 법은 아니니까.”

루나는 솔라의 생각의 흐름을 긍정하며, 다른 관점을 열었다.

“그럼 이렇게 상상해보자. 솔라, 네가 지금 favoriteService.js 파일을 새로 만들고 있는 개발자야. 너에게 주어진 재료는 단 하나, 어떤 값이 들어있을지 모르는 VITE_API_BASE_URL 변수뿐이야. 너의 목표는 이 변수를 사용해서 http://localhost:8080/favorites라는 최종 주소를 만들어내는 거야. 어떻게 할래?”

이것은 퀴즈라기보다 설계에 가까운 질문이었다. 솔라는 잠시 생각에 잠겼다.

“일단 bookService처럼 똑같이 환경 변수를 읽어오겠지. import.meta.env.VITE_API_BASE_URL 이렇게.”

“그렇지. 그런데 만약 그 변수 안에 http://localhost:8080/books가 들어있다면?”

“그럼 /books가 방해되네… 먼저 저걸 제거해야겠다. 주소 문자열 끝에 /books가 있으면, 그 부분을 잘라내는 코드를 넣어야겠어.”

솔라의 눈이 빛났다. 막혔던 길이 보이는 듯했다.

“좋아. /books를 제거하면 뭐가 남지?”

http://localhost:8080이 남겠지. 아…!”

솔라는 짧은 탄성을 내뱉었다. 다음 단계는 너무나 명확했다.

“거기다가 /favorites를 붙이면 되잖아! 그럼 http://localhost:8080/favorites가 만들어지네! 만약 책 한 권에 대한 찜 목록을 가져와야 하면, …/books/${bookId}/favorites 처럼 만들 수도 있고.”

순간, 모든 것이 제자리를 찾았다. VITE_API_BASE_URL은 고정된 이정표가 아니었다. 각 서비스가 요리할 수 있는 ‘원재료’에 가까웠다. bookService는 이 원재료로 ‘책 목록 요리’를 만들기 위해 /books라는 양념을 치는 자신만의 레시피를 가진 요리사였다. 그리고 favoriteService는 ‘찜 목록 요리’를 만들기 위해, 혹시 들어있을지 모르는 /books라는 껍질을 벗겨내고 /favorites라는 새로운 양념을 치는, 또 다른 레시피를 가진 요리사였던 것이다.

“알겠다! 각 서비스가 자기한테 필요한 모양으로 주소를 ‘재가공’하는 거였구나. 같은 재료를 받아도, 각자 다른 요리를 만드는 것처럼! bookService/books 전문가, favoriteService/favorites 전문가가 되는 거네.”

이것이 바로 유연한 API 경로 구성의 핵심이었다. 하나의 기본 주소를 공유하되, 각 서비스는 독립적으로 자신의 엔드포인트에 맞게 주소를 가공할 책임을 진다. 이로써 VITE_API_BASE_URL 하나만으로 수십 개의 서로 다른 API 엔드포인트와 통신할 수 있는 확장 가능한 구조가 완성되었다.

“자, 그럼 이제 처음으로 돌아가 보자.”

루나가 말했다.

“만약 예전의 너처럼 ‘백엔드 연결은 UI 대공사’라고 겁먹고 있는 후배 개발자가 있다면, 뭐라고 조언해줄 거야? 화면은 그대로 두고 백엔드만 바꾸는 게 어떻게 가능한지 설명해야 한다면.”

솔라는 잠시 숨을 고르고, 머릿속에 흩어져 있던 깨달음들을 하나로 엮어 차분하게 설명하기 시작했다. 이것은 이제 루나에게 되묻는 질문이 아니라, 자신의 말로 정리된 단단한 지식이었다.

“음… 우선 이렇게 말해줄 거야. 첫째, 화면 코드는 단 한 줄도 건드릴 필요 없다고. UI를 그리는 것과 데이터를 가져오는 건 완전히 다른 일이니까, 걱정부터 하지 말라고 할래.”

“둘째, 서버 주소는 딱 한 곳, .env 파일의 VITE_API_BASE_URL에만 적으면 된다고 알려줄 거야. 이게 우리 앱이 서버와 대화하는 유일한 창구라고.”

솔라는 마지막 핵심을 짚으며 미소지었다.

“그리고 셋째, 가장 중요한 건데, 각 API를 부르는 서비스 파일들이 이 기본 주소를 가져다가 자기 입맛에 맞게 ‘정규화’해서 쓴다는 걸 보여줄 거야. bookService/books를 붙여서 책 전문가가 되고, favoriteService는 혹시라도 있을지 모를 /books를 떼어내고 /favorites를 붙여서 찜 전문가가 되는 거지. 이렇게 하면 이정표 하나로 어디든 갈 수 있는 유연한 연결이 가능해진다고. 그러니 대공사가 아니라, 그냥 똑똑한 길 찾기 문제일 뿐이라고 말해줄래.”