[웹개발] React와 Express 연동하는 방법

방법이있지·2025년 8월 16일
2

웹개발

목록 보기
3/14
post-thumbnail

자바스크립트로 풀스택 개발을 할 땐 흔히 프론트엔드로 React.js, 백엔드로 Express.js를 사용하게 한다.

일반적으로 개발 과정에서는 React 프론트엔드와 Express 백엔드 서버를 동시에 실행시키고, 클라이언트 측 코드에서 백엔드로 요청을 보내고 응답을 받는 방식을 사용한다.

  • 실제 배포할땐 클라이언트인 React 쪽엔 html, css, js파일만 남고 서버를 사용하진 않는다. 대신 개발 과정에서는 편의를 위해 React 쪽도 서버를 둔다.

중요한 점은, Express는 요청에 포함된 JSON 형태의 데이터를 받으면 응답을 JSON 형태로 반환하는 역할만을 수행한다. 화면에 띄워주는 건 React가 해 준다.

  • Flask의 Jinja2나 JavaScript의 ejs 등 백엔드에서의 포맷팅 엔진을 사용하지 않는단 소리다.

이때 서버로 GET, POST 등의 API 요청을 보내주는 패키지가 axios다. 이를 사용하는 방법을 다루어보겠다.

프록시 서버 설정하기

Express 서버가 3000번 포트로 돌아가고, API 요청이 /foods 경로에서 이루어질때, 아래와 같이 React 쪽 코드에 프록시 서버 설정을 해 주어야 한다.

// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  // 이걸 해 주셔야 합니다.
  server: {
    proxy: {
      "/foods": "http://localhost:3000",
    },
  },
  // 감사합니다.
});

이렇게 설정을 해 주면, React 쪽에서 /foods 경로로 요청을 보낼 때 자동으로 http://localhost:3000(Express 서버)로 전달된다.

이 설정 없으면, Express 서버 말고 React 개발서버 자기 자신에게 요청을 보내는 오류가 발생하니까 꼭 꼭 꼭 해 두자.

Axios로 요청 보내기

게시판이나 쇼핑사이트 같은 사이트를 구현할 땐, 데이터베이스에 저장된 모든 데이터(게시글이든, 상품 정보든)를 화면에 표시해야 할 거다.

일단 Express 서버에선, /foods로 GET요청이 들어오면, MongoDB의 Food 컬렉션에 저장된 모든 도큐먼트를 JSON 형태로 응답하도록 구성할 수 있다.

// Express [서버]

