지금까지 살펴 본, Frontend에 Backend를 연동 시키는 동작의 기본을 정리해 본다. 물론 여기서 Web scrapping이나 다른 기능과 연동하는 작업들이 크지만, 좋은 시작점이 된다는 것을 파악했다. 그 내용을 정리해 보자.

전체 구성 요약

  • Frontend: React + Vite + Tailwind CSS v4
  • Backend: FastAPI + SQLite + SQLAlchemy
  • 기능 요약:
    • 과제 목록 조회 (ProjectList)
    • 과제 등록/수정 (ProjectForm)
    • DB 연동 (등록된 과제 저장/수정/삭제)
    • API 연동 (CORS, RESTful)

✅ 3단계: 과제 화면 구성

📌 파일 생성

  • src/pages/ProjectList.tsx
  • src/pages/ProjectForm.tsx
  • src/App.tsx 라우팅 설정

📌 라우팅 프롬프트

import { BrowserRouter, Routes, Route } from "react-router-dom";
import ProjectList from "./pages/ProjectList";
import ProjectForm from "./pages/ProjectForm";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<ProjectList />} />
        <Route path="/form" element={<ProjectForm />} />
        <Route path="/form/:id" element={<ProjectForm />} />
      </Routes>
    </BrowserRouter>
  );
}

✅ 4단계: 백엔드 FastAPI 구축

📌 FastAPI 설치

pip install fastapi uvicorn sqlalchemy

📌 main.py 예시

python

CopyEdit

from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base
from models import Project as ProjectModel
from pydantic import BaseModel
from typing import List

# 데이터베이스 테이블 생성
Base.metadata.create_all(bind=engine)

# FastAPI 앱 생성
app = FastAPI()

# CORS 설정 (React 개발 서버와 연동)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # 필요시 도메인 추가
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# DB 세션 의존성
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Pydantic 모델 정의
class ProjectBase(BaseModel):
    title: str
    schedule: str
    url: str
    leader: str
    startTime: str
    endTime: str
    interval: str

class ProjectCreate(ProjectBase):
    pass

class ProjectOut(ProjectBase):
    id: int

    class Config:
        orm_mode = True

# API 라우터

@app.post("/projects", response_model=ProjectOut)
def create_project(project: ProjectCreate, db: Session = Depends(get_db)):
    db_project = ProjectModel(**project.dict())
    db.add(db_project)
    db.commit()
    db.refresh(db_project)
    return db_project

@app.get("/projects", response_model=List[ProjectOut])
def list_projects(db: Session = Depends(get_db)):
    return db.query(ProjectModel).all()

@app.get("/projects/{project_id}", response_model=ProjectOut)
def get_project(project_id: int, db: Session = Depends(get_db)):
    project = db.query(ProjectModel).filter(ProjectModel.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    return project

@app.put("/projects/{project_id}", response_model=ProjectOut)
def update_project(project_id: int, updated: ProjectCreate, db: Session = Depends(get_db)):
    project = db.query(ProjectModel).filter(ProjectModel.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    for key, value in updated.dict().items():
        setattr(project, key, value)
    db.commit()
    db.refresh(project)
    return project

@app.delete("/projects/{project_id}")
def delete_project(project_id: int, db: Session = Depends(get_db)):
    project = db.query(ProjectModel).filter(ProjectModel.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    db.delete(project)
    db.commit()
    return {"ok": True}

model.py

from sqlalchemy import Column, Integer, String
from database import Base

class Project(Base):
    __tablename__ = "projects"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String)
    schedule = Column(String)
    url = Column(String)
    leader = Column(String)
    startTime = Column(String)
    endTime = Column(String)
    interval = Column(String)

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "sqlite:///./projects.db"

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
Base = declarative_base()

📌 실행

uvicorn main:app --reload

✅ 5단계: DB 모델, CRUD API 구성

📌 SQLite + SQLAlchemy 모델 정의 (예: Project 모델)

📌 주요 API

  • GET /projects: 목록 조회
  • POST /projects: 등록
  • GET /projects/{id}: 상세 조회
  • PUT /projects/{id}: 수정
  • DELETE /projects/{id}: 삭제

✅ 6단계: 프론트엔드 → 백엔드 연동

  • ProjectList.tsx에서 API 호출 후 리스트 렌더링
  • ProjectForm.tsx에서 등록/수정 처리 후 목록으로 라우팅

📌 예시

fetch("http://localhost:8000/projects", { method: "POST", ... })

``


✅ 7단계: UX 향상 + 기능 추가

  • 과제 클릭 시 수정 가능
  • 삭제 기능 추가
  • 기본값 설정 (06:00, 21:00, 1시간)

🧾 최종 상태 요약

구분 구현 완료 항목
UI Tailwind 기반 List + Form
기능 과제 CRUD
API FastAPI 기반 REST API
DB SQLite + SQLAlchemy
연동 React ↔ FastAPI 연결
배포 (추후 단계로 가능: Docker, Cloud Run 등)

 

ChatGPT와 같은 AI만 쓰다보면, 나의 학습은 점점 멀어 질 수 있다. 반대로도 걸어가 본다는 의미로 이야기한 부분을 상세히 살펴 보자. 나와 관련된 부분이 있기 때문에 학습 동기는 높을 수 밖에 없다.

 

React  App.tsx

You said:

"나는 React, Tailwind, Typscript에 대해서 잘 몰라. 다음 코드에 대해서 한줄 씩 쉽게 설명해줘."

 

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <h1 className="text-4xl font-bold text-blue-600">
        Tailwind 적용 성공!
      </h1>
    </div>
  );
}
ChatGPT said:

