React + FastAPI๋กœ ์›น ๊ฒŒ์ž„ ๋งŒ๋“ค๊ธฐ #1 - ํ”„๋กœ์ ํŠธ ์„ธํŒ…

React + Python FastAPI๋กœ
์›น ๊ฒŒ์ž„ ๋งŒ๋“ค๊ธฐ #1

ํ”„๋กœ์ ํŠธ ์„ธํŒ… โ€” ํด๋” ๊ตฌ์กฐ, Vite, FastAPI ํ™˜๊ฒฝ ๊ตฌ์„ฑ๊นŒ์ง€

1 ์™œ ์ด ๊ธฐ์ˆ  ์Šคํƒ์„ ๊ณจ๋ž๋Š”๊ฐ€

์ด๋ฒˆ์— ๋งŒ๋“ค ๊ฒŒ์ž„์€ ์žฅ์• ๋ฌผ์„ ํ”ผํ•ด ์ตœ๋Œ€ํ•œ ์˜ค๋ž˜ ๋ฒ„ํ‹ฐ๋Š” ๋ฌดํ•œ ๋‹ฌ๋ฆฌ๊ธฐ ๊ฒŒ์ž„์ด๋‹ค. ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์†๋„๊ฐ€ ๋นจ๋ผ์ง€๊ณ , ๊ธฐ๋ก์„ ๋‹‰๋„ค์ž„๊ณผ ํ•จ๊ป˜ ๋ฆฌ๋”๋ณด๋“œ์— ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

๊ฒŒ์ž„ ๋กœ์ง ์ž์ฒด๋Š” JavaScript๋กœ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ์ ์ˆ˜ ์ €์žฅ๊ณผ ๋ฆฌ๋”๋ณด๋“œ๋ฅผ ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ๊ณต์œ ํ•˜๋ ค๋ฉด ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ํ”„๋ก ํŠธ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

โš›๏ธ React Frontend

๊ฒŒ์ž„ ํ™”๋ฉด, UI, ์บ๋ฆญํ„ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ ๋ชจ๋“  ํ™”๋ฉด์„ ๋‹ด๋‹น. ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ ์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๋‹ค.

โšก Vite ๋นŒ๋“œ ๋„๊ตฌ

Create React App๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฅธ ๊ฐœ๋ฐœ ์„œ๋ฒ„. ์ €์žฅํ•˜๋ฉด ์ฆ‰์‹œ ๋ฐ˜์˜(HMR)๋˜์–ด ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค.

๐Ÿ FastAPI Backend

Python ๊ธฐ๋ฐ˜ ์›น ํ”„๋ ˆ์ž„์›Œํฌ. ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๊ณ , ์ž๋™์œผ๋กœ API ๋ฌธ์„œ(/docs)๋ฅผ ๋งŒ๋“ค์–ด์ค˜์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ํŽธํ•˜๋‹ค.

๐Ÿฆ„ Uvicorn ์„œ๋ฒ„

FastAPI๋ฅผ ์‹คํ–‰์‹œ์ผœ์ฃผ๋Š” ASGI ์„œ๋ฒ„. --reload ์˜ต์…˜์œผ๋กœ ์ฝ”๋“œ ์ˆ˜์ • ์‹œ ์ž๋™ ์žฌ์‹œ์ž‘๋œ๋‹ค.

๐Ÿ’ก
ํ•ต์‹ฌ ์•„์ด๋””์–ด: ๊ฒŒ์ž„ ํ™”๋ฉด(React)๊ณผ ์ ์ˆ˜ ์ €์žฅ(FastAPI)์„ ๋ถ„๋ฆฌํ•˜๋ฉด ๋‚˜์ค‘์— ์–ด๋А ์ชฝ์ด๋“  ๋…๋ฆฝ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. React๋Š” API ํ˜ธ์ถœ๋งŒ ํ•˜๋ฉด ๋˜๊ณ , FastAPI๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋งŒ ๋‹ด๋‹นํ•œ๋‹ค.

2 ์ „์ฒด ํด๋” ๊ตฌ์กฐ

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ๋ฅผ D:\Lee_project\neon-runner\๋กœ ์žก๊ณ , ํ”„๋ก ํŠธ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

D:\Lee_project\neon-runner\
โ”œโ”€โ”€ frontend\     โ† React + Vite ๊ฒŒ์ž„ ํ™”๋ฉด
โ”‚   โ”œโ”€โ”€ src\
โ”‚   โ”‚   โ”œโ”€โ”€ App.jsx    โ† ๊ฒŒ์ž„ ๋ฉ”์ธ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ””โ”€โ”€ main.jsx   โ† React ์ง„์ž…์ 
โ”‚   โ”œโ”€โ”€ index.html
โ”‚   โ”œโ”€โ”€ package.json
โ”‚   โ””โ”€โ”€ vite.config.js
โ””โ”€โ”€ backend\     โ† Python FastAPI ์ ์ˆ˜ ์„œ๋ฒ„
    โ”œโ”€โ”€ venv\        โ† Python ๊ฐ€์ƒํ™˜๊ฒฝ
    โ”œโ”€โ”€ main.py     โ† FastAPI ์„œ๋ฒ„ ์ฝ”๋“œ
    โ””โ”€โ”€ scores.json  โ† ์ ์ˆ˜ ์ €์žฅ ํŒŒ์ผ (์ž๋™์ƒ์„ฑ)