app.get('/foods', async (req, res) => {
  try {
    // Food 콜렉션의 모든 도큐먼트를 반환한다.
    const foods = await Food.find();
    // 이를 json 형태로 응답한다.
    res.status(200).json(foods);
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
});

이런 코드로 요청을 보내면, 아래와 같은 도큐먼트 객체의 배열 형태로 응답한다.

// {}로 구성된 키, 값으로 구성된 애들을 객체라고 한다.
[
  { "_id": "64d3a2...", "name": "사과", "price": 1000, "category": "과일" },
  { "_id": "64d3a3...", "name": "바나나", "price": 1500, "category": "과일" }
]

문제는 React에서 이러한 GET 요청을 보내고 응답을 받을 방법이 필요하단 거다. 그 역할을 axios가 해 준다.

// React [클라이언트]

import axios from "axios";
const API_URL = "/foods";

// 전체 조회
export async function getAllFoods() {
  // /foods 경로로 GET 요청.
  const res = await axios.get(API_URL);
  // 응답데이터를 반환
  return res.data;
}

axios를 활용해, 서버에 GET 요청을 보내는 getAllFoods 함수를 만들어 보자.

단순히 axios.get(요청경로)를 이용해 GET 요청을 보낼 수 있다. async/await 사용했으니, 서버로부터 응답이 올 때까지 return res.data가 실행되지 않는다.

이후 return res.data는, 서버에서 보낸 JSON 데이터를 그대로 반환한다. 이때 별도로 json 형태로 parse해야한다거나 할 필요는 없다. axios에서 알아서 다 해준다.

그러면 반환값은 앞서 본 도큐먼트 객체의 배열가 된다.

서버에서 반환한 값 사용하기

사이트를 새로 접속했거나, 새로고침했을 때, 데이터베이스의 모든 값을 나열해서 화면에 띄우고 싶다고 하자.

리액트의 App 컴포넌트에서, 화면에 띄울 내용을 배열 형태의 state로 관리한다고 가정한다.

일단 초기값은 빈 배열로 놔두되, App 컴포넌트가 처음으로 렌더링될 때 서버에 GET 요청을 보내 모든 데이터를 가져와야 한다.

// React [프론트엔드]
function App() {
  const [foods, setFoods] = useState([]);

// []: App이 처음으로 렌더링될 때만, 콜백 함수를 실행한다.
  useEffect(() => {
    getAllFoods().then(setFoods);
  }, []);

이럴 땐 useEffect라는 친구를 쓸 수 있다. 일반적으론 두번째 인자에 state를 넣으면, 해당 값이 바뀔때마다 첫번째 인자의 콜백함수를 실행한다.

그런데 두번째 인자에 []를 넣는 경우, 컴포넌트가 처음으로 렌더링될 때만 콜백함수를 실행한다.

콜백함수로 getAllFoods().then(setFoods)를 두면

  • 우선 앞서 만든 getAllFoods()로, DB에 GET 요청을 보내 데이터를 가져온다
  • 이후 도큐먼트 객체의 배열을 반환하면, .then(setFoods)foods state를 업데이트한다

이후엔 foods 배열의 값을 적절한 방식으로 렌더링하게끔 App을 구현하면, 서버에서 받은 데이터를 화면에 표시할 수 있다. 신난다!

POST 요청은 어떤가요

POST요청과 같이 JSON 데이터를 서버로 보낼 때가 있는데,
단순히 axios.post() 함수의 두번째 인자로 객체를 넣어주면 된다.

// React [프론트엔드]
// 생성
export async function createFood(name, price, category) {
  const res = await axios.post(API_URL, { name, price, category });
  return res.data;
}

createFood 함수는 매개변수로 받은 name, price, category를 JSON 객체로 만들어 /foods 경로로 POST 요청을 보내는 역할을 한다.

// Express [백엔드]
// JSON 데이터를 받을 수 있도록 설정
app.use(express.json());

// POST 요청 처리
app.post("/foods", (req, res) => {
  // req.body로 전달받은 JSON 객체에 접근할 수 있다.
  const { name, price, category } = req.body;
  // 이후 Food 데이터베이스에 삽입하는 등 후속 처리를 해주면 되겠다.
  const food = await Food.create({ name, price, category });
  res.status(201).json(food);
}

이런 식으로 객체를 보내주면, req.body를 이용해 접근할 수 있다.
Express에서는 JSON 응답을 받기 위해선 app.use(express.json()) 미들웨어를 설정해 두어야 한다는 점을 기억해 두자.

실제로 테스트해보기

실제 테스트는 React 서버와 Express 서버 양쪽 모두 실행한 뒤,
웹 브라우저에서 React 앱을 열고 이것저것 시도해보는 방식으로 할 수 있다.

배포 후엔?

npm run build를 쉘에서 실행하면, dist/ 폴더가 생성된다.

프론트엔드(리액트)쪽 html, css, js 코드가 포함된다. 앞선 REact 서버는 개발 편의를 위해 존재한 거고, 실제 배포할 땐 React 쪽은 서버를 쓰지 않는다.

대신 서버 쪽에서 dist/ 폴더에 있는 파일들을 인식할 수 있게 처리를 해야 한다.

// Express [백엔드]
import express from "express";
import path from "path";

const app = express();

// JSON 요청 처리
app.use(express.json());

// **추가할부분 1**
// React 정적 파일 제공
// __dirname: 현재 절대경로를 뜻하는 전역변수.
// 해당 폴더의 css, js, 이미지 등 정적 파일을 express가 사용할 수 있게 설정한다.
app.use(express.static(path.join(__dirname, "dist")));

// 기존에 만들어 둔 API 라우트
app.get("/foods", async (req, res) => {
  const foods = await Food.find();
  res.json(foods);
});

app.post("/foods", async (req, res) => {
  const { name, price, category } = req.body;
  const food = await Food.create({ name, price, category });
  res.status(201).json(food);
});

// **추가할부분 2**
// React SPA 처리
// Express에서 설정하지 않은 aPI 요청의 경우, 프론트엔드 쪽 index.html로 보내 React가 처리하게 함
app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "dist", "index.html"));
});

app.listen(3000, () => console.log("Server running on 3000"));

사실 이 부분은 아직 배포를 할 일이 없어서 간단히만 적었는데,

추가할부분 1에 표시한 대로, dist 폴더에 저장된 정적 파일을 express가 사용할 수 있게 미들웨어를 선언해야 한다.

그리고 추가핧부분 2에 표시한 대로, Express에서 정의하지 않은 경로의 경우 dist/index.html로 보내 React에서 처리할 수 있게 한다.

  • 이때 *는 어떤 경로든 콜백함수대로 실행하게끔 처리한다는 뜻이다.
  • 그러므로 기존에 설정한 다른 루트들 다음에, 이 부분을 추가해야 한다.
  • Express는 라우트가 선언된 순서대로, 요청을 처리하기 때문이다. 위 예제의 경우 "/foods"로 API 요청이 먼저 갔는지 확인하고, 아닌 경우에만 React 쪽으로 넘겨야 한다.
profile
뭔가 만드는 걸 좋아하는 프론트엔드 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글