물론입니다! 아래는 해당 코드를 React, Tailwind, Typescript를 처음 접하는 분도 쉽게 이해할 수 있도록 한 줄씩 설명한 것입니다.

import { useState } from 'react'

➡️ React에서 제공하는 기능 중 하나인 useState를 가져옵니다.
(※ 이 코드에서는 실제로 사용하지 않지만, 보통 컴포넌트 안에서 상태(state)를 관리할 때 사용됩니다.)

아래는 React의 대표적인 기능들을 간단한 설명과 함께 표로 정리한 것입니다. 초보자도 이해할 수 있도록 실제 쓰임새 중심으로 소개할게요.

✅ React 주요 기능 요약표

기능명 설명 사용 예 비고
useState 상태(state)를 관리하는 데 사용됨. 사용자가 클릭하거나 입력할 때 데이터를 저장. const [count, setCount] = useState(0) 함수형 컴포넌트 전용
useEffect 컴포넌트가 화면에 나타날 때값이 변경될 때 동작하게 함. useEffect(() => { fetchData() }, []) 비동기 API 호출, 구독 처리 등
props 부모 컴포넌트가 자식에게 값 전달할 때 사용됨. <Button label="확인" /> 읽기 전용 (수정 불가)
useRef DOM에 직접 접근하거나 값 저장용으로 사용됨 (렌더링과 무관한 값 저장). const inputRef = useRef(null) 포커스 제어, 이전 값 기억 등에 사용
useContext 컴포넌트 여러 개에서 공통 데이터 공유시 사용 (예: 다크모드, 로그인 정보 등). const theme = useContext(ThemeContext) Redux 없이 전역 상태 공유 가능
useReducer useState보다 복잡한 상태 로직을 관리할 때 사용됨. useReducer(reducer, initialState) Redux와 비슷한 구조
useMemo 계산이 무거운 값을 메모이제이션해서 성능 최적화함. useMemo(() => calcHeavy(val), [val]) 동일한 계산 반복 방지
useCallback 함수 재생성을 방지해서 렌더링 최적화. useCallback(() => doSomething(), [deps]) 자식 컴포넌트 성능 향상
useLayoutEffect useEffect와 유사하지만 화면 그리기 전에 실행됨. useLayoutEffect(() => {...}) DOM 위치 조정 등
React.memo 컴포넌트 자체를 메모이제이션해서 불필요한 리렌더링 방지. export default React.memo(MyComponent) 고성능 컴포넌트에 유용

📌 기본 개념 정리 (보너스)

  • 컴포넌트(Component): UI의 재사용 가능한 단위 (function MyButton() { return <button>Click</button>; })
  • JSX: HTML과 비슷한 문법으로 UI를 선언 (<div>Hello</div>)
  • Virtual DOM: 변경된 부분만 실제 화면에 적용하는 방식 → 빠른 렌더링
import reactLogo from './assets/react.svg'

