직접 만들어보는 To Do List - Express.js + React.js + SQLite (3)

Vite를 이용한 React 프로젝트 설정합니다. Express API 서버와 Vite 개발 서버가 동시 실행 되도록 Concurrently 라이브러리를 사용합니다. 흔히 겪는 CORS 설정, React 와 Express API 서버간의 통신 방법도 다룹니다.

직접 만들어보는 To Do List - Express.js + React.js + SQLite (3)
Photo by Kamran Abdullayev / Unsplash
  1. Express.js 설치
  2. Express.js - Route 설정
  3. React 설치
  4. Backend API
  5. Frontend - React

React 프로젝트 설정 - Vite

todo_list 폴더에서 바로 Vite를 이용해서 React를 설치하도록 하겠습니다.

npm create vite@latest frontend

frontend 폴더에서 vite 최신버전으로 프로젝트를 시작하겠다는 의미입니다. React -> Javascript 순으로 선택해주시면 됩니다.

vite 프로젝트가 설정이 끝나고나면 아래 명령으로 npm 패키지를 설치해줍니다.

cd frontend && npm i

frontend 폴더에 패키지까지 모두 설치되고 나면 아래와 같은 폴더 구조가 되어있을 것입니다.

보시다시피 node_modules 폴더가 todo_list 밑에도 하나 frontend 폴더 밑에도 하나 있습니다. monorepo 로 깔끔하게 구성하는 방법도 있겠지만, 여기서는 최대한 단순하게 진행하려고 합니다.

첫 번째 연재글에서 .gitignore 파일에서 모든 node_modules 폴더를 제외하라고 명시해주었습니다. 그래서 github에 올릴 때도 걱정이 없는 상태입니다.

미처 작성하지 않으신 분들은 첫 번째 연재글을 다시 한 번 확인해보시기 바랍니다.


App.jsx 파일 수정

Frontend 프로젝트로 넘어가서 App.jsx 파일부터 수정해보겠습니다.

// App.jsx
import React from 'react';

const App = () => {
    return (
        <div>
            To do list Frontend
        </div>
    );
};

export default App;

vite 에서 미리 설정해놓은 예시 코드들은 전부 사용하지 않을 것이기 때문에 모두 지워주고 위와 같이 작성해주세요.

그리고 frontend 폴더에서 아래와 같이 실행합니다.

npm run dev

일반적으로 vite 프로젝트에서 별다른 설정을 안했다면 http://localhost:5173 로 접속하면 아래와 같은 문구가 출력되는 것을 볼 수 있을 것입니다.

Concurrently 설치

현재는 todo_list 폴더에서 npm run server 명령으로 api 서버를 실행하고 frontend 폴더에서 또 npm run dev 명령으로 React 용 개발서버를 추가적으로 실행시켜주는 상황입니다.

번거로운 과정이기 때문에 이것을 한 번에 처리해줄 수 있는 concurrently 패키지를 설치합니다.

todo_list 폴더에서 아래와 같이 실행해주세요

npm i concurrently -D

개발과정에서만 사용할 패키지이기 때문에 -D 옵션을 붙여서 devDependency 에 추가되도록 설치합니다.

todo_list 폴더에 있는 package.json 파일에서 scripts 부분을 아래와 같이 수정해줍니다.

...
"scripts": {
    "start": "node backend/server.js",
    "server": "nodemon backend/server.js",
    "client": "npm run dev --prefix frontend",
    "dev": "concurrently \"npm run server\" \"npm run client\""
},
...

이제부터는 번거롭게 별도로 실행하지 않아도 됩니다.

todo_list 폴더에서 아래와 같이 실행해줍니다.

npm run dev

API 서버frontend 의 개발서버가 동시에 실행된 것을 볼 수 있습니다.

API 서버로부터 데이터 수신

이제 다시 리액트의 App.jsx 파일로 이동합니다.