๐Ÿ“
์™œ frontend / backend๋ฅผ ๋‚˜๋ˆด๋‚˜?
ํ•˜๋‚˜์˜ ํด๋”์— ๋‹ค ๋„ฃ์œผ๋ฉด Python ํŒจํ‚ค์ง€์™€ JS ํŒจํ‚ค์ง€๊ฐ€ ์„ž์—ฌ์„œ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค. ์—ญํ• ๋ณ„๋กœ ํด๋”๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด ๋‚˜์ค‘์— ๋ฐฐํฌํ•  ๋•Œ๋„ ๊ฐ๊ฐ ๋…๋ฆฝ์ ์œผ๋กœ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

3 PowerShell ์‹คํ–‰ ์ •์ฑ… ํ•ด์ œ

Windows์—์„œ PowerShell๋กœ npm ๋ช…๋ น์–ด๋ฅผ ์ฒ˜์Œ ์‹คํ–‰ํ•˜๋ฉด ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค.

์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€
npm : ์ด ์‹œ์Šคํ…œ์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ
C:\Program Files\nodejs\npm.ps1 ํŒŒ์ผ์„ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    + CategoryInfo : ๋ณด์•ˆ ์˜ค๋ฅ˜: (:) [], PSSecurityException

Windows PowerShell์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์„ ๋ง‰์•„๋†“๋Š”๋‹ค. npm์€ ๋‚ด๋ถ€์ ์œผ๋กœ .ps1 ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ •์ฑ…์„ ์™„ํ™”ํ•ด์•ผ ํ•œ๋‹ค.

๊ด€๋ฆฌ์ž ๊ถŒํ•œ PowerShell์—์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

PowerShell (๊ด€๋ฆฌ์ž)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
๐Ÿ”‘
์˜ต์…˜ ์„ค๋ช…
-Scope CurrentUser โ€” ํ˜„์žฌ ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ์ ์šฉ (์‹œ์Šคํ…œ ์ „์ฒด ๋ณ€๊ฒฝ X)
RemoteSigned โ€” ๋กœ์ปฌ ์Šคํฌ๋ฆฝํŠธ๋Š” ํ—ˆ์šฉ, ์ธํ„ฐ๋„ท์—์„œ ๋ฐ›์€ ์Šคํฌ๋ฆฝํŠธ๋Š” ์„œ๋ช… ํ•„์š”
โ†’ ๋ณด์•ˆ์„ ์œ ์ง€ํ•˜๋ฉด์„œ npm์ด ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์†Œํ•œ์˜ ์„ค์ •์ด๋‹ค.

4 React + Vite ํ”„๋ก ํŠธ์—”๋“œ ์„ธํŒ…

frontend\ ํด๋” ์•ˆ์—์„œ Vite ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. .์„ ๊ฒฝ๋กœ๋กœ ์“ฐ๋ฉด ํ˜„์žฌ ํด๋”์— ๋ฐ”๋กœ ์ƒ์„ฑ๋œ๋‹ค.

PowerShell
# frontend ํด๋”๋กœ ์ด๋™
cd D:\Lee_project\neon-runner\frontend

# Vite + React ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (ํ˜„์žฌ ํด๋”์—)
npm create vite@latest . -- --template react

# ํŒจํ‚ค์ง€ ์„ค์น˜
npm install

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
npm run dev

์„ค์น˜ ํ›„ http://localhost:5173 ์—์„œ React ๊ธฐ๋ณธ ํ™”๋ฉด์ด ๋œจ๋ฉด ์„ฑ๊ณต์ด๋‹ค.

๊ธฐ๋ณธ์œผ๋กœ ์ƒ์„ฑ๋œ ํŒŒ์ผ ์ค‘ ๊ฒŒ์ž„์— ํ•„์š” ์—†๋Š” ํŒŒ์ผ๋“ค์„ ์ •๋ฆฌํ•œ๋‹ค.

PowerShell โ€” ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ ์‚ญ์ œ
Remove-Item src\App.css
Remove-Item src\index.css
Remove-Item -Recurse src\assets