➡️ 현재 프로젝트 폴더의 assets 폴더에 있는 React 로고 이미지를 불러옵니다.
(※ 이 이미지도 이 코드에서는 사용되지 않지만, 보통 화면에 보여줄 때 쓰입니다.)

import viteLogo from '/vite.svg'

➡️ Vite라는 개발 도구의 로고 이미지도 불러옵니다. /vite.svg는 루트 경로를 기준으로 합니다.


import './App.css'

➡️ App.css라는 스타일 파일(CSS)을 불러옵니다. 이 파일에 정의된 스타일이 이 컴포넌트에 적용됩니다.

function App() {

➡️ App이라는 이름의 함수형 컴포넌트를 정의합니다. React에서는 UI를 구성하는 기본 단위입니다.


 return (

➡️ 이 함수가 어떤 화면을 보여줄 것인지를 JSX라는 문법을 사용해서 정의합니다. (HTML과 비슷해 보입니다)

    `<div className="min-h-screen flex items-center justify-center bg-gray-100">`

➡️ <div>는 화면을 구성하는 상자이고, className에 들어있는 건 Tailwind CSS 클래스들입니다:

  • min-h-screen: 화면 높이만큼 최소 높이를 줍니다.
  • flex: 내용을 가로/세로 정렬하기 위해 flexbox 레이아웃을 사용합니다.
  • items-center: 수직 방향으로 가운데 정렬합니다.
  • justify-center: 수평 방향으로 가운데 정렬합니다.
  • bg-gray-100: 배경색을 연한 회색으로 지정합니다.
      `<h1 className="text-4xl font-bold text-blue-600">`

➡️ 제목을 나타내는 <h1> 태그입니다. 역시 Tailwind 클래스로 꾸밉니다:

  • text-4xl: 글자 크기를 크게 설정합니다.
  • font-bold: 글자를 굵게 표시합니다.
  • text-blue-600: 글자 색을 진한 파란색으로 지정합니다.
        Tailwind 적용 성공!

➡️ 화면에 표시될 텍스트입니다. 실제로 사용자가 보는 메시지입니다.

      </h1>     </div>   );

➡️ 태그를 닫고, return 구문도 닫습니다.

`export default App;`

➡️ App 컴포넌트를 다른 파일에서 사용할 수 있도록 외부에 공개합니다. 예: main.tsx에서 이 컴포넌트를 사용함.

필요하다면, 각 Tailwind 클래스도 하나하나 풀어서 예시와 함께 더 설명해드릴 수 있어요.

 

Tailwind 설명

You said:

Tailwind 클래스도 설명해줘. 어떤 파일을 봐야하는 거야?

ChatGPT said:

좋습니다! 먼저 질문을 두 부분으로 나누어서 답변드릴게요.

✅ 1. Tailwind 클래스 설명 (예시 포함)

아래는 코드에 등장한 Tailwind CSS 클래스들에 대한 설명입니다:

클래스명 의미
min-h-screen 최소 높이를 화면 전체 높이(100vh)로 설정해요. 화면 전체를 채우는 데 자주 사용됩니다.
flex Flexbox 레이아웃을 적용해서 내부 요소를 정렬하거나 배치할 수 있어요.
items-center 세로 방향(교차축)으로 요소를 가운데 정렬해요.
justify-center 가로 방향(주축)으로 요소를 가운데 정렬해요.
bg-gray-100 배경색을 회색 계열 중 가장 밝은 색상으로 지정해요.
text-4xl 글씨 크기를 4단계 크게 설정해요. (Tailwind는 text-sm, text-lg, text-2xl 등 다양한 크기 제공)
font-bold 글씨를 굵게 보여줘요.
text-blue-600 글씨 색상을 진한 파란색 계열 중 하나로 설정해요.

Tailwind에서는 이렇게 기능별 클래스를 조합해서 스타일을 만듭니다.
➡️ CSS를 작성하지 않고도 HTML 속성으로 스타일을 적용할 수 있는 방식이에요.


✅ 2. Tailwind 클래스 전체 목록은 어디서 보나요?

📄 공식 문서 보기

Tailwind는 공식 문서를 아주 잘 정리해두었어요:

예를 들어:

✅ 3. 내 프로젝트에서 설정 파일은 어디 있나요?

Tailwind 설정은 보통 프로젝트 루트에 있는 다음 파일에서 확인해요:

tailwind.config.js 또는 tailwind.config.ts

이 파일에서 다음을 확인할 수 있어요:

  • 사용할 색상, 폰트 크기 등 커스터마이징
  • 어떤 파일에서 Tailwind 클래스를 사용할지 지정

예시:

// tailwind.config.js
module.exports = {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], // 사용 위치
  theme: {
    extend: {},
  },
  plugins: [],
}

