챗봇 공부 노트

[10편] FastAPI 백엔드 분리 & 프론트엔드 연동

frontend-diary-log 2026. 1. 26. 00:10

📘 Aura_AI Day 10

FastAPI 백엔드 분리 & 프론트엔드 연동 (노베이스 A~Z)

이 글은 **“FastAPI가 뭔지도 모르던 상태”에서
Day 10 작업을 처음부터 끝까지 이해하고,
직접 같은 구조를 다시 만들 수 있게” 쓰였습니다.


Part 1. Day 10의 정체부터 이해하기


0️⃣ Day 10은 “무엇을 만드는 날”인가요?

Day 10은 기능 개발 날이 아닙니다.
아키텍처 전환일입니다.

Day 1 ~ Day 9까지의 상태

  • Next.js App Router
  • Auth.js 인증
  • Zustand 전역 상태
  • 회원가입 / 로그인 완료
  • UI 중심 개발

👉 이 단계에서는 프론트 혼자서도 충분했습니다.


Day 10부터 바뀌는 점

이제 앞으로 만들 것들:

  • 날씨 데이터 가공
  • 사용자 조건 기반 추천
  • GPT 프롬프트 조합
  • 대화 기록 요약

이건 공통점이 있습니다.

UI가 아니라 “데이터 처리 로직” 중심

그래서 Day 10의 목표는 단 하나입니다.

프론트엔드와 독립된 “진짜 백엔드”를 하나 만들고
둘이 네트워크로 대화하게 만들기


1️⃣ 왜 Next.js API Route로 계속 안 갔나요?

❓ 흔한 질문

“이미 Next.js API Route 쓰고 있었는데
굳이 FastAPI를 또 만들 필요 있나요?”

✅ 결론

지금 시점에서는 FastAPI로 분리하는 게 맞습니다.


이유 1. 기술의 역할이 다르기 때문

영역적합한 기술

UI / 화면 / 상태 Next.js
인증 / 라우팅 Next.js
데이터 가공 / AI Python
GPT / 추천 로직 Python

👉 프론트가 잘하는 것과, 백엔드가 잘하는 걸 나눈 것


이유 2. GPT / 데이터 생태계

Python 쪽에는:

  • Pandas
  • LangChain
  • OpenAI SDK
  • 수많은 데이터 처리 라이브러리

👉 JS로도 가능하지만 비효율적


🎤 면접용 한 문장 (중요)

“Aura_AI의 핵심 로직은 GPT 프롬프트와 데이터 가공이기 때문에,
프론트엔드는 UI에 집중하고 백엔드는 Python 기반으로 분리했습니다.”


2️⃣ “프론트 안에 백엔드 폴더를 만든다”는 게 이상한가요?

❌ 아닙니다.
오히려 실무에서도 가장 흔한 초기 구조입니다.

이유

  • 하나의 레포로 관리
  • 배포 전까지 관리 편함
  • 나중에 백엔드만 분리 가능

👉 지금 이 타이밍이 정확히 맞음


3️⃣ Day 10에서 “일부러” 안 한 것들

이게 중요합니다.

오늘 안 한 것 ❌

  • DB 연결
  • GPT 호출
  • 복잡한 추천 로직
  • JWT / 쿠키 인증

오늘 한 것 ✅

  • FastAPI 서버 실행
  • CORS 문제 해결
  • 프론트 → 백엔드 요청
  • Header 기반 유저 식별
  • 요청/응답 구조 고정

👉 “길부터 닦은 날”


4️⃣ 전체 그림 먼저 보기 (아주 중요)

Day 10 이후의 데이터 흐름은 이겁니다.

[Next.js UI]
   ↓ fetch
(Header: x-user-id)
   ↓
[FastAPI]
   ↓
(가공 / 처리)
   ↓
[Response JSON]

아직:

  • GPT ❌
  • DB ❌

하지만:

  • 통신은 완성

5️⃣ 개발 환경 준비 (노베이스 기준)

5-1. Python 확인

python --version

권장: Python 3.10 이상


5-2. backend 폴더 생성

mkdir backend
cd backend

