본문 바로가기
카테고리 없음

[새싹 금천 5기] React + FAST API 연계 (이노그리드_기업맟춤형 클라우드 인재 양성 캠프)

by z00h 2025. 5. 22.

 

 

이벤트 등록 REST API 연계

 

✅ React + FastAPI 이벤트 등록/조회 연동 - 요약

1. 프로젝트 준비

  • Python 가상환경 활성화
  • React 프로젝트 생성 및 axios 설치

2. FastAPI API 엔드포인트 확인

  • POST /events/ → 이벤트 등록
  • GET /events/ → 이벤트 목록 조회
  • GET /events/{event_id} → 상세 조회

3. React 컴포넌트 구성

🔹 Regist.jsx

  • 입력 폼 구성 (id, title, image, description, tags, location)
  • axios.post로 이벤트 등록 요청
  • handleSubmit, handleChange 함수 구현

🔹 List.jsx

  • useEffect로 이벤트 목록 요청
  • axios.get로 데이터 로딩
  • 로딩/에러 처리 및 이벤트 리스트 출력

🔹 Detail.jsx

  • URL 파라미터로 event_id 전달받아 상세 조회
  • useParams() + axios.get 사용

4. CORS 이슈 해결

  • FastAPI에서 CORS 미들웨어 설정 추가
  • Preflight 요청 이해 (브라우저 사전 요청)

5. React 라우팅 설정

  • react-router-dom 설치
  • BrowserRouter, Routes, Route 구성
  • /regist, /list, /detail/:event_id 경로 매핑

 

 

 

파이썬 프로젝트 활성화

c:\python> cd c:\python\planner

c:\python\planner> .\venv\Scripts\activate

리액트 프로젝트에 이벤트(event\Regist.jsx) 등록 컴포넌트 추가

우선 연계에 앞서 이벤트 등록 시 전달되어야 하는 값을 확인해야한다.

 

  1. 엔드포인트 확인
# 이벤트 등록       /events/ => create_event()
@event_router.post("/", status_code=status.HTTP_201_CREATED)
async def create_event(data: Event = Body(...), 
                       session = Depends(get_session)) -> dict:

 

 

 

2.요청 본문 전달 값 확인

→ 확인후 React에서 입력창을 만들어야함

 