실재로 쓰인 tailwind.config.ts파일의 내용

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

v4부터는 vite와 연계한다고 해서 다음 사항이 포함되어 있었다.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(), 
    tailwindcss(),
  ],
});

src/index.css 파일에 다음과 같이 되어 있다면:

`@import "tailwindcss";`

이건 Tailwind CSS v4에서 새롭게 도입된 방식이에요. 이전 버전과 다르게, 더 간단한 방식으로 설정이 가능해졌습니다.

React 조금 더 들어가 보기

You said:

React에서 제공하는 기능 중 하나인 useState를 가져옵니다. (※ 이 코드에서는 실제로 사용하지 않지만, 보통 컴포넌트 안에서 상태(state)를 관리할 때 사용됩니다.)

여기서 React의 대표적인 기능들을 더 소개해줘. 표로 정리 해주면 더 좋겠어.

ChatGPT said:

물론입니다!

필요하신 항목이 있으면 코드 예시나 시각화도 함께 설명드릴게요.

 

react-router-dom 살펴 보기

You said:

react 대신 react-router-dom도 쓰이던데 비슷한 것들이 있어?

ChatGPT said:

좋은 질문이에요!
reactReact의 핵심 라이브러리이고, react-router-domReact에서 페이지 이동(라우팅)을 구현하기 위한 라이브러리입니다.

아래에 react와 비슷하게 사용되며 함께 조합되는 대표적인 라이브러리들을 목적별로 표로 정리해드릴게요.

 

✅ React와 함께 자주 쓰이는 대표 라이브러리 비교표

라이브러리 이름 역할 및 설명 주로 사용하는 기능 비고
react UI 구성의 핵심 라이브러리 useState, useEffect, JSX 필수, 앱의 뼈대
react-router-dom 페이지 이동(라우팅) 처리 <Route>, <Link>, useNavigate SPA에서 URL 기반 화면 전환
react-dom React 컴포넌트를 실제 HTML DOM에 연결 ReactDOM.createRoot() 브라우저용 렌더링 전용
react-redux 전역 상태(state) 관리 useSelector, useDispatch 대규모 앱에서 유용
@reduxjs/toolkit Redux를 더 쉽게 사용할 수 있게 해주는 도구 createSlice, configureStore Redux 표준 툴킷
react-query 또는 @tanstack/react-query 서버 데이터 캐싱, 로딩, 에러 처리 자동화 useQuery, useMutation API 호출 + 캐싱 최적화
axios API 통신 (HTTP 요청) axios.get(), axios.post() fetch보다 더 편리함
formik + yup 입력 폼 처리 + 유효성 검사 useFormik, validationSchema 복잡한 폼 처리 시 유용
react-hook-form 성능 중심의 폼 처리 라이브러리 useForm, Controller 가볍고 빠름
framer-motion 애니메이션 처리 <motion.div>, animate Tailwind와 함께 사용 가능
classnames 조건부 CSS 클래스 적용 classNames('a', { b: isActive }) Tailwind에서 자주 활용
react-helmet 또는 react-helmet-async HTML <head> 관리 (타이틀, 메타태그 등) <Helmet><title>...</title></Helmet> SEO 대응 시 사용

 

📌 react-router-dom은 언제 쓰나요?

React는 기본적으로 한 페이지로 동작하는 SPA(Single Page Application) 구조입니다.
react-router-dom을 사용하면, 아래처럼 URL에 따라 다른 화면을 보여줄 수 있어요.

예시:

import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>
        </BrowserRouter>
    );
}

➡️ 이런 식으로 화면(컴포넌트)을 URL에 따라 나눌 수 있고, <Link to="/about">About</Link> 같은 내부 링크도 제공합니다.

 

돌아 보며

기본적은 React+Vite+Tailwind v4를 기본 설정하면서 관련된 코드들을 살펴 보았다. 이를 기반으로 React Frontend를 구성하는 기본 내용들을 살펴 보았다. 

+ Recent posts