fetch API를 이용해서 api 서버로부터 데이터를 받아옵니다.

// App.jsx
import React, { useEffect } from "react"

const App = () => {
  useEffect(() => {
    fetch("http://localhost:5000/api/todos").then((data) => console.log(data))
    return () => {}
  }, [])

  return <div>To do list Frontend</div>
}

export default App

데이터가 서버로부터 도착할 때까지 기다려야 하기 때문에 useEffect 안에서 fetch api를 이용해 값을 받아옵니다.

안타깝게도 아래와 같은 오류 메세지가 뜰 것입니다.

흔히 CORS Cross-Origin Resource Sharing라고 불리는 보안 정책때문입니다.

API 서버5000번 포트, 프론트엔드에서 가동되는 개발서버 포트는 5173 포트이기 때문에 일치하지 않는 포트로부터의 통신이 허용되지 않도록 크롬 브라우저에서 보안정책으로 강제하고 있습니다.

방법은 두 가지가 있습니다. vite.config.js 파일에서 proxy 설정을 해주거나, api 서버에서 cors 설정을 해주면 됩니다.

여기서는 서버에서 cors 설정을 해주는 방법으로 진행합니다.

vite.config.js에서 proxy 설정하는 방법은 아래 링크를 참고하시기 바랍니다.

Vite
Vite, 차세대 프런트엔드 개발 툴

CORS 설정

express에서 cors 설정을 해주기 위해서 todo_list 폴더에서 아래와 같이 패키지를 설치해줍니다.

npm i cors

아래와 같이 server.js 파일도 수정해줍니다.

// server.js
const express = require("express")
const app = express()
const PORT = 5000

// cors 패키지
const cors = require("cors")

app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// http://localhost:5173 포트로부터의 요청만 허용
app.use(cors({ origin: "http://localhost:5173" }))

...

이제 리액트 vite 개발서버에서 요청도 정상적으로 허용이 될 겁니다.

데이터 수신

App.jsx 파일을 아래와 같이 수정합니다.

// App.jsx
import React, { useEffect } from "react"

const App = () => {
  
  useEffect(() => {
    // 5000번 포트 API 서버로부터 데이터 수신
    fetch("http://localhost:5000/api/todos")
      .then((data) => data.json() /* 수신된 데이터를 json 형식으로 */)
      .then((result) => console.log(result.message) /* console.log로 message 출력 */).catch((error) => console.log(error) /* 에러 발생시 에러 출력 */)
  }, [])

  return <div>To do list Frontend</div>
}

export default App

소스를 저장하고 개발자도구를 띄워서 console 출력 내용을 확인합니다.

위와 같이 호출되면 제대로 된 데이터를 얻어온 상태입니다.

server.js 에서 http://localhost:5000/api/todosget 요청이 들어오면 JSON 형식으로 message 키에 전체 To Do List라는 문자열을 담아 보내도록 작성해주었기 때문에 위와 같은 결과가 나온 것입니다.

이제 저 메세지를 화면에 직접 출력해봅시다.

App.jsx를 다시 아래와 같이 수정해주세요

// App.jsx
import React, { useEffect, useState } from "react"

const App = () => {
  // todos  
  const [todos, setTodos] = useState(null)
  
  useEffect(() => {
    // 5000번 포트 API 서버로부터 데이터 수신
    fetch("http://localhost:5000/api/todos")
      .then((data) => data.json() /* 수신된 데이터를 json 형식으로 */)
      .then(
        (result) => setTodos(result.message) /* message를 todos에 담는다 */,
      )
      .catch((error) => console.log(error) /* 에러 발생시 에러 출력 */)
  }, [])

  // todos를 화면에 출력
  return <div>{todos}</div>
}

export default App

이제는 메세지가 화면에 직접적으로 출력이 될 것입니다. 기본적으로 API 서버와 리액트간의 통신에 성공한 상태입니다. 다음 포스팅에서 이어가도록 하겠습니다.