class Event(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    title: str
    image: str
    description: str
    tags: List[str] = Field(sa_column=Column(JSON)) # JSON 형식으로 저장
    location: str

 

 

 

리액트 프로젝트에 이벤트 등록(src\event\Regist.jsx) 컴포넌트를 추가

해당하는 본문 전달값을 상태변수로 만들어야한다.

import { useState } from "react";


export default function Regist() {
    const [form, setForm] = useState({
        id: '',
        title: '',
        image: '',
        description: '',
        tags: '',
        location: ''
    });


    const { id, title, image, description, tags, location } = form;


    const handleChange = e => setForm({...form, [e.target.net]: e.target.value});


    return (
        <>
            <h1>이벤트 등록</h1>
            <input onChange={handleChange} value={id} type="number" name="id" placeholder="이벤트 ID를 입력하세요." />
            <input onChange={handleChange} value={title} type="text" name="title" placeholder="이벤트 제목을 입력하세요." />
            <input onChange={handleChange} value={image} type="text" name="image" placeholder="이벤트 이미지 URL을 입력하세요." />
            <input onChange={handleChange} value={description} type="text" name="description" placeholder="이벤트 설명을 입력하세요." />
            <input onChange={handleChange} value={tags} type="text" name="tags" placeholder="이벤트 관련 태그를 입력하세요." />
            <input onChange={handleChange} value={location} type="text" name="location" placeholder="이벤트 위치를 입력하세요." />
        </>
    );
}

 

 

 

axios 모듈을 추가

c:\javascript\my-app> npm install axios

c:\javascript\my-app> npm run dev

 

등록 버튼 추가 및 클릭시 API호출 기능 추가

import { useState } from "react";
import axios from "axios";


export default function Regist() {
    const [form, setForm] = useState({
        id: '',
        title: '',
        image: '',
        description: '',
        tags: '',
        location: ''
    });


    const { id, title, image, description, tags, location } = form;


    const handleChange = e => setForm({...form, [e.target.name]: e.target.value});
    const handleSubmit = e => {
        e.preventDefault();
        axios
            .post("http://localhost:8000/events/",
                {id, title, image, description, tags, location})
            .then(res => console.log(res))
            .catch(err => console.log(err));
    };


    return (
        <>
            <h1>이벤트 등록</h1>
            <form onSubmit={handleSubmit}>
                <input onChange={handleChange} value={id} type="number" name="id" placeholder="이벤트 ID를 입력하세요." />
                <input onChange={handleChange} value={title} type="text" name="title" placeholder="이벤트 제목을 입력하세요." />
                <input onChange={handleChange} value={image} type="text" name="image" placeholder="이벤트 이미지 URL을 입력하세요." />
                <input onChange={handleChange} value={description} type="text" name="description" placeholder="이벤트 설명을 입력하세요." />
                <input onChange={handleChange} value={tags} type="text" name="tags" placeholder="이벤트 관련 태그를 입력하세요." />
                <input onChange={handleChange} value={location} type="text" name="location" placeholder="이벤트 위치를 입력하세요." />
                <button type="submit">등록</button>
            </form>
        </>
    );
}

 

 

 

 

등록 요청시 CORS 오류 발생한다.

 

CORS(Cross-Origin Resource Sharing)란

쉽게 말해 다른 출처의 서버에 요청할 수 있게 허용하는 규칙이다.

React와 FastAPI의 포트가 다르기 때문에 출처가 다르므로 FastAPI에서 CORS 설정이 필요하다.

 

 

이때 정식 요청을 보내기전에 브라우저에서 예비요청을 보내게 되는데 이것을 Preflight요청 이라고 한다.

 

 

 

*** FAST API서버에 CORS 설정 추가

main.py파일 CORS 허용 로직 추가

 

 

버에서 CORS 설정을 추가한 후 이벤트 추가 요청을 정상적으로 완료하였다.

 

 

 

 

목록 조회하기

리액트 src\event\List.jsx 파일 생성

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const List = () => {
  const [events, setEvents] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get('http://localhost:8000/events/')
      .then((response) => {
        setEvents(response.data);
        setLoading(false);
      })
      .catch((err) => {
        setError('이벤트 데이터를 불러오는 데 실패했습니다.');
        setLoading(false);
      });
  }, []);

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>{error}</p>;

  return (
    <div>
      <h2>이벤트 목록</h2>
      {events.length === 0 ? (
        <p>이벤트가 없습니다.</p>
      ) : (
        <ul>
          {events.map((event) => (
            <li key={event.id} style={{ marginBottom: '20px' }}>
              <h3>{event.title}</h3>
              {event.image && <img src={event.image} alt={event.title} style={{ width: '200px' }} />}
              <p><strong>설명:</strong> {event.description}</p>
              <p><strong>위치:</strong> {event.location}</p>
              <p><strong>태그:</strong> {event.tags}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default List;

 

 

 

 

 

 

리액트 일반적인 라이프사이클 (클래스 컴포넌트)

  • 생성될때 (1번만 호출)
    • construcor : 클래스형 컴포넌트에서 constructor 함수 호출, 초기화 작업 수행
    • render 함수 호출 : return하는것들을 화면에 보여줌
    • componentDidMount : 랜더링시 호출되는 함수, 시간을 필요로하는 외부 연계
  • 업데이트 할 때 (수시로 호출)
    • New Props, setState() props 및 상태 변수 바뀔때 랜더링 → componentDidUpdate 메서드 호출

 

 

함수형 컴포넌트에서는 useEffect 함수를 사용한다.

 

useEffect(이펙트 함수,[])

  • 특정 조건이 되었을때 실행되는 함수이다. 매개변수 배열에 조건을 명시 가능하다. (의존 배열)
  • 의존 배열이 없으면 mount(기본적인 화면 출력되었을때), update 되었을때 실행된다.

 

 

 

라우터

페이지 이동을 구현하는데 사용하는 라이브러리이다. 페이지를 한꺼번에 가져와 새로고침이 되지 않기 때문에 특정 조건에 맞는 컴포넌트 출력한다.

 

설치방법

react-router-dom 설치

npm install react-router-dom

BrowserRouter 사용 → App.jsx

 

 

설정 후 아래 라우팅 관련 코드를 추가하고 주소 입력 시 라우팅 적용된 것을 확인할 수 있다.

  • http://localhost:5173/regist
  • http://localhost:5173/list
import { BrowserRouter, Route, Routes, Link } from "react-router-dom";
import List from "./event/List";
import Regist from "./event/Regist";


function App() {
    return (
        <>
            <BrowserRouter>
                <ul>
                    <li><Link to="/regist">등록</Link></li>
                    <li><Link to="/list">목록조회</Link></li>
                </ul>

                <Routes>
                    <Route path="/regist" element={<Regist />} />
                    <Route path="/list" element={<List />} />
                </Routes>
            </BrowserRouter>
        </>
    );
}
export default App;

 

 

 

가변 데이터 전달 방법

  1. 파라미터 : /profile/honggildong
  2. 쿼리 문자열 : /profile?name=honggildong

 

 

 

이벤트 상세 조회

/src/event/Detail.jsx 파일 추가

 

- 요구사항

  • 함수형 컴포넌트로 구현
  • 컴포넌트 이름 => Detail
  • 엔트포인트 => http://localhost:8000/events/{event_id}
  • event_id는 리액트 라우트의 파라미터로 전달받음
  • 이벤트 정보는 id, title, image, description, tags, location으로 구성
import React, { useEffect, useState } from 'react';

import { useParams } from 'react-router-dom';

import axios from 'axios';

const Detail = () => {

const { event_id } = useParams(); // URL 파라미터에서 event_id 추출

const [event, setEvent] = useState(null);

const [loading, setLoading] = useState(true);

const [error, setError] = useState(null);

useEffect(() => {

axios.get(`http://localhost:8000/events/${event_id}`)

.then((response) => {

setEvent(response.data);

setLoading(false);

})

.catch((err) => {

setError('이벤트 정보를 불러오는 데 실패했습니다.');

setLoading(false);

});

}, [event_id]);

if (loading) return <p>로딩 중...</p>;

if (error) return <p>{error}</p>;

if (!event) return <p>이벤트 정보가 없습니다.</p>;

return (

<div>

<h2>{event.title}</h2>

{event.image && <img src={event.image} alt={event.title} style={{ width: '300px' }} />}

<p><strong>설명:</strong> {event.description}</p>

<p><strong>위치:</strong> {event.location}</p>

<p><strong>태그:</strong> {event.tags}</p>

<p><strong>이벤트 ID:</strong> {event.id}</p>

</div>

);

};

export default Detail;

App.jsx 파일 내 상세조회 컴포넌트 라우터 추가

import { BrowserRouter, Route, Routes, Link } from "react-router-dom";

import List from "./event/List";

import Regist from "./event/Regist";

import Detail from "./event/Detail";

function App() {

return (

<>

<BrowserRouter>

<ul>

<li><Link to="/regist">등록</Link></li>

<li><Link to="/list">목록조회</Link></li>

</ul>

<Routes>

<Route path="/regist" element={<Regist />} />

<Route path="/list" element={<List />} />

<Route path="/detail/:event_id" element={<Detail />} />

</Routes>

</BrowserRouter>

</>

);

}

export default App;

 

 

 

목록 클릭시 이벤트 상세 내용 출력되도록 수정

 

List.jsx

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';

const List = () => {
  const [events, setEvents] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get('http://localhost:8000/events/')
      .then((response) => {
        setEvents(response.data);
        setLoading(false);
      })
      .catch((err) => {
        setError('이벤트 데이터를 불러오는 데 실패했습니다.');
        setLoading(false);
      });
  }, []);

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>{error}</p>;

  return (
    <div>
      <h2>이벤트 목록</h2>
      {events.length === 0 ? (
        <p>이벤트가 없습니다.</p>
      ) : (
        <ul>
          {events.map((event) => (
            <li key={event.id} style={{ marginBottom: '20px' }}>
              <h3><Link to={`/detail/${event.id}`}>{event.title}</Link></h3>
              {event.image && <img src={event.image} alt={event.title} style={{ width: '200px' }} />}
              <p><strong>설명:</strong> {event.description}</p>
              <p><strong>위치:</strong> {event.location}</p>
              <p><strong>태그:</strong> {event.tags}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default List;