이 시점부터:

  • Next.js = 프론트
  • backend 폴더 = 백엔드

5-3. 패키지 설치

pip install fastapi uvicorn python-dotenv

각각 왜 필요한가요?

  • fastapi
    • API 서버 프레임워크
    • 라우터, 요청/응답 구조 제공
  • uvicorn
    • FastAPI를 실행시켜주는 서버
    • uvicorn main:app --reload
  • python-dotenv
    • .env 파일 읽기
    • GPT Key / DB 대비

6️⃣ Day 10 백엔드 폴더 구조 (완성형)

backend/
 ├─ main.py                # 서버 시작점
 ├─ routers/               # API 엔드포인트
 │   ├─ health.py
 │   ├─ users.py
 │   ├─ chats.py
 │   └─ __init__.py
 ├─ schemas/               # 데이터 구조 정의
 │   ├─ chat.py
 │   └─ __init__.py

이 구조의 핵심 원칙

“요청을 받는 곳”과
“데이터 모양을 정의하는 곳”을 분리

이걸 안 하면:

  • 파일 하나에 코드 500줄
  • 수정할 때마다 사고 남

7️⃣ main.py의 역할 (아직 코드 X, 개념만)

main.py는 백엔드의 index.ts 같은 존재입니다.

여기서 하는 일:

  1. FastAPI 서버 생성
  2. CORS 설정
  3. 라우터 연결

👉 비즈니스 로직 없음

 


📘 Aura_AI Day 10

FastAPI 백엔드 분리 & 프론트엔드 연동

Part 2. 서버 뼈대 만들기 + 라우터 이해하기 (진짜 A~Z)


8️⃣ FastAPI 서버의 시작점: main.py

중요한 인식부터 잡고 갑니다

main.py는
👉 “이 서버가 어떤 규칙으로 동작할지”를 정의하는 파일입니다.

여기에는:

  • 비즈니스 로직 ❌
  • 데이터 처리 ❌

대신:

  • 서버 생성
  • 보안 규칙
  • 라우터 연결

만 있습니다.


8-1. main.py 전체 코드 (완성본)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from routers.health import router as health_router
from routers.users import router as users_router
from routers.chats import router as chats_router

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(health_router)
app.include_router(users_router)
app.include_router(chats_router)

이제 한 줄씩 해부합니다.


8-2. FastAPI()는 뭔가요?

app = FastAPI()
  • FastAPI 서버 인스턴스를 생성합니다
  • Express의 const app = express() 같은 개념

👉 이 app이 서버 그 자체


8-3. CORS는 왜 필요한가요? (진짜 중요)

from fastapi.middleware.cors import CORSMiddleware

❓ CORS가 뭐예요?

“다른 출처(origin)의 요청을 허용할지 말지”를 정하는 보안 규칙


현재 상황

구분주소

프론트 http://localhost:3000
백엔드 http://localhost:8000

👉 출처가 다름

그래서 CORS 설정 없으면:

  • 브라우저가 요청 자체를 차단
  • 서버 로그도 안 남음
  • 초보자 멘붕 포인트 1위

8-4. CORS 설정 코드 해부

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

각각의 의미

  • allow_origins
    • 이 주소에서 오는 요청만 허용
    • 실무에서는 환경별로 분리
  • allow_methods=["*"]
    • GET / POST / PUT / DELETE 전부 허용
  • allow_headers=["*"]
    • x-user-id 같은 커스텀 헤더 허용 (이게 중요)

👉 이 줄이 없으면 Day 10은 절대 성공 못함


9️⃣ 라우터란 무엇인가?

❓ 라우터(router)가 뭐예요?

URL과 함수를 연결하는 묶음

예:

  • /health → health_check()
  • /users/me → get_my_info()

왜 파일로 나누나요?

❌ 한 파일에 전부 쓰면:

  • 파일 1000줄
  • 수정 시 충돌

✅ 기능별로 나누면:

  • 유지보수 쉬움
  • 팀 협업 가능

9-1. 라우터 import 방식이 중요한 이유

from routers.health import router as health_router

이게 중요한 이유는 당신이 실제로 겪은 에러 때문입니다.

❌ 잘못된 방식 (에러 유발)