๊ทธ๋ฆฌ๊ณ  App.jsx์™€ main.jsx๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
src/App.jsx โ€” ์ดˆ๊ธฐํ™”
export default function App() {
  return (
    <div>
      <h1>NEON RUNNER</h1>
    </div>
  )
}
โœ…
Vite๋ฅผ ์“ฐ๋Š” ์ด์œ : ๊ธฐ์กด Create React App(CRA)์€ Webpack ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐœ๋ฐœ ์„œ๋ฒ„๊ฐ€ ๋А๋ฆฌ๊ณ  ์„ค์ •์ด ๋ณต์žกํ–ˆ๋‹ค. Vite๋Š” ESM(ES Module) ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•ด์„œ ์„œ๋ฒ„ ์‹œ์ž‘์ด ์ˆ˜์‹ญ ๋ฐฐ ๋น ๋ฅด๊ณ , HMR(์ฝ”๋“œ ์ €์žฅ ์ฆ‰์‹œ ๋ฐ˜์˜)๋„ ํ›จ์”ฌ ๋น ๋ฅด๋‹ค.

5 Python FastAPI ๋ฐฑ์—”๋“œ ์„ธํŒ…

์ ์ˆ˜ ์ €์žฅ๊ณผ ๋ฆฌ๋”๋ณด๋“œ ์กฐํšŒ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” API ์„œ๋ฒ„๋ฅผ Python์œผ๋กœ ๋งŒ๋“ ๋‹ค.

๋จผ์ € ๊ฐ€์ƒํ™˜๊ฒฝ(venv)์„ ๋งŒ๋“ ๋‹ค. ๊ฐ€์ƒํ™˜๊ฒฝ์€ ํ”„๋กœ์ ํŠธ๋ณ„๋กœ Python ํŒจํ‚ค์ง€๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ ํŒจํ‚ค์ง€์™€ ์ถฉ๋Œํ•˜์ง€ ์•Š๋Š”๋‹ค.

PowerShell โ€” ์ƒˆ ํ„ฐ๋ฏธ๋„
# backend ํด๋”๋กœ ์ด๋™
cd D:\Lee_project\neon-runner\backend

# ๊ฐ€์ƒํ™˜๊ฒฝ ์ƒ์„ฑ
python -m venv venv

# ๊ฐ€์ƒํ™˜๊ฒฝ ํ™œ์„ฑํ™” (์•ž์— (venv) ํ‘œ์‹œ ๋œจ๋ฉด ์„ฑ๊ณต)
venv\Scripts\activate

# FastAPI + ์„œ๋ฒ„(uvicorn) ์„ค์น˜
pip install fastapi uvicorn
๐Ÿ’ก
์™œ ๊ฐ€์ƒํ™˜๊ฒฝ์„ ์“ฐ๋‚˜?
์ „์—ญ Python ํ™˜๊ฒฝ์— ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๋ฉด ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๋ฒ„์ „ ์ถฉ๋Œ์ด ๋‚  ์ˆ˜ ์žˆ๋‹ค. venv๋กœ ๊ฒฉ๋ฆฌํ•˜๋ฉด ์ด ํ”„๋กœ์ ํŠธ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ‚ค์ง€ ๋ฒ„์ „์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ backend\main.py๋ฅผ ๋งŒ๋“ ๋‹ค. ์ด ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ API ์„œ๋ฒ„ ์ „์ฒด๋‹ค.

backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import json
import os

app = FastAPI()

# React ๊ฐœ๋ฐœ์„œ๋ฒ„(5173)์—์„œ ์˜ค๋Š” ์š”์ฒญ ํ—ˆ์šฉ
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_methods=["*"],
    allow_headers=["*"],
)

DB_FILE = "scores.json"