from routers import health
app.include_router(health.router)  # 에러 가능

✅ 안전한 방식

from routers.health import router as health_router

👉 Python 패키지 로딩 순서 문제 방지


9-2. 라우터 등록

app.include_router(health_router)

의미:

  • health.py 안에 정의된 모든 API를 서버에 등록

🔟 첫 번째 라우터: health.py

백엔드 만들 때 가장 먼저 만드는 파일


10-1. routers/health.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/health")
def health_check():
    return { "status": "ok" }

10-2. 왜 이게 필요하나요?

  • 서버가 켜졌는지 확인
  • 배포 후 헬스 체크
  • 로드밸런서가 쓰는 API

👉 실무 필수


테스트 방법

uvicorn main:app --reload

브라우저:

http://localhost:8000/health

결과:

{ "status": "ok" }

🔟-1. 여기까지 성공하면?

이제 당신은:

  • FastAPI 서버 실행 가능
  • CORS 이해함
  • 라우터 구조 이해함

👉 절반 성공


1️⃣1️⃣ 유저 라우터: users.py

Day 10의 핵심 개념 중 하나


11-1. 왜 JWT 안 쓰고 Header 쓰나요?

Day 10의 목표는:

“통신 구조 검증”

  • JWT ❌
  • 쿠키 ❌
  • OAuth ❌

👉 그래서 가장 단순한 방식 선택


11-2. routers/users.py 전체 코드

from fastapi import APIRouter, Header, HTTPException

router = APIRouter(prefix="/users")

@router.get("/me")
def get_my_info(x_user_id: str | None = Header(default=None)):
    if not x_user_id:
        raise HTTPException(status_code=401, detail="User not authenticated")

    return {
        "id": x_user_id,
        "name": "Dummy User",
        "email": "dummy@example.com"
    }

11-3. 코드 완전 해부

Header(...)

x_user_id: str | None = Header(default=None)

의미:

  • HTTP Header에서 x-user-id 값을 읽음
  • 없으면 None

인증 실패 처리

if not x_user_id:
    raise HTTPException(status_code=401)

👉 “로그인 안 된 요청”의 기본 패턴


프론트에서 보내는 헤더

fetch("http://localhost:8000/users/me", {
  headers: {
    "x-user-id": user.id
  }
});

🎤 면접용 포인트 (여기서)

“JWT를 바로 쓰지 않고 Header 기반으로 유저를 식별해
프론트와 백엔드 통신 구조를 먼저 검증했습니다.”


✅ Part 2 요약

  • FastAPI 서버 구조 완성
  • CORS 정확히 이해
  • 라우터 분리 이유 이해
  • Header 기반 인증의 의미 이해

📘 Aura_AI Day 10

FastAPI 백엔드 분리 & 프론트엔드 연동

Part 3. Pydantic 스키마 + 채팅 API + 프론트 연결 (핵심)


1️⃣2️⃣ schemas 폴더는 왜 필요한가?

노베이스가 가장 헷갈리는 지점부터 짚고 갑니다

❓ 질문

“라우터에서 바로 request.body 쓰면 되는데
왜 굳이 schemas 폴더를 만드나요?”


✅ 결론부터

👉 데이터 구조를 ‘문서 + 검증 + 계약’으로 만들기 위해서


❌ schemas 없이 가면?

@router.post("/start")
def start_chat(body: dict):
    message = body["message"]

문제점:

  • message 없으면 런타임 에러
  • 타입 검증 ❌
  • API 문서 자동 생성 ❌

✅ schemas를 쓰면?

  • 요청 데이터 자동 검증
  • 타입 강제
  • Swagger 문서 자동 생성
  • GPT 연동 시 안정성 ↑

👉 실무에서는 무조건 schemas 분리


1️⃣3️⃣ schemas/chat.py

13-1. 전체 코드

from pydantic import BaseModel

class ChatRequest(BaseModel):
    message: str

class ChatResponse(BaseModel):
    reply: str

13-2. BaseModel이 뭐예요?

FastAPI의 핵심 중 핵심

BaseModel은:

  • 타입 검증
  • 데이터 파싱
  • 자동 문서화

를 담당합니다.


13-3. 이 코드에서 일어나는 일

{
  "message": "오늘 날씨 어때?"
}
  • message가 문자열 ❌ → 요청 거부
  • message 없음 ❌ → 422 에러

👉 백엔드가 스스로 방어


1️⃣4️⃣ 채팅 라우터: routers/chats.py

이제 진짜 “서비스 같은 API”가 등장합니다


14-1. 전체 코드 (완성본)

from fastapi import APIRouter, Header, HTTPException
from schemas.chat import ChatRequest, ChatResponse

router = APIRouter(prefix="/chats")

@router.post("/start", response_model=ChatResponse)
def start_chat(
    body: ChatRequest,
    x_user_id: str | None = Header(default=None)
):
    if not x_user_id:
        raise HTTPException(status_code=401, detail="Unauthorized")

    return ChatResponse(
        reply=f"({x_user_id}) 님의 메시지: {body.message}"
    )

1️⃣4️⃣-2. 이 함수의 구조를 그림으로 보면

POST /chats/start
│
├─ Header: x-user-id
├─ Body: { message }
│
├─ 인증 체크
└─ 응답: { reply }

1️⃣4️⃣-3. response_model은 왜 쓰나요?

@router.post("/start", response_model=ChatResponse)

의미:

  • 응답은 반드시 ChatResponse 형태
  • 다른 값 반환하면 자동 필터링

👉 API 계약 보장


❓ 없어도 되지 않나요?

기술적으로는 ❌
실무적으로는 ⛔

response_model 없는 API = 문서 없는 계약서


1️⃣5️⃣ FastAPI 자동 문서 (Swagger)

서버 실행 후 접속:

http://localhost:8000/docs

여기서 확인 가능:

  • 모든 API
  • 요청/응답 구조
  • 직접 테스트

👉 면접에서 자랑해도 되는 포인트


1️⃣6️⃣ 프론트엔드 연동 구조

16-1. 흐름 요약

Zustand user.id
   ↓
HTTP Header (x-user-id)
   ↓
FastAPI
   ↓
Response

16-2. 실제 프론트 요청 코드

await fetch("http://localhost:8000/chats/start", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-user-id": user.id,
  },
  body: JSON.stringify({
    message: inputMessage,
  }),
});

16-3. 왜 Header에 user id를 넣었나요?

  • URL ❌ (노출)
  • Body ❌ (의미상 안 맞음)
  • Header ✅ (인증/식별 용도)

👉 HTTP 철학에 맞는 선택


🎤 면접용 Q&A (추가)

Q. 왜 Next.js API Route를 안 쓰고 FastAPI로 분리했나요?

A.

Aura_AI의 핵심 로직은
날씨 데이터 처리, GPT 프롬프트 엔지니어링 등
Python 생태계가 훨씬 강점인 영역입니다.

프론트엔드는 UI에,
백엔드는 데이터 처리에 집중하도록
역할을 분리했습니다.


Q. 왜 JWT 안 쓰고 Header 기반 인증을 썼나요?

A.

Day 10의 목표는
인증 구현이 아니라 통신 구조 검증이었기 때문에
가장 단순한 식별 방식으로 먼저 설계를 검증했습니다.


1️⃣7️⃣ Day 10에서 일부러 안 한 것들 (중요)

❌ JWT
❌ DB
❌ GPT API
❌ OAuth

👉 이유

구조를 먼저 안정화시키기 위해


1️⃣8️⃣ Day 11로 이어지는 설계 포인트

지금 구조는 그대로 두고:

  • x-user-id → JWT
  • dummy reply → GPT 응답
  • in-memory → DB

갈아끼우기만 하면 됨

👉 이게 바로 확장 가능한 설계


✅ Day 10 최종 정리

오늘 얻은 것

  • FastAPI 서버 분리
  • CORS 완벽 이해
  • Router / Schema 분리
  • Header 기반 인증 구조
  • 프론트 ↔ 백엔드 통신

한 줄 요약 (진짜)

“FastAPI로 독립 백엔드를 분리하고,
프론트와 안정적인 통신 구조를 완성한 날”