def load_scores():
    if not os.path.exists(DB_FILE):
        return []
    with open(DB_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_scores(scores):
    with open(DB_FILE, "w", encoding="utf-8") as f:
        json.dump(scores, f, ensure_ascii=False, indent=2)

# ์ ์ˆ˜ ๋ฐ์ดํ„ฐ ํ˜•ํƒœ ์ •์˜
class ScoreEntry(BaseModel):
    nickname: str
    score: int
    time: float

# GET /leaderboard โ€” ์ƒ์œ„ 20๊ฐœ ์ ์ˆ˜ ์กฐํšŒ
@app.get("/leaderboard")
def get_leaderboard():
    scores = load_scores()
    return scores[:20]

# POST /score โ€” ์ ์ˆ˜ ์ €์žฅ ํ›„ ์ˆœ์œ„ ๋ฐ˜ํ™˜
@app.post("/score")
def post_score(entry: ScoreEntry):
    scores = load_scores()
    scores.append(entry.model_dump())
    scores.sort(key=lambda x: x["score"], reverse=True)
    save_scores(scores)
    rank = next(i+1 for i, s in enumerate(scores)
                 if s["nickname"] == entry.nickname
                 and s["score"] == entry.score)
    return {"rank": rank, "total": len(scores)}

์„œ๋ฒ„ ์‹คํ–‰:

PowerShell
uvicorn main:app --reload

# ์„ฑ๊ณต ์‹œ ์ถœ๋ ฅ:
# INFO:     Uvicorn running on http://127.0.0.1:8000

http://127.0.0.1:8000/leaderboard ์—์„œ []๊ฐ€ ๋ณด์ด๋ฉด API ์ •์ƒ ๋™์ž‘์ด๋‹ค. http://127.0.0.1:8000/docs์—์„œ๋Š” ์ž๋™ ์ƒ์„ฑ๋œ API ๋ฌธ์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

6 CORS ์„ค์ •์ด ์™œ ํ•„์š”ํ•œ๊ฐ€

React๋Š” localhost:5173์—์„œ ์‹คํ–‰๋˜๊ณ , FastAPI๋Š” localhost:8000์—์„œ ์‹คํ–‰๋œ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ํฌํŠธ(์ถœ์ฒ˜)๋กœ ๋ณด๋‚ด๋Š” ์š”์ฒญ์„ ์ฐจ๋‹จํ•œ๋‹ค. ์ด๊ฒƒ์ด CORS(Cross-Origin Resource Sharing) ์ •์ฑ…์ด๋‹ค.

backend/main.py โ€” CORS ์„ค์ • ๋ถ€๋ถ„
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # React ์ฃผ์†Œ๋งŒ ํ—ˆ์šฉ
    allow_methods=["*"],                       # GET, POST ๋“ฑ ๋ชจ๋‘ ํ—ˆ์šฉ
    allow_headers=["*"],                       # ๋ชจ๋“  ํ—ค๋” ํ—ˆ์šฉ
)
โš ๏ธ
๊ฐœ๋ฐœ ํ™˜๊ฒฝ vs ๋ฐฐํฌ ํ™˜๊ฒฝ: ์ง€๊ธˆ์€ ๊ฐœ๋ฐœ ํŽธ์˜๋ฅผ ์œ„ํ•ด localhost:5173๋งŒ ํ—ˆ์šฉํ–ˆ๋‹ค. ๋‚˜์ค‘์— ์‹ค์ œ ์„œ๋ฒ„์— ๋ฐฐํฌํ•  ๋•Œ๋Š” allow_origins๋ฅผ ์‹ค์ œ ๋„๋ฉ”์ธ์œผ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

7 ์ง„ํ–‰ ์ƒํ™ฉ & ๋‹ค์Œ ํŽธ ์˜ˆ๊ณ 

  • ์™„๋ฃŒ D:\Lee_project\neon-runner\ ํด๋” ๊ตฌ์กฐ ์ƒ์„ฑ
  • ์™„๋ฃŒ PowerShell ์‹คํ–‰ ์ •์ฑ… ํ•ด์ œ
  • ์™„๋ฃŒ React + Vite ํ”„๋ก ํŠธ์—”๋“œ ์„ธํŒ… (localhost:5173)
  • ์™„๋ฃŒ Python ๊ฐ€์ƒํ™˜๊ฒฝ + FastAPI ์„ธํŒ… (localhost:8000)
  • ์™„๋ฃŒ ์ ์ˆ˜ ์ €์žฅ / ๋ฆฌ๋”๋ณด๋“œ API ๊ธฐ๋ณธ ๊ตฌ์กฐ ์™„์„ฑ
  • ๋‹ค์ŒํŽธ Canvas๋กœ ๊ฒŒ์ž„ ๋ฐฐ๊ฒฝ + ์บ๋ฆญํ„ฐ ๊ทธ๋ฆฌ๊ธฐ
  • ์˜ˆ์ • ์žฅ์• ๋ฌผ ์ƒ์„ฑ + ์ถฉ๋Œ ๊ฐ์ง€
  • ์˜ˆ์ • ์ ์ˆ˜ ์‹œ์Šคํ…œ + FastAPI ์—ฐ๋™

์ด๋ฒˆ ํŽธ์€ ๊ฒŒ์ž„ ์ฝ”๋“œ๋ณด๋‹ค ํ™˜๊ฒฝ ์„ธํŒ…์ด ์ฃผ์˜€๋‹ค.

์„ธํŒ…์ด ์ž˜ ๋˜์–ด์žˆ์–ด์•ผ ๋‚˜์ค‘์— ํ—ค๋งค์ง€ ์•Š๋Š”๋‹ค.

๋‹ค์Œ ํŽธ์—์„œ๋Š” ๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ฒŒ์ž„ ํ™”๋ฉด์„ ๊ทธ๋ฆฐ๋‹ค.

โ–ถ ๋‹ค์ŒํŽธ: Canvas๋กœ ์บ๋ฆญํ„ฐ์™€ ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๊ธฐ