반응형

파이썬 FastAPI 예외 처리, 테스트 및 프로젝트 구조화

실무에서 진짜 중요한 FastAPI 프로젝트의 마무리 3대장!
예외 처리, 테스트, 구조화로 완성도를 끌어올려보세요.

 

 

안녕하세요, 여러분!

오늘은 FastAPI를 활용한 프로젝트에서 완성도와 유지보수성을 높이는 핵심 주제 3가지를 함께 살펴보려 합니다.

FastAPI는 정말 빠르고 유연한 웹 프레임워크지만, 규모가 커지면 예외 처리와 테스트, 그리고 깔끔한 프로젝트 구조가 필수죠.

초반엔 하나의 파일에 모든 걸 몰아넣는 게 편하게 느껴질 수 있지만, 진짜 문제는 그 이후부터입니다.

에러가 터지거나, 기능이 많아지고, 팀과 협업하는 상황이 오면 코드 관리가 점점 지옥이 되거든요. 😓

그래서 오늘은 그런 혼돈을 예방할 수 있는 3가지 실전 기술을 소개하려고 합니다.

하나씩 차근차근 설명드릴게요. 초보자도 충분히 따라올 수 있으니 걱정 마세요!

 

1. 효율적인 프로젝트 구조 설계하기 🏗️

1.1 모듈화의 필요성과 폴더 구조

FastAPI 프로젝트가 커지기 시작하면 코드가 한 파일에 몰려 있으면 유지보수가 매우 어려워져요.

기능이 많아질수록 파일을 나누고, 책임을 분리하고, 폴더 구조를 체계적으로 구성하는 것이 필수입니다.

FastAPI에서 추천하는 기본적인 디렉터리 구조는 다음과 같습니다:

app/
├── main.py          # FastAPI 앱 실행 진입점
├── models.py        # SQLAlchemy 모델 정의
├── schemas.py       # Pydantic 스키마 정의
├── database.py      # DB 설정, 연결 관리
├── routers/
│   ├── __init__.py
│   ├── users.py     # 사용자 관련 API
│   └── todos.py     # 할일 관련 API
└── core/
    └── config.py    # 환경 변수 및 설정 관리

 

각 파일과 폴더가 맡는 역할이 명확하죠?

특히 routers 폴더는 기능별 라우트를 나누기 좋고, core 폴더에는 설정 파일을 정리할 수 있어요.

1.2 APIRouter로 구조화하는 방법

FastAPI에서는 APIRouter를 활용해서 라우팅을 기능 단위로 나눌 수 있어요.

예를 들어 todos 관련 API는 routers/todos.py

다음과 같이 구성합니다:

from fastapi import APIRouter, Depends, HTTPException
from .. import models, schemas
from ..database import get_db
from sqlalchemy.orm import Session

router = APIRouter(prefix="/todos", tags=["Todos"])

@router.get("/", response_model=List[schemas.TodoItem])
def list_todos(db: Session = Depends(get_db)):
    return db.query(models.Todo).all()

 

이제 main.py에서는 이렇게 간단히 router를 불러와 등록하면 돼요:

from fastapi import FastAPI
from .routers import users, todos

app = FastAPI()
app.include_router(users.router)
app.include_router(todos.router)

 

이런 식으로 각 기능은 독립적으로 관리되고, main에서는 전체 앱을 조립하는 역할만 하게 됩니다.

마치 블록처럼요!

1.3 모듈 간 의존성과 순환참조 방지

구조화할 때 주의할 점 중 하나는 순환 참조(Circular Import)입니다. 예를 들어 models.pyschemas.py를 import하고, schemas.py가 다시 models.py를 참조하면 문제 발생! 😵‍💫

해결 방법은 의외로 간단해요.

보통 schemas.py는 오직 필드 선언에만 집중하고, models.py는 비즈니스 로직 중심으로 구성합니다.

서로 직접 참조하지 않게 하고, 실제 연결은 routers/*.py 파일에서 이루어지도록 조정하면 돼요.

추가로 환경 설정은 core/config.py에 넣고, .env 파일과 pydantic.BaseSettings를 함께 쓰면 환경별로 설정을 유연하게 다룰 수 있어요.

 

 

2. APIRouter를 활용한 모듈화 📦

2.1 APIRouter란 무엇인가요?

FastAPI에서 APIRouter는 말 그대로 “라우터” 역할을 해요.

여러 개의 API 경로들을 하나로 묶어주는 객체인데, 마치 미니 FastAPI 인스턴스처럼 동작해요.

FastAPI의 구조화된 애플리케이션을 만들기 위해 필수적인 도구라고 볼 수 있죠.

main.py 하나에 모든 API 경로를 넣으면 작고 단순할 땐 괜찮지만, 규모가 커지면 지옥 같은 수정의 나락이 펼쳐집니다. 😱

그래서 각 기능별로 라우터를 나눠주는 것이 좋아요.

2.2 APIRouter 사용 예제

routers/todos.py 파일에서 할 일(Todo) 목록을 관리하는 API를 만든다고 가정해볼게요:

from fastapi import APIRouter, Depends, HTTPException
from typing import List
from .. import models, schemas
from ..database import get_db
from sqlalchemy.orm import Session

router = APIRouter(prefix="/todos", tags=["Todos"])

@router.get("/", response_model=List[schemas.TodoItem])
def list_todos(db: Session = Depends(get_db)):
    return db.query(models.Todo).all()

 

이제 main.py에 가서 해당 라우터를 연결하면 됩니다:

from fastapi import FastAPI
from .routers import todos

app = FastAPI()
app.include_router(todos.router)

 

prefix="/todos" 덕분에 /todos로 시작하는 모든 경로가 이 라우터에 포함됩니다.

tags는 자동 문서화(swagger UI)에서 그룹 이름처럼 사용되죠.

2.3 APIRouter의 이점 💡

  • 기능별로 API를 독립적으로 관리 가능 → 유지보수 용이
  • Swagger 문서에서 각 API 그룹을 구분해 보여줌 → 테스트 용이
  • 다른 파일과 독립적으로 테스트 가능 → 유닛 테스트 구성에도 유리

프로젝트가 점점 커질수록 APIRouter를 쓰는 구조는 선택이 아니라 필수가 됩니다.

기획자나 다른 개발자가 함께 보는 문서화된 API를 만드는 데도 매우 유리하거든요.

 

 

3. FastAPI의 예외 처리 방법 알아보기 ⚠️

3.1 HTTPException의 기본 사용법

FastAPI에서 예외를 처리할 때 가장 기본이 되는 클래스가 바로 HTTPException이에요.

이걸 이용하면 코드에서 명시적으로 에러 응답을 보낼 수 있죠.

예를 들면 이렇습니다:

from fastapi import HTTPException

if not user:
    raise HTTPException(status_code=404, detail="User not found")

 

이 코드가 실행되면 FastAPI는 자동으로 {"detail": "User not found"} 같은 JSON 응답과 함께 404 상태 코드를 반환합니다.

3.2 자주 쓰는 예외 처리 시나리오

  • 조회할 데이터가 없을 때 → raise HTTPException(status_code=404)
  • 사용자 인증 실패 시 → status_code=401 또는 403
  • 클라이언트의 잘못된 요청 → status_code=400과 적절한 메시지

detail에 넣는 메시지는 문자열이나 JSON 형태가 가능해서 에러 코드와 설명을 같이 담을 수도 있어요. 예:

raise HTTPException(
    status_code=400,
    detail={"error": "Invalid email", "code": 1001}
)

 

이렇게 하면 클라이언트에서 에러 코드를 받아서 처리하기 쉬워지죠.

3.3 FastAPI의 자동 유효성 검사

FastAPI는 입력 데이터의 유효성 검사를 자동으로 해줍니다.

예를 들어 Pydantic 모델에서 title: str로 지정해두면, 숫자나 null이 들어왔을 때 자동으로 422 Unprocessable Entity 오류를 반환해요.

다만, 값은 타입이 맞더라도 비즈니스 로직에 어긋나는 경우는 직접 HTTPException으로 처리해줘야 합니다.

예를 들어 제목이 빈 문자열이면 아래처럼 처리할 수 있어요:

if todo.title.strip() == "":
    raise HTTPException(status_code=400, detail="Title cannot be empty")

 

이처럼 자동 유효성 검사 + 비즈니스 검증을 조합하면, 훨씬 견고한 API를 만들 수 있어요.

 

 

4. 전역 예외 핸들러 활용법 💥

4.1 커스텀 예외 핸들러란?

FastAPI는 예외 처리기를 전역으로 등록할 수 있어요.

즉, 특정 예외가 발생했을 때 공통된 형식의 응답을 보내고 싶다면 핸들러를 따로 정의해서 자동으로 처리되게 할 수 있다는 말이죠.

예를 들어 데이터베이스의 제약 조건 위반 같은 경우, 매번 처리하기보다는 한 번에 묶어서 처리하면 편하겠죠?

4.2 SQLAlchemy 예외 핸들링 예제

SQLAlchemy에서 가장 자주 만나는 예외 중 하나는 IntegrityError입니다.

중복된 키 삽입, not null 위반 등에서 발생하는 예외죠.

이걸 전역으로 처리해봅시다.

from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError
from fastapi import Request, FastAPI

app = FastAPI()

@app.exception_handler(IntegrityError)
async def integrity_error_handler(request: Request, exc: IntegrityError):
    return JSONResponse(
        status_code=400,
        content={"detail": "데이터베이스 제약 조건 위반 (중복, null 등)"}
    )

 

이제 어떤 라우터에서든 IntegrityError가 발생하면 자동으로 이 핸들러가 실행되어 공통된 메시지를 보내게 됩니다. 무척 깔끔하죠? 👍

4.3 RequestValidationError 커스터마이징

FastAPI는 입력 검증에 실패하면 기본적으로 422 오류와 함께 RequestValidationError를 발생시켜요. 그런데 이 에러 메시지가 너무 상세하거나 개발자스러워서, 사용자 입장에서는 당황스러울 수 있어요.

그럴 땐 아래처럼 기본 예외 핸들러를 오버라이딩해서 에러 메시지를 심플하게 바꿔줄 수 있습니다:

from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from fastapi.exception_handlers import request_validation_exception_handler

@app.exception_handler(RequestValidationError)
async def custom_validation_handler(request, exc):
    return JSONResponse(
        status_code=422,
        content={"detail": "입력 형식이 잘못되었습니다. 필수 값을 확인하세요."}
    )

 

물론 실제 서비스에서는 유형별 메시지 분기exc.errors()를 순회하여 더 정교하게 구성할 수도 있어요.

4.4 실무에서의 활용 포인트 💡

  • 모든 라우터에서 반복되는 예외 처리 코드 제거
  • 사용자에게 친절하고 일관된 오류 메시지 제공
  • 로깅과 모니터링에 유리한 구조 구성 가능

전역 예외 핸들러는 단순히 “에러를 막는” 게 아니라 일관성 있고 신뢰감 있는 서비스를 만드는 첫걸음입니다.

 

 

5. TestClient를 사용한 테스트 자동화 🧪

5.1 FastAPI의 TestClient란?

FastAPI는 Starlette를 기반으로 만들어졌기 때문에 TestClient라는 테스트 도구를 제공합니다.

이 도구는 실제 서버를 실행하지 않고도 app 객체를 직접 호출하여 테스트를 할 수 있도록 해주는 매우 강력한 기능이에요.

보통 pytest와 함께 사용되며, REST API의 요청/응답 시나리오를 자동화하는 데 아주 적합합니다.

즉, 매번 브라우저나 Postman으로 테스트하지 않아도 된다는 거죠!

5.2 기본 테스트 코드 작성법

아래는 간단한 예시입니다.

test_main.py 파일을 만들고 다음과 같은 테스트 코드를 작성해볼 수 있어요:

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_create_todo():
    response = client.post("/todos", json={"title": "Test", "description": "Test Desc"})
    assert response.status_code == 201
    assert response.json()["title"] == "Test"

def test_read_todo_not_found():
    response = client.get("/todos/999")
    assert response.status_code == 404

 

위 테스트는 할일을 생성하고, 존재하지 않는 할일을 조회할 때 404를 반환하는지를 검증합니다.

.get, .post, .delete 등을 통해 실제 요청을 시뮬레이션할 수 있고, response.status_coderesponse.json()으로 응답 값을 비교합니다.

5.3 테스트에서 자주 검증하는 항목들

  • 상태 코드가 올바르게 반환되는가?
  • 응답 데이터의 필드/값이 기대한 대로 구성되어 있는가?
  • 에러 발생 시 에러 메시지가 정확히 반환되는가?

5.4 실무 팁 💡

  • 테스트 함수는 반드시 test_로 시작해야 pytest에서 인식합니다.
  • 반복되는 검증 로직은 함수로 분리해 재사용성을 높입니다.
  • 정상 케이스뿐 아니라 에러 케이스도 테스트하세요!

 

테스트는 개발자의 실수를 사전에 방지해주는 방패입니다.

특히 협업하거나 유지보수가 필요한 프로젝트에서는 테스트 코드의 유무가 신뢰성과 품질의 기준이 되기도 해요.

 

 

6. 테스트 환경의 DB 처리 전략 🗄️

6.1 테스트에 실제 DB를 쓰면 안 되는 이유

실제 개발용 데이터베이스를 테스트에 사용하면 어떤 일이 벌어질까요?

예기치 않게 데이터를 삭제하거나 오염시킬 수 있습니다. 😱

특히 DELETEDROP 같은 쿼리를 테스트하는 경우에는 더더욱 위험하죠.

그래서 테스트 환경에서는 격리된 테스트 전용 DB를 사용하는 것이 원칙입니다.

6.2 메모리 SQLite DB 사용 예제

FastAPI에서 SQLite의 메모리 DB는 테스트 환경을 빠르게 구성할 수 있는 좋은 도구입니다.

아래는 예시 설정입니다:

# test_db.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.models import Base

SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"

engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def override_get_db():
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()

 

그리고 main.py 또는 conftest.py에서 FastAPI의 의존성을 오버라이드 해줍니다:

from app.main import app
from app.test_db import override_get_db

app.dependency_overrides[get_db] = override_get_db

 

이렇게 하면 테스트에서 사용하는 get_db()가 실제 DB가 아닌 임시 메모리 DB로 교체됩니다.

테스트마다 새로운 환경에서 시작되니 데이터 충돌 걱정 없이 깔끔하게 실행 가능해요.

6.3 데이터 초기화 전략

테스트 전에 테이블을 생성하고 데이터를 초기화해주는 것도 중요합니다.

아래처럼 setup_function 혹은 pytest.fixture를 사용해 테스트 시작 전에 초기 세팅을 할 수 있어요.

import pytest
from app.test_db import engine, Base

@pytest.fixture(scope="function", autouse=True)
def setup_and_teardown():
    Base.metadata.create_all(bind=engine)
    yield
    Base.metadata.drop_all(bind=engine)

 

이렇게 하면 테스트마다 DB가 깨끗하게 리셋되기 때문에, 데이터 충돌로 인한 테스트 실패를 방지할 수 있어요.

6.4 실전 활용 팁 💡

  • 테스트용 DB URL을 별도로 구성하고 설정 파일로 분리하세요.
  • 테스트 중 발생하는 로그는 최소화하여 결과에 집중할 수 있도록 합니다.
  • CI 환경에서는 --disable-warnings 옵션을 이용해 깔끔한 출력 유지

 

테스트의 핵심은 "예측 가능한 상태에서 테스트가 반복 가능해야 한다"는 것이에요.

이를 위해 테스트용 DB는 선택이 아닌 필수 전략입니다.

 

 

마무리하며 🌱

이번 글에서는 FastAPI 프로젝트의 후반부에서 반드시 챙겨야 할 예외 처리, 테스트, 구조화 전략에 대해 하나씩 짚어봤습니다.

 

단순히 기능을 구현하는 것을 넘어서, 확장성과 유지보수성을 고려한 설계가 왜 중요한지를 느끼셨을 거예요.

특히 예외 처리에서는 HTTPException과 전역 핸들러의 활용, 테스트에서는 TestClient와 SQLite 메모리 DB의 유용함, 그리고 APIRouter를 통한 구조화는 프로젝트의 ‘완성도’를 좌우합니다.

코드가 잘 돌아가는 것도 좋지만, 에러에 강하고 테스트가 보장된 프로젝트가 진짜 안정적인 프로젝트예요.

FastAPI를 쓰면 API를 정말 빠르게 만들 수 있지만, 그 위에 신뢰성과 관리 용이성이라는 무기를 더해보세요. 그게 바로 한 단계 높은 개발자의 길이니까요. 😉

 

이제 여러분의 FastAPI 프로젝트는 단순한 샘플이 아닌, 실제 서비스로 연결될 수 있는 기반이 갖춰졌습니다. 이제 자신 있게 확장하고 테스트하고, 에러를 두려워하지 마세요!

반응형
반응형

파이썬 FastAPI 고급 ORM – 관계 모델링과 다중 테이블 연동

여러분의 FastAPI 앱이 점점 커지고 있나요?
단일 테이블로는 한계가 있다 느낄 때,
지금이 바로 관계형 모델링을 배워야 할 타이밍입니다!

 

 

안녕하세요, 개발자 여러분 😊
오늘은 FastAPI와 SQLAlchemy ORM을 활용하여 "다중 테이블 관계 설정"Pydantic 스키마 연계까지 다뤄볼 거예요.

특히 User와 Todo 같은 실용적인 예제를 중심으로 일대다(1:N), 다대다(N:M) 관계를 어떻게 모델링하는지, 그리고 이를 CRUD API로 어떻게 연결하는지 구체적으로 살펴봅니다.

FastAPI를 사용하다 보면 단일 테이블로 구현한 간단한 예제는 금방 만들 수 있지만, 실제 애플리케이션에서는 테이블 간의 관계가 필수입니다. 오늘은 이걸 제대로 배워볼 거예요.

 

1. 테이블 간 관계 설정의 기초 🧩

FastAPI에서 데이터베이스를 제대로 다루기 위해선 SQLAlchemy ORM을 이용한 관계형 모델링이 거의 필수예요.

특히 다수의 테이블이 얽힌 구조에서는 일대다(One-to-Many), 다대다(Many-to-Many) 같은 관계 설정이 핵심이 됩니다.

예를 들어 한 명의 사용자가 여러 개의 Todo를 소유하거나, 여러 사용자가 여러 프로젝트에 동시에 참여할 수 있다면 관계형 설계가 반드시 필요하죠.

1.1 일대다 관계란 무엇인가요?

가장 흔한 관계가 바로 1:N 관계예요.

한 사람이 여러 개의 블로그 글을 작성하는 경우처럼요.

이를 SQLAlchemy에서는 다음과 같은 방식으로 표현합니다:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    username = Column(String, unique=True, index=True)
    posts = relationship("Post", back_populates="author")

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String)
    content = Column(String)
    author_id = Column(Integer, ForeignKey("users.id"))
    author = relationship("User", back_populates="posts")

 

여기서 핵심은 ForeignKeyrelationship입니다.

Post.author_id는 User 테이블의 id를 참조하는 외래 키이고, 두 모델은 back_populates로 서로를 참조합니다.

이 설정 덕분에 user.posts로 해당 사용자의 모든 게시글을 가져올 수 있고, post.author로 작성자 정보를 조회할 수 있어요.

1.2 다대다 관계는 어떻게 구성할까요?

조금 더 복잡한 구조인 N:M 관계는 중간 테이블(association table)을 만들어야 합니다.

예를 들어 사용자와 프로젝트는 다음과 같이 설정할 수 있습니다.

association_table = Table(
    "user_project",
    Base.metadata,
    Column("user_id", ForeignKey("users.id"), primary_key=True),
    Column("project_id", ForeignKey("projects.id"), primary_key=True),
)

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    username = Column(String)
    projects = relationship("Project", secondary=association_table, back_populates="members")

class Project(Base):
    __tablename__ = "projects"
    id = Column(Integer, primary_key=True)
    title = Column(String)
    members = relationship("User", secondary=association_table, back_populates="projects")

 

이처럼 secondary 파라미터를 이용해 중간 테이블을 지정하고, 관계를 서로 연결하면 user.projects, project.members로 양방향 접근이 가능해요.

실제 현업에서도 협업 앱, 팀 관리 시스템 등에서 자주 쓰이는 구조입니다.

📋 일대다 vs 다대다 – 요약 비교

구분 일대다 (1:N) 다대다 (N:M)
예시 사용자 – 게시글 사용자 – 프로젝트
테이블 구성 두 개의 테이블 + 외래 키 세 개의 테이블 (중간 테이블 포함)
SQLAlchemy 설정 ForeignKey, relationship association_table, secondary, relationship

이제 관계 설정의 기초를 이해하셨다면, 다음 단계에서는 실제로 FastAPI에서 SQLAlchemy ORM 모델을 작성하고, 이 관계가 어떻게 작동하는지 하나씩 코드를 통해 살펴볼 거예요.

 

 

2. 관계를 표현하는 SQLAlchemy 모델 구조 🏗️

자, 이제 본격적으로 모델을 구성해볼 시간이에요.

FastAPI에서 SQLAlchemy ORM을 사용할 때는 클래스를 기반으로 테이블을 정의하고,

relationship()ForeignKey()를 통해 테이블 간의 관계를 표현합니다.

 

우리가 구현할 시나리오는 아주 현실적이에요:

사용자(User)가 여러 개의 할 일(Todo)을 가질 수 있는 일대다(1:N) 구조입니다.

2.1 사용자(User)와 할 일(Todo) 모델 정의

아래 코드는 SQLAlchemy ORM을 사용한 관계형 모델 정의 예시입니다.

중요한 건 각 모델이 서로를 어떻게 참조하고 있는지예요.

from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from database import Base  # Base는 SQLAlchemy의 declarative_base()로 정의

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    todos = relationship("Todo", back_populates="owner")

class Todo(Base):
    __tablename__ = "todos"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String, nullable=True)
    done = Column(Boolean, default=False)
    owner_id = Column(Integer, ForeignKey("users.id"))
    owner = relationship("User", back_populates="todos")

 

이 구조에서 User.todos는 해당 사용자가 작성한 모든 Todo 항목을 리스트로 반환하며, Todo.owner는 해당 항목의 작성자(User 객체)를 가리킵니다.

이렇게 하면 ORM 객체 간 탐색이 매우 쉬워져요.

🧠 관계형 구조의 장점은?

  • ORM 객체 간 탐색이 쉬워져 복잡한 쿼리를 단순화할 수 있습니다.
  • Pydantic과 함께 사용할 때 직관적인 JSON 변환이 가능합니다.
  • 모델 간의 의존성 설계가 명확해져 유지보수가 쉬워집니다.

2.2 모델 구조 정리 예시

테이블 컬럼 비고
users id, username username은 unique + index
todos id, title, description, done, owner_id owner_id는 users.id를 ForeignKey로 참조

이제 관계 설정이 완료된 ORM 모델 구조를 완성했어요!

 

다음 단계에서는 Pydantic 스키마를 통해 ORM 객체와 어떻게 연동하고 응답 데이터를 어떻게 구성하는지 다뤄볼게요.

이 부분이 바로 FastAPI의 강력함이 드러나는 순간입니다 💪

 

 

3. Pydantic 스키마와 ORM 연계하기 🔄

FastAPI의 가장 큰 장점 중 하나는 Pydantic을 활용한 데이터 유효성 검증과 직렬화예요.

특히 ORM 모델과 Pydantic BaseModel을 함께 사용하면, 우리가 만든 SQLAlchemy 객체를 JSON으로 변환하거나 사용자 입력을 유효성 있게 처리하는 게 정말 간단해집니다.

핵심은 orm_mode 설정을 통해 ORM 객체를 바로 변환할 수 있도록 만드는 거예요.

3.1 Todo 스키마 구성

먼저 Todo 항목을 표현할 Pydantic 스키마를 작성해보겠습니다.

아래처럼 기본 정보와 orm_mode 설정만 해주면 FastAPI에서 ORM 객체를 응답 모델로 사용할 수 있게 됩니다.

from pydantic import BaseModel
from typing import Optional

class TodoItem(BaseModel):
    id: int
    title: str
    description: Optional[str] = None
    done: bool

    class Config:
        orm_mode = True

 

이제 response_model=TodoItem으로 설정한 API에서 SQLAlchemy Todo 객체를 반환하면, FastAPI가 자동으로 이 객체를 JSON으로 변환해줍니다.

놀랍게도 따로 변환 로직을 만들 필요가 없어요.

3.2 User 입력/출력 스키마 분리

FastAPI에서는 API의 목적에 따라 입력용과 출력용 스키마를 분리해서 사용하는 게 일반적이에요.

예를 들어 사용자 등록 시엔 비밀번호를 받아야 하지만, 사용자 정보를 응답할 땐 비밀번호가 절대 노출되어선 안 되죠.

class UserCreate(BaseModel):
    username: str
    password: str  # 실제로는 해싱 필요

class User(BaseModel):
    id: int
    username: str

    class Config:
        orm_mode = True

 

이처럼 분리함으로써 보안은 물론 API 명세도 깔끔해지고 유지보수가 쉬워져요.

특히 비밀번호 필드는 출력을 철저히 막는 게 기본입니다.

실습에서는 해싱을 생략하지만, 실제 서비스에서는 절대 평문 저장하면 안 된다는 거, 꼭 기억해주세요!

3.3 관계 필드를 포함한 응답 스키마

관계를 활용한 응답을 구성할 때는 중첩 모델(Nested Model)을 사용할 수 있어요.

예를 들어 사용자 조회 시 해당 사용자의 Todo 목록을 함께 반환하고 싶다면 아래처럼 구성합니다.

from typing import List

class UserWithTodos(BaseModel):
    id: int
    username: str
    todos: List[TodoItem] = []

    class Config:
        orm_mode = True

 

주의할 점은 데이터량이 많아지면 응답 속도나 용량에 영향을 줄 수 있으므로 꼭 필요한 경우에만 중첩 구조를 사용하는 게 좋습니다.

예를 들어 Todo가 수천 개라면...? 페이지네이션이 필요하겠죠 😅

✅ 정리: ORM ↔ Pydantic 연동 요약

구분 내용
입력 스키마 필수 입력값 (비밀번호 포함 가능)
출력 스키마 응답에 포함할 필드만 선택
orm_mode ORM 객체를 자동으로 Pydantic으로 변환 가능하게 함
중첩 스키마 관계 객체를 포함하고 싶을 때 List, Optional 등으로 표현

이제 ORM과 스키마 간의 연결 고리를 완성했습니다.

 

다음은 이걸 활용해서 실제 API를 만들고, 사용자와 Todo 항목을 어떻게 엮는지 실습으로 들어가볼게요.

 

 

4. 사용자 기반 Todo API 만들기 ⚙️

지금부터는 앞서 만든 User ↔ Todo 관계를 실제 API에서 어떻게 다룰 수 있는지 구체적으로 알아볼게요.

예제를 통해 사용자와 연동된 Todo 항목을 만들고, 조회하고, 연결 관계를 유지하면서 데이터를 다룰 수 있게 해볼 거예요.

4.1 사용자 등록 API

사용자 등록은 POST /users로 진행합니다.

중복 사용자명을 체크하고, 평문 비밀번호를 그대로 저장합니다(실습 단순화용).

@app.post("/users", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    existing_user = db.query(User).filter(User.username == user.username).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="이미 존재하는 사용자명입니다.")
    new_user = User(username=user.username)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

 

여기선 비밀번호 해싱 없이 단순하게 처리하지만, 실제 서비스라면 반드시 bcrypt 등으로 암호화해야 한다는 점 잊지 마세요.

4.2 사용자 기반 Todo 생성

이제 Todo 항목을 만들면서 사용자와 연결해볼게요.

POST /users/{user_id}/todos로 요청 시 해당 사용자의 소유 Todo를 생성하는 방식입니다.

@app.post("/users/{user_id}/todos", response_model=TodoItem)
def create_todo_for_user(user_id: int, todo: TodoItemCreate, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="해당 사용자를 찾을 수 없습니다.")
    new_todo = Todo(**todo.dict(), owner_id=user_id)
    db.add(new_todo)
    db.commit()
    db.refresh(new_todo)
    return new_todo

 

이 API는 user_id를 URL로 받고, 실제로 존재하는 사용자인지 확인한 후 그 사용자에게 Todo 항목을 연결해요. 데이터 무결성을 유지하는 좋은 예입니다.

4.3 사용자 별 Todo 목록 조회

GET /users/{user_id}/todos 요청으로 해당 사용자의 할 일 목록을 조회할 수 있어요. List[TodoItem] 형태로 응답하므로 클라이언트 입장에서도 처리하기 쉽습니다.

@app.get("/users/{user_id}/todos", response_model=List[TodoItem])
def get_user_todos(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="사용자가 존재하지 않습니다.")
    return user.todos

🚀 라우팅 구조 요약

  • POST /users → 사용자 생성
  • POST /users/{user_id}/todos → Todo 생성 (소유자 연결)
  • GET /users/{user_id}/todos → Todo 목록 조회

 

이러한 RESTful한 구조는 실무에서도 아주 자주 사용되는 패턴입니다.

리소스 기반 URL 설계HTTP 메서드 의미를 자연스럽게 활용할 수 있어 API의 일관성과 가독성을 높여줘요.

 

 

5. 관계 응답과 데이터 최적화 전략 📦

데이터베이스 관계를 설정하고 API를 만들다 보면, 관계 필드를 API 응답에 포함해야 할 때가 많아요.

예를 들어 사용자 정보를 조회할 때 해당 사용자가 등록한 모든 할 일 목록까지 함께 보내주고 싶은 경우가 있죠.

이럴 땐 orm_mode=True 설정과 함께 Nested Pydantic 모델을 사용하면 됩니다.

5.1 관계 필드 포함한 사용자 응답 예시

앞서 정의한 UserWithTodos 스키마를 활용하면 사용자와 해당 사용자의 할 일을 한꺼번에 응답할 수 있어요.

@app.get("/users/{user_id}", response_model=UserWithTodos)
def get_user_detail(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
    return user

 

이 API는 매우 직관적이고, 프론트엔드 입장에서도 한 번의 요청으로 사용자와 그에 따른 할 일을 모두 받아볼 수 있으니 편리하죠.

하지만 여기에는 중요한 함정이 있어요…

5.2 Lazy Loading의 한계

SQLAlchemy는 기본적으로 관계 데이터를 지연 로딩(Lazy Loading)합니다.

즉, 관계 필드를 실제로 접근하기 전까지는 쿼리를 실행하지 않죠.

이게 편리하긴 한데, 반복되는 쿼리 발생으로 N+1 문제가 생길 수 있어요.

예를 들어 1명의 사용자를 불러오고 todos를 포함해 응답하려면, user.todos 접근 시마다 새로운 SELECT 쿼리가 실행되기 때문에 비효율적입니다.

이런 상황에선 selectinload() 같은 Eager Loading 전략을 사용하면 좋아요.

from sqlalchemy.orm import selectinload

@app.get("/users/{user_id}", response_model=UserWithTodos)
def get_user_with_todos(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).options(selectinload(User.todos)).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="해당 사용자가 없습니다.")
    return user

 

selectinload()를 옵션에 포함시키면, 사용자와 할 일 목록을 단 두 번의 쿼리로 한번에 가져올 수 있어서 효율적이에요.

5.3 중첩 응답의 설계 전략

모든 관계 데이터를 항상 응답에 포함시키는 건 좋은 전략이 아닐 수 있어요.

데이터가 커질수록 성능에 악영향을 줄 수 있거든요.

그래서 보통은 아래와 같은 전략을 추천해요.

  • 단순 응답: 기본적으로 ID, 이름 등 요약된 정보만 제공
  • 선택적 확장: 필요할 때만 쿼리 파라미터(ex: ?include=todos)로 전체 정보 요청
  • 페이지네이션 도입: 할 일 목록이 많다면 offset/limit 사용

 

🔍 이런 경우 주의하세요!

관계가 양방향(back_populates)으로 설정되어 있는 경우 순환 참조 문제가 생길 수 있어요.

예를 들어 User → Todo → User → Todo... 식으로 무한 루프가 발생할 수 있으니, 이런 경우엔 관계 필드를 Optional로 정의하거나 재귀 깊이를 제어하는 방법을 활용해야 합니다.

이처럼 관계 응답은 강력한 기능이지만 설계 전략이 명확해야 성능과 보안 모두 잡을 수 있어요.

 

다음 단계에서는 실제 서비스에서 모델 구조가 변경되었을 때 어떻게 대응하는지, 마이그레이션 도구 Alembic을 소개해볼게요!

 

 

6. (선택) Alembic으로 마이그레이션 관리하기 📜

여러분, ORM 모델을 작성하고 데이터베이스와 연동하는 단계까지는 성공했지만, 현실은 언제나 변화하죠. 테이블 구조가 바뀌면 어떻게 해야 할까요?

매번 수동으로 SQL ALTER TABLE 문을 쓰기엔 너무 번거롭고 위험하기까지 합니다.

그래서 등장한 게 바로 Alembic입니다!

6.1 Alembic이란 무엇인가요?

Alembic은 SQLAlchemy 프로젝트의 공식 마이그레이션 도구예요.

우리가 ORM 모델을 수정하면, 이를 자동 감지해 버전 기반 마이그레이션 파일로 만들어주고, 데이터베이스에 적용하거나 되돌리는 작업을 쉽게 해줍니다.

  • 모델 → DB 테이블 생성
  • 모델 수정 → 마이그레이션 스크립트 자동 생성
  • 변경 사항 추적 및 롤백 가능

6.2 설치 및 초기 설정

pip install alembic
alembic init alembic

 

위 명령어를 입력하면 alembic 폴더와 설정 파일이 생성돼요.

그다음 env.py 파일에서 SQLAlchemy Base 모델과 데이터베이스 URL을 연결해줘야 해요.

6.3 자동 마이그레이션 생성

alembic revision --autogenerate -m "Add owner_id to Todo"
alembic upgrade head

 

이렇게 하면 모델 변경 사항을 기반으로 마이그레이션 파일이 생성되고, upgrade head 명령으로 DB에 실제 적용할 수 있어요.

테이블이 추가되거나 컬럼이 변경될 때도 자동으로 반영되니 정말 유용하죠.

💡 팁: 버전 관리 전략

  • 모든 모델 변경 시 마이그레이션 생성 필수!
  • downgrade로 이전 버전으로 복구 가능 (테스트 환경에서 유용)
  • 버전 파일은 Git으로 함께 관리!

 

이번 글에서는 실습에 Alembic을 직접 적용하지는 않았지만, FastAPI + SQLAlchemy를 실제 서비스에 도입할 때는 반드시 필요한 도구입니다.

공식 문서를 꼭 참고해 보시고, 여러분의 프로젝트에 도입해보세요!

 

지금까지 FastAPI에서 SQLAlchemy를 활용한 고급 ORM 관계 모델링다중 테이블 연동에 대해 하나하나 살펴보았습니다.

단순한 CRUD를 넘어서 관계를 정의하고, Pydantic 모델과 연계하고, 사용자 기반의 RESTful API를 만드는 실전 흐름까지 따라오셨다면 정말 큰 도약을 하신 거예요!

처음에는 관계 설정이 어렵게 느껴질 수 있지만, 코드를 따라 치고, 하나씩 요청을 만들어보면 생각보다 명확해진답니다.

특히 ORM 객체와 Pydantic 스키마의 분리, Lazy vs Eager Loading 개념, API 응답 최적화 전략은 실무에서도 자주 마주하게 되니 이번 기회에 확실히 익혀두시길 추천드려요.

앞으로 여러분의 FastAPI 프로젝트에 더 복잡한 모델 구조나 비즈니스 로직을 추가하게 되면, 이 관계 모델링이 탄탄한 기반이 되어줄 거예요.

그리고 모델이 복잡해질수록 Alembic을 통한 마이그레이션 관리는 필수입니다. 꼭 연습해보세요!

 

다음 글에서는 사용자 인증(Authentication)과 권한 부여(Authorization), OAuth2 등 보안 요소를 어떻게 FastAPI에 적용하는지 다룰 예정이에요.

궁금하셨던 분들 많으시죠? 😉 

반응형
반응형

파이썬 FastAPI 데이터베이스 연동
: SQLAlchemy ORM 시작

여러분, 웹 API를 만들었는데 데이터를 저장할 방법이 없어서 매번 초기화된다고요?
그렇다면 지금이 바로 ORM을 배워야 할 순간입니다!

 

 

안녕하세요, 여러분! 😊

FastAPI로 REST API를 구현하면서 데이터를 메모리나 리스트에만 저장했다면, 이제 다음 단계로 나아갈 차례입니다.

바로 데이터베이스 연동이죠.

특히 실습 환경에서는 간편한 SQLite로, 실전 배포 단계에서는 PostgreSQL이나 MySQL을 사용하게 될 텐데요.

오늘은 그 첫걸음으로 SQLAlchemy ORM을 FastAPI에 통합하는 방법을 알아보겠습니다.

ORM이 뭐고, 왜 쓰는지부터 실제 코드 예제까지 차근차근 풀어볼게요.

초보자도 이해할 수 있도록! 그럼 바로 시작해볼까요? 😄

 

1. 관계형 데이터베이스와 ORM 기초 📘

1.1 관계형 데이터베이스란 무엇인가요?

웹 애플리케이션에서 가장 기본적이면서도 중요한 요소 중 하나는 데이터 저장입니다.

사용자의 정보, 게시글, 댓글 등은 휘발성 메모리보다 영구 저장이 가능한 데이터베이스에 보관해야 하죠.

그리고 그중에서도 가장 널리 쓰이는 건 관계형 데이터베이스(Relational Database)입니다.

관계형 데이터베이스는 데이터를 표(table)의 형태로 저장하고, 그 안에는 열(컬럼)행(레코드)이 존재합니다.

예를 들어 사용자의 정보를 저장할 때는 다음과 같은 테이블이 만들어집니다.

id username email
1 minsu minsu@example.com
2 jiyoon jiyoon@example.com

또한 외래 키(Foreign Key)라는 개념을 이용해서 테이블 간 관계를 설정할 수 있어요.

예를 들어

사용자 테이블과 게시글 테이블이 있다면, 게시글 테이블은 작성자의 ID를 외래 키로 저장함으로써 '누가 쓴 글인지'를 연결할 수 있는 겁니다.

1.2 ORM(Object Relational Mapping)이란?

그런데 여러분, SQL 쿼리 직접 작성해보셨나요?

INSERT INTO, SELECT, UPDATE 같은 명령어들 말이죠. 배우는 건 어렵지 않지만, 반복적으로 작성하다 보면 지치기 마련이에요. 😓

특히 Python처럼 객체 지향 언어에서는 SQL보다 객체를 다루는 게 더 자연스럽죠.

그래서 등장한 게 바로 ORM (Object-Relational Mapping)입니다.

ORM은 객체와 데이터베이스를 자동으로 매핑해주는 기술로, 클래스를 정의하고 객체를 조작하면 자동으로 SQL이 실행돼요.

즉, SQL을 거의 몰라도 DB를 다룰 수 있는 거죠.

  • Python 코드로 SQL 없이 DB 조작 가능
  • 모델 클래스 정의 → 자동으로 테이블 생성
  • 쿼리 결과도 객체로 반환되어 사용이 간편함

ORM을 사용하면 생산성은 높이고, 오류는 줄이고 코드의 일관성까지 챙길 수 있어요.

물론 SQL을 완전히 모르고 개발하는 건 위험하지만, 기본 SQL을 익힌 후 ORM을 활용하면 정말 편리합니다. 😎

자, 이제 ORM의 세계를 본격적으로 들어가 볼 시간이에요.

다음 섹션에서는 FastAPI와 함께 사용할 ORM 라이브러리인 SQLAlchemy를 소개해볼게요!

 

 

2. SQLAlchemy ORM의 기본 구조 이해하기 🧩

2.1 SQLAlchemy란?

SQLAlchemy는 Python 생태계에서 가장 널리 쓰이는 ORM 도구 중 하나입니다.

단순히 ORM 기능뿐 아니라, SQL 생성기, DB 연결기, 트랜잭션 관리자까지 포괄적으로 포함된 종합 프레임워크죠.

SQLAlchemy는 크게 두 가지 레벨로 나뉘는데요.

하나는 Core (SQL 표현 언어), 다른 하나는 ORM (객체 관계 매핑)입니다.

저희는 이 중 ORM을 활용하여 FastAPI와 연결할 거예요.

2.2 SQLAlchemy ORM의 구조

ORM 방식으로 SQLAlchemy를 사용할 때는 다음과 같은 구성요소들을 기억해 두셔야 해요.

각각의 역할이 명확하게 나뉘어 있어서 한 번 구조를 이해해두면 이후 확장도 쉬워집니다.

구성 요소 설명
Engine 데이터베이스와의 연결을 생성하는 객체 (URL 기반으로 DB 접근)
Session ORM이 실제로 DB와 통신할 때 사용하는 작업 단위 (add, commit, query 등 처리)
Base 모든 ORM 모델이 상속받을 추상 클래스 (테이블 메타데이터 포함)
Model 데이터베이스 테이블과 매핑되는 Python 클래스 (컬럼과 필드 정의)

위 구성요소들을 바탕으로 SQLAlchemy로 앱을 구축할 수 있어요.

FastAPI와 통합할 때는 이 구조를 기본으로 시작하게 됩니다.

2.3 SQLAlchemy ORM의 동작 흐름

  1. Engine 생성: create_engine()로 데이터베이스 연결 설정
  2. Session 생성: sessionmaker를 이용해 세션 팩토리 구성
  3. Base 선언: declarative_base()로 모델의 부모 클래스 생성
  4. 모델 정의: 클래스를 통해 테이블 구조와 컬럼 정의
  5. CRUD 작업: 세션을 통해 데이터 생성/조회/수정/삭제 수행

이 흐름을 기억해두면, 이후 FastAPI에서 API 요청 → ORM 처리 → DB 반영 흐름을 구현할 때 큰 도움이 됩니다.

 

그럼 이제 실제로 FastAPI 프로젝트에 SQLAlchemy를 어떻게 통합하는지 본격적으로 코드를 통해 알아볼까요?

다음 섹션에서는 실습 위주로 하나씩 따라가며 설명드릴게요.

 

 

3. FastAPI 프로젝트에 SQLAlchemy 통합 🔗

3.1 SQLite로 로컬 개발 환경 구성하기

FastAPI와 SQLAlchemy를 연동할 때, 처음부터 복잡한 PostgreSQL 같은 DB를 연결하면 진입장벽이 높아질 수 있어요.

그래서 우리는 초보자에게 아주 친숙한 SQLite를 먼저 사용합니다. 설치할 필요도 없고, 파일 하나만 있으면 실행되니까요!

우선 SQLAlchemy 패키지를 설치해요. 터미널에서 아래 명령어를 실행합니다:

pip install sqlalchemy

그 다음, FastAPI 프로젝트 루트 디렉토리에 database.py라는 파일을 만들고

아래처럼 작성해 주세요:

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"  # 현재 디렉토리에 app.db 생성
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
Base = declarative_base()
  • create_engine: SQLite 파일로 연결되는 엔진 객체 생성
  • sessionmaker: 세션 팩토리. 세션 객체를 생성하여 DB 작업에 사용
  • declarative_base: 모든 모델이 상속받는 베이스 클래스 생성

3.2 DB 세션을 위한 의존성 주입 함수 만들기

FastAPI의 핵심 기능 중 하나인 의존성 주입을 활용해서 각 API 요청마다 자동으로 DB 세션을 열고 닫는 구조를 만들 수 있어요.

아래 코드를 database.py 파일 하단에 추가해보세요:

from sqlalchemy.orm import Session
from fastapi import Depends

def get_db():
    db: Session = SessionLocal()
    try:
        yield db
    finally:
        db.close()

이제 FastAPI 라우터 함수에서는 다음처럼 db: Session = Depends(get_db) 형태로 세션을 자동 주입받을 수 있습니다.

아주 편리하죠? 😄

3.3 구조 정리 – 폴더와 파일 구성은 이렇게

SQLAlchemy를 FastAPI 프로젝트에 통합할 때는 구조화가 중요합니다.

아래처럼 파일을 구성하면 확장성과 유지보수에 훨씬 유리해요.

📁 app/
├── main.py
├── database.py        # DB 연결 설정 및 세션 함수
├── models.py          # ORM 모델 클래스 정의
├── schemas.py         # Pydantic 스키마
└── crud.py            # DB CRUD 함수

이제 다음 단계에서는 실제로 모델을 정의하고 테이블을 생성하는 작업을 해볼 거예요.

드디어 DB와 코드가 연결되는 진짜 실습에 돌입합니다!

 

 

4. ORM 모델 정의와 테이블 생성 🏗️

4.1 모델 클래스 정의하기

ORM에서 가장 중요한 부분 중 하나는 모델 클래스 정의입니다.

여기서 "모델"은 데이터베이스의 테이블과 매핑되는 Python 클래스를 의미해요.

예를 들어 Todo 리스트를 저장하고 싶다면 Todo 모델을 만들어야겠죠?

이제 models.py 파일을 새로 만들어 아래와 같이 작성해봅시다:

# models.py
from sqlalchemy import Column, Integer, String, Boolean
from .database import Base

class Todo(Base):
    __tablename__ = "todos"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String, nullable=True)
    done = Column(Boolean, default=False)
  • __tablename__: 이 모델이 매핑될 실제 DB 테이블 이름
  • id: 기본 키. 자동 증가 설정
  • title, description: 문자열 컬럼, nullable=True로 NULL 허용
  • done: 완료 여부를 나타내는 불리언(Boolean) 컬럼

4.2 테이블 생성 – 코드로 자동화하기

모델 클래스를 정의했으면, 다음은 실제 SQLite DB 파일에 테이블을 생성해야 합니다.

별도로 SQL을 작성하지 않고도 간단히 처리할 수 있어요.

FastAPI 앱의 진입점인 main.py에 아래 코드를 추가해 주세요:

# main.py
from fastapi import FastAPI
from . import models
from .database import engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

create_all() 함수는 Base를 상속받은 모든 모델 클래스의 테이블을 데이터베이스에 자동으로 생성해줍니다.

이미 존재하는 테이블은 건너뛰기 때문에 안심하고 실행해도 돼요.

이제 서버를 실행하면, app.db 파일 안에 todos 테이블이 생기고, 우리가 정의한 컬럼들이 포함돼 있을 거예요. SQLite 브라우저를 통해 직접 확인해보는 것도 재미있겠죠? 😉

4.3 한눈에 보는 코드 구성 요약

파일 내용
database.py DB 연결 엔진, 세션 팩토리, Base 클래스 정의
models.py Todo 모델 클래스 정의
main.py FastAPI 앱 시작 시 테이블 자동 생성

이제 데이터베이스에 연결하고 테이블까지 만들었으니, 본격적으로 데이터를 넣고 꺼내는 CRUD 엔드포인트를 구현해 볼 차례예요.

 

다음 단계에서는 FastAPI 라우터와 SQLAlchemy 세션을 연결해 실제 데이터를 다뤄볼 거예요.

 

 

5. DB 세션 관리 및 CRUD 구현 🛠️

5.1 DB 세션 주입하기 (Dependency Injection)

FastAPI의 가장 멋진 기능 중 하나는 의존성 주입입니다.

각 API 요청에서 DB 세션을 쉽게 주입받을 수 있고, 요청이 끝나면 자동으로 세션을 닫아줘요. 🤩

앞서 만든 get_db() 함수를 기억하시죠? 이를 FastAPI 라우트 함수에 아래처럼 적용하면 됩니다:

from fastapi import Depends
from sqlalchemy.orm import Session
from .database import get_db

이제 API 핸들러에서 db: Session = Depends(get_db)를 인자로 추가하면 DB 연결이 자동으로 처리됩니다.

5.2 CRUD 기능 구현하기

이제 진짜 핵심인 CRUD 구현을 해볼게요!

예시로 Todo 항목을 데이터베이스에서 생성하고 불러오는 코드부터 시작합니다.

먼저 Pydantic 스키마부터 설정할게요:

# schemas.py
from pydantic import BaseModel

class TodoItemCreate(BaseModel):
    title: str
    description: str | None = None

class TodoItem(BaseModel):
    id: int
    title: str
    description: str | None = None
    done: bool

    class Config:
        orm_mode = True

이제 main.py 또는 router 파일에 API 핸들러를 작성해봅시다.

(1) Todo 생성

@app.post("/todos", response_model=schemas.TodoItem)
def create_todo(item: schemas.TodoItemCreate, db: Session = Depends(get_db)):
    db_todo = models.Todo(title=item.title, description=item.description)
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

(2) Todo 단일 조회

@app.get("/todos/{id}", response_model=schemas.TodoItem)
def read_todo(id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todo

(3) Todo 전체 조회

@app.get("/todos", response_model=list[schemas.TodoItem])
def read_all_todos(db: Session = Depends(get_db)):
    return db.query(models.Todo).all()

(4) Todo 상태 업데이트

@app.put("/todos/{id}", response_model=schemas.TodoItem)
def update_done_status(id: int, done: bool, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo.done = done
    db.commit()
    db.refresh(todo)
    return todo

(5) Todo 삭제

@app.delete("/todos/{id}", status_code=204)
def delete_todo(id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    db.delete(todo)
    db.commit()

5.3 정리: FastAPI + SQLAlchemy 조합의 강력함

  • DB 연결과 세션 관리를 코드 몇 줄로 자동화
  • ORM 모델과 Pydantic 스키마를 분리해 가독성과 유지보수 향상
  • API와 DB가 자연스럽게 연결되어 생산성 UP

이제 API를 실행해보면 SQLite 파일에 데이터가 저장되고, 브라우저나 API 클라이언트에서 요청을 보내도 데이터가 유지되는 걸 확인할 수 있어요.

이건 정말 개발자에게 기쁨이죠! 🎉

 

 

6. 실습 과제: SQLite를 이용한 Todo API 완성 🧪

6.1 실습 목표 정리

이번 섹션에서는 지금까지 배운 내용을 바탕으로 하나의 완성된 Todo API 프로젝트를 직접 만들어보는 게 목표예요. 🚀

FastAPI, SQLAlchemy, SQLite를 연결하여 CRUD 엔드포인트를 직접 구현하고 테스트하는 과정을 통해 백엔드 개발의 핵심을 체감할 수 있습니다.

6.2 실습 내용 정리 – 구현할 기능 목록 ✅

  • POST /todos → Todo 항목 생성
  • GET /todos → 전체 Todo 목록 조회
  • GET /todos/{id} → 특정 Todo 항목 조회
  • PUT /todos/{id}?done=true → 완료 상태 업데이트
  • DELETE /todos/{id} → Todo 항목 삭제

6.3 테스트 방법 및 점검 포인트 🔍

API는 Swagger UI를 통해 쉽게 테스트할 수 있어요.

http://127.0.0.1:8000/docs 주소를 브라우저에 입력하면, 자동 생성된 문서를 통해 각 요청을 테스트해볼 수 있습니다.

 

그리고 아래 항목들을 직접 확인해보세요:

  • POST 요청 후 실제 app.db 파일에 데이터가 저장되는가?
  • 서버를 재시작해도 데이터가 보존되는가?
  • 상태코드 (200, 201, 204, 404 등) 가 올바르게 반환되는가?

6.4 실습 마무리 및 다음 단계 안내 🧭

이번 실습을 통해 FastAPI 애플리케이션에 SQLAlchemy를 통합하고, 데이터를 영구 저장하는 진짜 백엔드 앱을 만들 수 있게 되었습니다. 🎉

 

다음 단계에서는 이 API를 바탕으로 프론트엔드와 연결하거나, 배포 환경을 준비하거나,

다른 DB (예: PostgreSQL)로 전환하는 연습을 해보면 좋겠죠? 😉

 

 

마무리🚀

지금까지 우리는 FastAPI와 SQLAlchemy를 활용해 데이터베이스와 연동되는 Todo API를 구축해봤습니다.

단순한 in-memory 데이터 저장 방식에서 벗어나, 이제는 SQLite 파일을 통해 데이터를 영구 저장하고 관리할 수 있는 수준으로 발전했어요. 🧱

ORM을 도입하면서 코드의 구조가 훨씬 깔끔해지고, 유지보수도 쉬워졌다는 걸 직접 체험해보셨을 겁니다.

특히 FastAPI의 의존성 주입과 SQLAlchemy의 강력한 매핑 기능이 얼마나 잘 어우러지는지도 느껴지셨을 거예요.

 

이제 여러분은 다음 단계로 나아갈 준비가 되었습니다.

PostgreSQL 같은 다른 관계형 DB로 확장하거나, Alembic을 통한 마이그레이션 관리, 혹은 Docker 환경에서의 운영 환경 구축 등 다양한 실전 영역에 도전해보세요!

 

🎯 오늘 다룬 기술을 응용하면 여러분만의 Todo 웹앱, 게시판, 메모장 등 다양한 백엔드 서비스로 발전시킬 수 있어요.  다음 편에서는 FastAPI와 프론트엔드(예: Streamlit)를 연동하는 방법도 소개할 예정입니다!

 

 

반응형
반응형

파이썬 FastAPI Pydantic을 통한 데이터 모델링과 검증

FastAPI의 진짜 힘은 바로 Pydantic에 있다!
데이터를 자동으로 검증하고, 문서화까지 완벽하게 해주는
이 강력한 조합을 여러분은 알고 계셨나요?

 

 

안녕하세요, 여러분!

이번 글에서는 FastAPI에서 가장 자주 사용되는 기능 중 하나인 Pydantic 기반의 데이터 모델링과 검증에 대해 알아보려 합니다.

FastAPI를 처음 접할 땐 간단한 라우팅으로 시작하지만, 실제 프로젝트로 들어가면 사용자 입력 데이터의 유효성을 검증하고 그에 맞는 응답 모델을 처리하는 것이 핵심이 됩니다.

이 모든 과정을 도와주는 것이 바로 Pydantic이죠. 특히 초보 개발자들에게는 서버로 들어오는 요청이 어떤 구조인지, 그 구조가 올바른지 자동으로 확인해주는 기능은 정말 고마운 존재예요.

예외 처리, 직렬화, 문서 자동화까지 한 번에 해결할 수 있으니, 놓칠 수 없겠죠?

 

1. Pydantic BaseModel 소개와 특징 ✨

FastAPI의 진짜 매력은 어디에 있을까요? 바로 Pydantic을 활용한 자동 데이터 검증 기능에 있습니다.

Python에서 데이터 모델을 정의할 때 많은 분들이 dataclass를 사용해봤을 텐데요, Pydantic은 거기에 한 단계 더 나아가 유효성 검사, 직렬화/역직렬화, 문서화까지 제공해주는 놀라운 도구입니다.

📌 BaseModel이란?

Pydantic의 핵심은 BaseModel입니다.

BaseModel을 상속한 클래스는 구조화된 데이터 모델로서 동작하며, 입력값의 타입을 검증하고, 오류가 있으면 상세한 메시지를 반환합니다.

 

예를 들어, 사용자의 정보를 받는 모델을 아래처럼 정의할 수 있습니다.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    is_active: bool = True
  • id: 정수형으로 강제
  • name: 문자열로 강제
  • is_active: 기본값 True로 설정

이 모델에 잘못된 타입이 들어오면 어떻게 될까요?

예를 들어 User(id="abc", name=123, is_active="yes")처럼 잘못된 데이터를 전달하면,

FastAPI는 422 Unprocessable Entity 오류와 함께 자세한 오류 메시지를 응답합니다.

📈 Pydantic의 장점 요약

기능 설명
타입 검증 정의된 필드 타입과 일치하지 않으면 자동으로 오류 반환
기본값 지정 필드를 선택적으로 선언 가능
입출력 직렬화 딕셔너리 ↔︎ 모델 객체 변환을 자동 처리
문서 자동화 FastAPI와 함께 사용할 경우 Swagger UI에 자동 반영

결론적으로,

Pydantic을 이용하면 데이터 구조 정의와 검증을 한 번에 처리할 수 있어 코드가 깔끔하고 안정적입니다.

초보자부터 숙련 개발자까지 모두에게 사랑받는 이유죠.

 

 

2. 데이터 유효성 검증과 오류 처리 🛡️

FastAPI에서 데이터 유효성 검증은 거의 자동으로 이뤄집니다.

Pydantic 모델을 엔드포인트 함수의 인자로 선언만 하면,

JSON 요청 바디를 파싱 → 타입 검증 → 오류 응답까지 단 한 줄의 추가 코드 없이 처리해주죠.

📥 요청 데이터 검증 흐름

  1. 클라이언트가 JSON 데이터를 POST로 전송
  2. FastAPI가 Pydantic 모델로 데이터 파싱 시도
  3. 정의된 필드 타입과 다르면 422 오류 발생
  4. 정상 파싱되면 뷰 함수에 모델 인스턴스를 넘겨줌

예를 들어, 사용자 이름을 문자열로 받아야 한다고 가정해봅시다.

그런데 클라이언트가 name: 1234처럼 숫자를 보내면?

{
  "detail": [
    {
      "loc": ["body", "name"],
      "msg": "str type expected",
      "type": "type_error.str"
    }
  ]
}

이처럼 RequestValidationError가 발생하고, FastAPI는 상세한 오류 메시지를 JSON으로 반환합니다.

개발자는 try-catch도 안 써도 되고, 클라이언트는 어떤 필드가 잘못됐는지 명확히 알 수 있죠.

⚙️ Field()를 통한 추가 제약 설정

Pydantic의 Field() 함수를 사용하면,

단순 타입 검증 외에도 문자 길이 제한, 정규표현식 검사, 기본값 설정, 예시 값 등을 지정할 수 있어요.

from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(..., min_length=2, max_length=50, example="홍길동")
    email: str = Field(..., regex=r'^\S+@\S+\.\S+$', example="user@example.com")
  • min_length, max_length: 문자열 길이 제약
  • regex: 정규표현식으로 포맷 검증
  • example: Swagger 문서에 예시로 표시

이렇게 Field를 적극 활용하면 API 입력 조건이 문서화와 동시에 자동 검증되어, 사용자와 개발자 모두에게 큰 도움이 됩니다.

✅ 요약: 검증이 자동이라 행복해요

  • FastAPI + Pydantic은 요청을 자동으로 검증하고 오류를 처리한다.
  • 별도의 try-except 없이도 잘못된 요청을 막을 수 있다.
  • Field()로 더욱 정밀한 제약 조건을 줄 수 있다.

이제 우리는 Pydantic이 단순한 타입 검사 도구가 아니라 API의 품질과 신뢰도를 끌어올려주는 핵심 기술이라는 걸 확실히 알게 되었죠!

 

 

3. 요청 바디 모델링 방법 📦

FastAPI에서는 클라이언트로부터 JSON 형식의 데이터를 받을 때 Pydantic 모델을 직접 함수 매개변수에 선언하는 방식으로 간단하게 요청 바디를 처리할 수 있습니다.

이 방식은 특히 입력값이 여러 필드로 구성된 복합 구조일 때 더욱 유용하게 쓰이죠.

📝 모델 정의와 요청 처리 예제

예를 들어 할 일(Todo)을 등록하는 API를 만든다고 해봅시다.

titledescription을 갖는 요청 바디를 받아야 한다면 아래와 같이 모델을 정의할 수 있어요.

from pydantic import BaseModel

class TodoItemCreate(BaseModel):
    title: str
    description: str | None = None  # 선택적 필드

이제 위 모델을 FastAPI 라우터 함수에 인자로 넣으면 됩니다.

FastAPI는 클라이언트의 JSON 요청을 자동으로 파싱하고 item 객체로 만들어줍니다.

@app.post("/todos", status_code=201)
def create_todo(item: TodoItemCreate):
    todo = {"id": len(todos) + 1, "title": item.title, "desc": item.description}
    todos.append(todo)
    return todo

📌 요청 실패 시 자동 오류 처리

만약 클라이언트가 필수 필드인 title을 빠뜨리거나 문자열 대신 숫자를 보낸다면, 함수는 아예 실행되지 않고 FastAPI가 422 Unprocessable Entity 오류를 자동으로 반환합니다.

덕분에 우리는 로직에만 집중할 수 있고, 예외 처리 코드는 거의 필요 없습니다.

🧾 Swagger 문서 자동 반영

놀랍게도, 위처럼 Pydantic 모델을 선언하면 /docs 경로에서 자동 생성되는 Swagger UI 문서에도 입력 필드, 설명, 데이터 타입, 예시 값까지 모두 자동으로 표시됩니다.

즉, 코드를 짜는 것만으로 문서까지 완성되는 셈이죠.

🧠 요청 바디 처리 시 체크포인트

  • BaseModel을 상속한 요청 모델을 선언하자
  • 요청 본문으로 JSON 데이터가 들어오면 FastAPI가 자동으로 Pydantic 모델로 변환한다
  • 모델에 정의된 조건을 만족하지 않으면 함수가 실행되지 않고 오류를 반환한다
  • Field()를 이용해 제약조건과 문서 예시를 추가할 수 있다

이처럼 요청 바디에 Pydantic 모델을 사용하면 코드가 훨씬 직관적이고 안전합니다.

특히 프론트엔드와 협업할 때 Swagger 문서 덕분에 API 스펙 공유가 정말 쉬워집니다.

 

 

4. 응답 모델 지정과 데이터 직렬화 📤

FastAPI에서는 요청을 받을 때뿐만 아니라 응답을 보낼 때도 Pydantic 모델을 활용할 수 있습니다. response_model 파라미터를 사용하면 반환할 데이터의 구조를 명확히 지정할 수 있어요.

이를 통해 보안성, 신뢰성, 문서화까지 한 번에 해결됩니다.

📦 response_model의 기본 개념

API 엔드포인트에서 반환할 데이터가 어떤 구조여야 하는지를 response_model에 정의해두면,

FastAPI는 자동으로 해당 구조로 직렬화(serialization)하고 그 외의 필드는 제외합니다.

예를 들어,

다음과 같은 모델이 있다고 해볼게요.

class TodoItem(BaseModel):
    id: int
    title: str
    description: str | None = None

이제 GET 요청에 대해 이 모델을 응답 모델로 지정하면 다음과 같은 라우팅 코드를 작성할 수 있어요.

@app.get("/todos/{id}", response_model=TodoItem)
def read_todo(id: int):
    todo = get_todo_by_id(id)
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todo

여기서 todo가 dict든 ORM 객체든 상관없이 FastAPI는 TodoItem 모델 형태로 자동 변환해 응답해줍니다.

내부적으로는 dict로 변환 후 JSON으로 직렬화됩니다.

🔒 보안성을 위한 필드 필터링

예를 들어 사용자 정보를 반환할 때 DB에는 hashed_password 같은 민감한 정보가 있을 수 있습니다.

이때 응답 모델에 해당 필드를 포함하지 않으면, FastAPI는 자동으로 해당 필드를 제거하고 안전한 응답만 반환합니다.

🧩 ORM 객체 대응을 위한 orm_mode

SQLAlchemy 같은 ORM을 사용할 경우, 모델 인스턴스를 바로 반환할 수 있는데요.

이때는 응답 모델 내부에 Config 클래스를 선언해 orm_mode = True로 설정해줘야 합니다.

class TodoItem(BaseModel):
    id: int
    title: str
    description: str | None = None

    class Config:
        orm_mode = True

이 설정을 해주면 FastAPI는 ORM 객체에서 필드를 자동으로 추출해서 응답 모델로 직렬화해줍니다.

🧾 response_model이 주는 이점 정리

장점 설명
데이터 필터링 불필요하거나 민감한 데이터 제거
타입 강제 모델에 맞는 타입으로 자동 변환
문서 자동화 Swagger UI에 응답 구조가 반영됨
보안 향상 출력 데이터의 제어 가능

결론적으로 response_model은 응답을 명시적으로 통제할 수 있는 강력한 기능입니다.

특히 API 문서 자동화와 보안 강화 측면에서 필수로 활용해야 할 요소죠!

 

 

5. 인메모리 Todo API 실습 예제 🧪

이제까지 배운 내용을 바탕으로 간단한 Todo API 예제를 만들어보며 실습해볼 시간이에요.

데이터베이스 없이 인메모리 리스트를 사용하여 CRUD 기능을 구현해볼 거예요.

이 과정을 통해 Pydantic 모델을 어떻게 활용하고, FastAPI의 기능과 어떻게 연결되는지 확실히 체감할 수 있습니다.

🔧 모델 정의

from pydantic import BaseModel
from typing import List, Optional

class TodoItemCreate(BaseModel):
    title: str
    description: Optional[str] = None

class TodoItem(TodoItemCreate):
    id: int
    done: bool = False

TodoItemCreate는 생성 요청용, TodoItem은 응답 및 조회용으로 사용됩니다.

생성 시에는 ID와 완료 여부가 없기 때문에 모델을 상속 구조로 나누어 중복을 줄였습니다.

🚀 엔드포인트 구현

from fastapi import FastAPI, HTTPException

app = FastAPI()
todos: List[TodoItem] = []
current_id = 0

@app.post("/todos", response_model=TodoItem, status_code=201)
def create_todo(item: TodoItemCreate):
    global current_id
    current_id += 1
    todo = TodoItem(id=current_id, **item.dict())
    todos.append(todo)
    return todo

@app.get("/todos", response_model=List[TodoItem])
def list_todos():
    return todos

@app.get("/todos/{id}", response_model=TodoItem)
def get_todo(id: int):
    for todo in todos:
        if todo.id == id:
            return todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.put("/todos/{id}", response_model=TodoItem)
def update_todo(id: int, done: bool):
    for todo in todos:
        if todo.id == id:
            todo.done = done
            return todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.delete("/todos/{id}", status_code=204)
def delete_todo(id: int):
    for idx, todo in enumerate(todos):
        if todo.id == id:
            todos.pop(idx)
            return
    raise HTTPException(status_code=404, detail="Todo not found")

📋 기능 요약

HTTP 메서드 엔드포인트 설명
POST /todos 할 일 생성
GET /todos 전체 할 일 목록 조회
GET /todos/{id} 단일 할 일 조회
PUT /todos/{id} 완료 여부 수정
DELETE /todos/{id} 삭제

이처럼 Pydantic 모델을 중심으로 CRUD API를 설계하면 입력과 출력의 명세가 명확해지고,

자동 문서화 기능까지 덤으로 얻을 수 있습니다.

작은 실습이지만, API 설계의 기본을 익히기에 딱 좋은 구조입니다!

 

 

6. 실습을 통한 개념 정리 및 확장 방향 📚

지금까지 FastAPI에서 Pydantic을 활용한 데이터 모델링과 검증에 대해 배우고, 인메모리 Todo API 예제를 통해 실습까지 마쳤습니다.

단순히 동작하는 API를 만들었다는 것 이상의 의미가 있어요.

이번 학습을 통해 우리는 다음과 같은 핵심 역량을 갖추게 되었어요.

🎯 핵심 학습 내용 정리

  • Pydantic의 BaseModel을 이용해 데이터 구조를 선언하고, 타입 검증과 기본값 설정까지 깔끔하게 처리하는 법을 익혔어요.
  • 요청 바디에 Pydantic 모델을 활용해 JSON을 자동 파싱하고 오류를 422 상태 코드로 처리하는 구조를 경험했죠.
  • response_model을 통해 응답 데이터를 명확히 통제하고, 민감 정보를 자동 제거하는 실전 팁도 확인했어요.
  • Swagger 문서화에 입력/출력 모델이 자동 반영되는 과정을 통해 개발 생산성을 실감했습니다.

🔮 다음 단계: 확장 방향 제안

이제 실전 프로젝트나 포트폴리오 개발로 확장하고 싶다면, 아래 내용을 고려해보세요.

  1. SQLite 또는 PostgreSQL 연동 – 실제 DB와 연동해 Todo를 저장하고 불러오는 구조로 확장하기
  2. SQLAlchemy ORM 사용 – ORM 객체와 Pydantic 모델을 함께 사용하는 방법 학습하기
  3. JWT 인증 추가 – 사용자 인증/인가 처리로 보안을 강화하기
  4. 테스트 코드 작성pytest 등을 이용한 유닛 테스트 추가로 품질 보장

🌈 마무리하며

단순히 작동하는 코드보다 더 중요한 건, 데이터의 흐름을 안전하고 명확하게 만드는 것입니다.

Pydantic은 그 시작이자 가장 든든한 동반자예요.

앞으로 여러분의 FastAPI 프로젝트에 이 강력한 도구를 적극 활용해보세요.

 

 

마무리 ✍️

이번 글에서는 FastAPI에서 Pydantic을 활용한 데이터 모델링과 검증 방법을 처음부터 끝까지 실습 중심으로 정리해봤습니다.

처음 접할 땐 어렵게 느껴질 수 있지만, 막상 하나하나 따라 하다 보면 자동 유효성 검사, 직렬화, 문서화 등 정말 많은 일을 FastAPI가 알아서 해주는 걸 느끼게 될 거예요.

여기서 끝나지 않고, 이제는 진짜 서비스에 연결할 준비를 해보세요.

 

SQLite, PostgreSQL 같은 실제 데이터베이스 연동부터, JWT 인증 구현, SQLAlchemy ORM 적용, 서비스 배포 등 단계별로 확장할 수 있습니다.

중요한 건, 이 모든 기반에는 Pydantic 모델링이 있다는 점이에요.

혹시 오늘 처음 FastAPI를 써보신 분이라면, 분명 놀라셨을 거예요.

어떻게 이렇게 적은 코드로 API가 만들어지고, 검증까지 되는지.

앞으로는 더 다양한 프로젝트에서 Pydantic을 능숙하게 다루실 수 있을 겁니다. 👍

 

이제 여러분의 프로젝트에서 Pydantic을 자신 있게 활용해보세요.

복잡한 데이터 구조도 깔끔하게, 예외 처리도 자동으로, API 문서화까지 한 번에! 😎

반응형
반응형

파이썬 FastAPI 요청 처리 심화 – 경로와 쿼리 매개변수, 다양한 요청 방식

FastAPI에서의 요청 처리는 단순한 URL 호출을 넘어, 변수, 쿼리, 그리고 다양한 HTTP 메서드로 확장됩니다. 그 핵심을 이번 글에서 파헤쳐볼게요!

 

 

안녕하세요, 개발자 여러분 😊

이번 글에서는 FastAPI의 진짜 매력을 느낄 수 있는 "요청 처리 심화" 파트를 다뤄보려 해요.

단순히 /hello 경로만 호출해서 문자열 반환하는 것에 만족하셨다면, 이제 한 단계 더 나아가 보죠.

경로 변수와 쿼리 파라미터, 그리고 다양한 HTTP 메서드를 활용한 CRUD API 설계까지, 여러분이 실전에서 꼭 만나게 될 상황들을 중심으로 소개합니다.

개발자라면 누구나 REST API를 다루게 되는데, FastAPI만큼 직관적이고 깔끔하게 처리할 수 있는 프레임워크는 드물어요.

이 글을 끝까지 따라오시면, 여러분도 FastAPI로 유연하고 강력한 API를 만드는 데 자신감을 가지게 될 거예요!

 

1. 경로 변수(Path Parameters)의 개념과 활용

FastAPI를 사용하다 보면 경로(URL) 안에 직접 데이터를 넣어서 API 요청을 날리는 경우가 정말 많습니다.

예를 들어

사용자 정보를 조회할 때 /users/10 이런 식으로 요청을 보내곤 하죠.

여기서 10경로 변수입니다.

FastAPI에서는 /users/{user_id}와 같이 중괄호({})를 사용해서 경로 변수를 선언할 수 있고, 해당 값은 함수 인자에서 받아올 수 있어요.

여기에 타입 힌트까지 붙이면 더 강력해지죠.

다음 예제를 함께 살펴볼게요.

📌 실습 예제 – 사용자 ID로 조회하기

@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}

 

👉 예를 들어 http://127.0.0.1:8000/users/7에 요청하면, {"user_id": 7} 이 응답됩니다!

🎯 경로 변수의 특징 정리

  • URL 경로 내에 값을 직접 삽입할 수 있다.
  • user_id: int처럼 타입 지정 가능 → FastAPI가 자동으로 검증
  • 잘못된 타입 요청 시 자동으로 422 오류 반환됨

📎 참고 팁

FastAPI는 Pydantic의 Path() 함수를 활용해 경로 변수에 대해 더 정교한 검증을 할 수 있도록 지원해요.

예를 들어,

최소값, 최대값을 설정하거나 설명을 추가할 수 있답니다.

이건 나중에 심화로 다룰 테니 일단 기본 개념만 확실히 익혀둬요!

✅ 요약 정리

  1. {변수명}으로 경로에 변수 삽입 가능
  2. 타입 힌트 사용 시 자동 변환 및 검증 처리
  3. 잘못된 타입 전달 시 422 Unprocessable Entity 오류

 

 

2. 쿼리 파라미터(Query Parameters)의 처리와 기본값 설정

FastAPI에서는 경로 이외에도 쿼리 파라미터를 아주 쉽게 처리할 수 있어요.

우리가 웹사이트에서 흔히 보는 ?page=1&size=10 같은 주소 기억나시죠?

이런 식의 URL에서 pagesize 같은 값들을 FastAPI는 아주 자연스럽게 함수 인자로 넘겨줍니다.

FastAPI는 함수에 정의된 매개변수가 경로 변수와 일치하지 않으면 자동으로 쿼리 문자열로 간주해서 값을 찾아요.

즉, /items?skip=5&limit=20 요청은 아래 함수로 처리할 수 있습니다.

🔍 실습 예제 – 아이템 페이징 처리

@app.get("/items")
def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

 

🔗 http://127.0.0.1:8000/items?skip=5&limit=20로 접속해보세요!

결과는 {"skip": 5, "limit": 20}으로 출력될 거예요.

⚙️ 기본값과 선택적 파라미터

매개변수에 = 기본값을 지정하면, 그 값은 선택적 파라미터가 됩니다.

예를 들어

위의 skip: int = 0은 값을 주지 않아도 기본적으로 0으로 설정돼요.

그래서 /items만 호출해도 오류 없이 작동합니다.

🎯 쿼리 파라미터의 활용 예

  • 게시글 목록 조회 시 페이징 처리 (page, size)
  • 상품 검색 시 필터링 옵션 전달 (keyword, category 등)

📎 고급 활용 – Query 함수로 유효성 검증

FastAPI는 Query()라는 유틸리티를 제공해요.

이걸 쓰면 최소값, 최대값, 정규식 검증 등 다양한 제약 조건을 설정할 수 있어요.

예를 들어:

from fastapi import Query

@app.get("/search")
def search_items(q: str = Query(..., min_length=3, max_length=50)):
    return {"query": q}

 

▶️ 이처럼 Query를 활용하면 검색어가 최소 3자 이상, 50자 이하일 때만 유효하게 동작하게 만들 수 있어요!

✅ 요약 정리

  1. 경로에 포함되지 않은 인자는 자동으로 쿼리 파라미터로 처리
  2. 기본값을 지정해 선택적 파라미터 구현 가능
  3. Query()를 통해 유효성 검사와 문서화 가능

 

 

3. 다양한 HTTP 메서드로 API 설계하기

웹 개발을 하다 보면 단순한 데이터 조회(GET) 외에도, 데이터를 생성, 수정, 삭제하는 다양한 작업이 필요하죠.

이럴 때 사용하는 것이 바로 HTTP 메서드입니다.

FastAPI는 @app.post(), @app.put(), @app.delete() 같은 데코레이터로 쉽게 메서드를 구분해 구현할 수 있어요.

 

보통 REST API에서 CRUD 작업은 다음과 같은 매핑으로 이루어져요:

  • POST – 자원 생성(Create)
  • GET – 자원 조회(Read)
  • PUT / PATCH – 자원 수정(Update)
  • DELETE – 자원 삭제(Delete)

🛠 실습 예제 – Todo 리스트 API

이제 간단한 Todo 리스트 API를 만들어볼게요.

아직 데이터베이스는 연결하지 않고, 메모리 안에 리스트로 관리할 거예요.

from typing import List

todos: List[str] = []

@app.post("/todos")
def create_todo(item: str):
    todos.append(item)
    return {"msg": "Todo created", "item": item}

@app.get("/todos")
def read_todos():
    return {"todos": todos}

@app.delete("/todos/{index}")
def delete_todo(index: int):
    if 0 <= index < len(todos):
        removed = todos.pop(index)
        return {"msg": "Todo deleted", "item": removed}
    else:
        return {"msg": "Index out of range", "index": index}

📌 포인트 정리

  • POST 요청으로 할 일(item)을 생성할 수 있어요.
  • GET 요청으로 현재 저장된 모든 할 일들을 조회할 수 있어요.
  • DELETE 요청으로 특정 인덱스의 할 일을 삭제할 수 있어요.

✨ 추후에는 item을 문자열 대신 JSON 구조로 받고, 유효성 검사도 추가할 예정입니다.

      지금은 기본 동작 원리를 익히는 게 목표예요!

 

 

4. 간단한 Todo 리스트 API 실습

이번에는 앞서 배운 내용을 바탕으로 아주 간단한 Todo API를 하나씩 구현해볼 거예요.

복잡한 데이터베이스는 사용하지 않고, 우선 파이썬 리스트에 데이터를 저장하면서 개념과 흐름을 익히는 데 집중해봅시다.

📝 기능 목록

  • POST – 새로운 Todo 항목 추가
  • GET – 전체 Todo 항목 조회
  • DELETE – 특정 항목 삭제

이 Todo API는 FastAPI의 기본 기능만으로도 손쉽게 만들 수 있어요.

아래는 전체 구현 코드입니다.

from typing import List
from fastapi import FastAPI

app = FastAPI()
todos: List[str] = []

@app.post("/todos")
def create_todo(item: str):
    todos.append(item)
    return {"msg": "Todo created", "item": item}

@app.get("/todos")
def read_todos():
    return {"todos": todos}

@app.delete("/todos/{index}")
def delete_todo(index: int):
    if 0 <= index < len(todos):
        removed = todos.pop(index)
        return {"msg": "Todo deleted", "item": removed}
    else:
        return {"msg": "Index out of range", "index": index}

💡 테스트 방법

  1. 터미널에서 uvicorn main:app --reload로 실행
  2. http://127.0.0.1:8000/docs로 접속
  3. Swagger UI에서 API 직접 테스트

📎 실전 팁

현재는 문자열 하나만 다루고 있지만, 보통은 title, description, due_date 같은 구조화된 데이터를 다루게 돼요.

그래서 다음에는 Pydantic 모델을 이용한 JSON 구조 요청 처리도 이어서 다뤄볼 거예요.

 

🛠 지금은 구조보다 흐름과 메서드별 동작 방식을 이해하는 게 포인트입니다!

 

 

5. 응답 코드 설정과 HTTPException 사용법

API를 개발하다 보면 어떤 작업이 성공했는지, 실패했는지 클라이언트에게 명확한 상태 코드로 알려주는 게 매우 중요해요.

FastAPI에서는 status_code 파라미터나 HTTPException 클래스를 이용해서 간단하게 응답 상태를 설정할 수 있습니다.

🔢 status_code 파라미터

기본적으로 FastAPI는 정상 응답 시 200 OK 상태 코드를 반환하지만, POST 요청에서 자원이 성공적으로 생성됐다는 의미로는 201 Created를 사용하는 것이 더 적절해요.

from fastapi import FastAPI

app = FastAPI()

@app.post("/todos", status_code=201)
def create_todo(item: str):
    return {"msg": "Todo created", "item": item}

 

📍 위처럼 status_code=201을 명시하면 Swagger 문서(/docs)에서도 명확히 표현되고, 실제 응답 헤더에도 적용돼요.

🚨 HTTPException으로 에러 처리하기

클라이언트가 잘못된 요청을 보냈을 때, 그냥 텍스트로 알려주는 대신 HTTPException을 활용하면 더 표준적인 방식으로 에러를 응답할 수 있어요.

FastAPI는 이 기능을 아주 깔끔하게 지원합니다.

from fastapi import HTTPException

@app.delete("/todos/{index}")
def delete_todo(index: int):
    if 0 <= index < len(todos):
        removed = todos.pop(index)
        return {"msg": "Todo deleted", "item": removed}
    else:
        raise HTTPException(status_code=404, detail="Todo index out of range")

 

❗ 이렇게 하면 404 Not Found 상태 코드와 함께 에러 메시지가 JSON 형태로 전달됩니다.

🧾 예시 응답 구조

{
  "detail": "Todo index out of range"
}

✅ 요약 정리

  1. status_code 파라미터로 성공 응답 상태 지정 가능 (예: 201)
  2. 에러 상황에는 HTTPException을 활용해 상태 코드 + 상세 메시지 전달
  3. Swagger 문서에 자동으로 반영되어 테스트가 쉬움

🧠 상태 코드와 예외 처리는 API 설계에서 신뢰도를 높이는 핵심 요소예요.

      놓치지 말고 꼭 적용해보세요!

 

 

6. 자동 문서화 /docs로 확인하는 API 인터페이스

FastAPI를 처음 접했을 때 가장 감탄했던 기능 중 하나가 자동 API 문서화였어요.

우리가 별도로 Swagger를 설정하지 않아도, FastAPI는 프로젝트를 실행하면 /docs 경로에 멋진 UI를 제공해줍니다.

이 문서화 기능은 Swagger UI를 기반으로 하고 있으며, OpenAPI 스펙을 따르기 때문에 API를 설계, 테스트, 설명하는 데 매우 유용합니다.

📘 /docs에서 확인할 수 있는 정보들

  • 등록된 모든 API 경로 (GET, POST, DELETE 등)
  • 각 API가 사용하는 요청 파라미터와 타입
  • 응답 형식 및 예시 JSON
  • 상태 코드 정보 및 설명

📌 테스트도 직접 가능!

Swagger UI의 진짜 매력은 단순 문서화가 아니라 인터랙티브한 테스트 기능입니다.

각 API 아래에 있는 "Try it out" 버튼을 누르면, 실제로 파라미터를 입력해보고 바로 응답 결과를 확인할 수 있어요.

 

🧪 서버를 켜둔 상태에서 http://127.0.0.1:8000/docs 로 접속하면 지금까지 만든 Todo API를 바로 테스트할 수 있습니다!

📄 응답과 상태 코드 자동 문서화

우리가 status_codeHTTPException을 지정해 놓으면 그 정보 또한 Swagger UI에 자동으로 반영돼요.

예를 들어

Todo 삭제 API에서 404 에러가 발생할 수 있다고 하면, 이 상황도 문서 상에 함께 노출돼서 클라이언트 개발자도 명확히 이해할 수 있습니다.

✅ 요약 정리

  1. FastAPI는 기본적으로 Swagger UI 기반의 문서 제공
  2. 각 API의 경로, 메서드, 파라미터, 응답을 자동 문서화
  3. /docs 페이지에서 직접 테스트 가능

🚀 자동 문서화 기능은 FastAPI의 가장 큰 장점 중 하나입니다.

      실무에서는 팀원들과 협업하거나 외부 개발자에게 API를 설명할 때 이 기능이 정말 유용하게 쓰이죠.

 

 

마무리

여기까지 따라오셨다면 FastAPI의 요청 처리 방식에 대해 꽤나 깊이 있게 이해하셨을 거예요.

단순한 라우팅을 넘어, 경로 변수와 쿼리 파라미터를 어떻게 활용할 수 있는지, 그리고 다양한 HTTP 메서드와 응답 코드 설정, 예외 처리, 자동 문서화까지…

이 모든 요소들이 잘 어우러져야 실용적이고 유지보수하기 쉬운 API를 만들 수 있습니다.

FastAPI는 배우기도 쉽고, 작성한 코드가 곧 문서가 되어주기 때문에 백엔드 API를 처음 만드는 분들에게도 정말 좋은 선택이에요.

 

다음 단계에서는 Pydantic 모델을 활용한 본문(JSON) 처리와 입력 검증에 대해 다룰 예정입니다.

점점 더 실무에 가까운 내용으로 나아가니, 기대해 주세요!

 

📌 오늘 배운 내용은 실제 웹 서비스,

      예를 들어 블로그 API나 게시판 API처럼 사용자와 데이터를 주고받는 시스템의 기본이 되는 부분입니다.

      꼭 복습하시고, 실습도 꼭 해보세요!

반응형
반응형

FastAPI로 배우는 REST API 개발 입문

REST API, 아직도 어렵게 느껴지시나요?
FastAPI를 이용하면 믿을 수 없을 만큼 쉽게 웹 API를 만들 수 있어요!

 

 

안녕하세요!

오늘부터 여러분과 함께 Python의 강력한 웹 프레임워크 FastAPI를 활용한 REST API 개발을 단계별로 배워보려고 합니다.

요즘 웹 개발에 있어서 REST API는 기본 중의 기본이죠.

다양한 시스템이 서로 데이터를 주고받기 위해 가장 많이 사용되는 방식입니다.

특히 FastAPI는 빠르고 간편하며, 자동 문서화 기능까지 갖춰져 있어 초보자도 쉽게 API를 개발할 수 있는 환상적인 도구예요.

이 블로그 시리즈에서는 REST API의 개념부터 시작해서, FastAPI 설치, Hello World 예제, 엔드포인트 추가 실습까지 하나하나 직접 구현해보며 개념과 실습을 모두 챙길 거예요.

준비되셨나요?

그럼 첫 번째 이야기, REST API와 FastAPI의 만남을 시작해볼게요! 🚀

 

1. REST API란 무엇인가요? 🌐

REST API는 웹에서 데이터를 주고받는 가장 표준적인 방법 중 하나입니다.

먼저 이름부터 풀어볼까요?

REST는 Representational State Transfer의 약자인데요, 말이 좀 어렵게 느껴질 수 있지만 핵심은 간단합니다.

"웹 자원을 고유한 주소(URI)로 표현하고, 그 자원에 대해 HTTP 메서드를 통해 동작을 지정한다"는 게 핵심이에요.

우리가 평소에 웹 브라우저에서 주소창에 https://example.com/users를 입력하면 "users"라는 자원(데이터 목록)에 접근하는 거잖아요?

이처럼 REST에서는 각각의 자원을 URL을 통해 표현합니다.

그리고 어떤 동작을 하고 싶은지에 따라 GET, POST, PUT, DELETE 같은 HTTP 메서드를 사용해 요청을 보냅니다.

📌 REST 아키텍처의 6가지 원칙

  • 클라이언트-서버 구조 : UI와 데이터 처리를 분리해 독립적으로 발전 가능
  • 무상태성 (Stateless) : 서버는 요청을 받을 때 클라이언트의 상태를 기억하지 않음
  • 캐시 처리 가능 : 클라이언트는 서버 응답을 캐싱해 효율성 향상 가능
  • 계층화된 시스템 : 중간 서버를 통해 보안, 로드 밸런싱 등 기능 분리
  • 인터페이스 일관성 : URI 설계와 메서드 사용이 일관되게 유지되어야 함
  • 코드 온 디맨드(Optional) : 서버에서 클라이언트로 스크립트 등을 전달 가능

🧩 RESTful API 설계의 예시

HTTP 메서드 URI 설명
GET /users 모든 사용자 조회
GET /users/123 ID가 123인 사용자 조회
POST /users 새 사용자 등록
PUT /users/123 ID가 123인 사용자 정보 수정
DELETE /users/123 ID가 123인 사용자 삭제

이러한 설계 방식 덕분에 RESTful API는 명확하고 예측 가능하며 유지보수가 쉬운 구조를 가질 수 있어요.

그래서 요즘 거의 모든 서비스가 REST API 기반으로 구성되고 있죠.

그리고 이걸 Python으로 정말 쉽게 만들 수 있도록 도와주는 도구가 바로 FastAPI입니다.

 

 

2. RESTful API의 구조와 특징 🔍

RESTful API는 단순히 REST 원칙을 따르는 API 이상을 의미해요.

클라이언트와 서버 간의 소통을 효율적이고 일관되게 만들어주는 아키텍처 스타일이자 규칙의 집합이죠.

흔히들 “REST스럽다”는 표현을 쓰는데, 이는 API가 REST의 원칙을 잘 따르고 있다는 의미예요.

✅ RESTful API의 구조적 특징

  1. URI를 통한 자원 표현 – 모든 자원은 고유한 URI로 식별됩니다. 예: /users, /posts/1
  2. HTTP 메서드의 의미 명확화 – CRUD 작업을 각각 GET, POST, PUT, DELETE로 매핑합니다.
  3. 무상태성(Stateless) – 요청은 독립적으로 처리되며, 서버는 이전 요청 상태를 저장하지 않습니다.
  4. 표현(Representation)의 활용 – 클라이언트는 자원의 ‘표현’을 받으며, 보통은 JSON이나 XML 형식을 사용합니다.
  5. 표준 HTTP 상태 코드 사용 – 예: 200(성공), 201(생성됨), 404(없음), 500(서버 오류)

💡 RESTful API가 주는 이점

RESTful API를 설계하면 얻게 되는 이점도 많아요.

가장 큰 장점은 바로 일관성과 가독성입니다.

 

예를 들어,

다음처럼 URI를 설계한다면 처음 보는 사람도 어떤 기능인지 대충 감이 올 거예요.

  • GET /articles – 전체 글 목록 불러오기
  • POST /articles – 새 글 작성
  • GET /articles/3 – ID가 3인 글 조회

 

이처럼 RESTful한 API는 예측 가능하고 규칙 기반이기 때문에 협업 시에도 소통이 쉬워지고, 문서화를 따로 하지 않아도 사용할 수 있을 만큼 직관적인 경우도 많습니다.

 

❗REST API vs RESTful API – 혼동하지 마세요

간혹 REST API와 RESTful API를 같은 개념으로 쓰기도 하지만, 기술적으로는 살짝 차이가 있어요.

REST API는 단지 REST 기반 구조를 사용하는 API를 의미하고, RESTful API는 REST의 원칙을 충실히 따르는 ‘REST다운’ API를 말합니다.

즉, RESTful API는 REST API보다 좀 더 엄격한 규칙을 따른다는 뜻이에요.

 

RESTful한 구조를 유지하기 위해서는 불필요한 동사 사용을 피하고, 자원 중심으로 URI를 설계하며, HTTP 상태 코드도 적절히 활용해야 해요.

이런 기준을 지켜야만 “RESTful하다”고 말할 수 있답니다!

이제 RESTful API의 기본 철학과 구조에 대해 어느 정도 감이 오셨죠? 😉

 

다음 섹션에서는 FastAPI라는 도구를 통해 이런 RESTful API를 실제로 구현하는 방법을 배워볼 거예요.

기대되시죠?

 

 

3. FastAPI 프레임워크 소개 🚀

FastAPI는 최근 Python 웹 개발자들 사이에서 가장 주목받고 있는 비동기 기반 웹 프레임워크입니다.

Flask나 Django처럼 웹 애플리케이션을 만들 수 있으면서도,

비동기 처리, 자동 문서화, 타입 기반 유효성 검사 같은 최신 기능들을 기본으로 제공합니다.

📌 FastAPI의 핵심 특징

  • 빠른 성능 – Starlette(ASGI 서버) 기반으로 구성되어 Node.js나 Go와 맞먹는 수준의 퍼포먼스를 자랑합니다.
  • 타입 기반 유효성 검사 – Python의 타입 힌트를 활용해 Pydantic이 자동으로 데이터 검증 및 스키마 생성을 해줍니다.
  • 자동 문서화 – OpenAPI(Swagger)와 ReDoc 기반 문서가 자동 생성되어 /docs/redoc 경로에서 API 테스트도 가능합니다.
  • 비동기 처리 지원async/await 문법으로 고성능 API 서버 구현도 손쉽게 가능합니다.

⚙️ 왜 FastAPI인가요?

처음에는 Flask로도 충분하지 않나? 하는 생각이 들 수도 있어요. 저도 그랬거든요.

하지만 FastAPI를 사용해보면 정말 많은 차이를 느낄 수 있습니다.

특히 Swagger 문서 자동 생성비동기 처리 성능은 다른 프레임워크들이 따라오기 어려울 정도예요.

여기에 Python의 타입 힌트를 적극적으로 활용해, 코드 작성과 동시에 문서화와 유효성 검사를 자동으로 처리해주는 점이 초보자들에게도 큰 장점으로 작용합니다.

비교 항목 Flask FastAPI
성능 중간 매우 빠름
문서화 수동 또는 확장 자동 제공 (Swagger / ReDoc)
타입 힌트 지원 선택적 강력히 활용
비동기 지원 간접적 지원 기본 제공

결론적으로 FastAPI는 빠르게 프로토타입을 만들고, 동시에 안정성과 문서화까지 챙기고 싶은 개발자에게 최적의 선택이에요.

Python을 이미 알고 있다면, FastAPI는 자연스럽고 직관적으로 배울 수 있습니다.

 

다음 글에서는 실제로 FastAPI를 설치하고 기본 개발 환경을 세팅하는 과정을 차근차근 안내해드릴게요.

이제 실습을 통해 본격적으로 시작해볼까요? 🔧

 

 

4. 개발 환경 설정과 FastAPI 설치 ⚙️

지금부터는 본격적으로 FastAPI를 개발 환경에 설치해보는 시간입니다.

FastAPI는 Python 3.7 이상에서 사용할 수 있기 때문에, 우선 Python 버전 확인부터 해볼게요.

그리고 가상환경을 만들어서 프로젝트별 의존성을 관리하는 것이 좋습니다.

🔍 Python 및 가상환경 설정

  1. Python 버전 확인: python --version
  2. 가상환경 생성: python -m venv venv
  3. 가상환경 활성화
    • Windows: venv\Scripts\activate
    • Mac/Linux: source venv/bin/activate

가상환경이 활성화되면 프롬프트 앞에 (venv)가 붙어요.

이제 여기서 필요한 라이브러리를 설치하면 됩니다.

📦 FastAPI와 Uvicorn 설치

FastAPI는 자체적으로 웹 서버를 포함하고 있지 않기 때문에 ASGI 서버가 필요해요.

대부분의 개발자들은 Uvicorn을 사용합니다.

 

pip install fastapi
pip install "uvicorn[standard]"

 

[standard] 옵션을 포함하면 HTTP/2, WebSocket 등 다양한 기능도 함께 설치돼요.

설치가 끝나면 FastAPI 애플리케이션을 실행할 준비가 완료된 셈입니다.

📝 프로젝트 구조와 첫 파일 만들기

간단한 FastAPI 프로젝트 구조는 다음과 같아요:

  • main.py – 앱 실행 파일
  • venv/ – 가상환경 폴더

main.py 파일을 열고 FastAPI 기본 코드를 작성할 준비를 합니다.

다음 단계에서는 “Hello, FastAPI”라는 응답을 반환하는 아주 간단한 예제를 통해 첫 API를 직접 만들어볼 거예요.

지금까지 잘 따라오셨나요? 🧑‍💻

환경 설정만 마쳐도 절반은 완성된 셈입니다!

 

이제 직접 서버를 띄워보며 FastAPI가 어떻게 동작하는지 살펴보겠습니다.

 

 

5. Hello FastAPI - 첫 API 만들기 👋

드디어 우리가 기다리던 순간!

이제 FastAPI를 직접 실행해 보며 첫 번째 API를 만들어볼 거예요.

처음 만드는 만큼 아주 단순한 "Hello, FastAPI" 메시지를 반환하는 예제부터 시작해 보겠습니다.

📄 main.py 코드 작성

이제 main.py 파일을 열고

아래와 같이 코드를 작성해 주세요:

 

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
  return {"message": "Hello, FastAPI"}

 

이 코드에서 @app.get("/")는 루트 경로(/)에 GET 요청이 들어왔을 때 실행되는 함수입니다.

return 부분에서 반환하는 딕셔너리는 자동으로 JSON으로 변환돼서 응답으로 전송돼요.

🚀 서버 실행

이제 작성한 FastAPI 앱을 실행해볼게요.

터미널에 다음 명령어를 입력하세요:

 

uvicorn main:app --reload

 

main은 파일 이름, app은 FastAPI 객체입니다.

--reload 옵션을 붙이면 코드 변경 시 서버가 자동으로 재시작돼서 개발할 때 아주 편리하죠!

🔎 실행 결과 확인하기

정말 신기하지 않나요?

별도의 설정 없이도 이렇게 깔끔한 API 문서가 자동 생성됩니다. 🧙‍♂️

그리고 이게 FastAPI의 가장 큰 매력 중 하나예요. 작성한 API들을 테스트도 가능하니, 개발과 동시에 검증까지 가능하죠.

이제 FastAPI의 기본 구조와 실행 흐름을 이해하셨을 거예요.

다음 단계에서는 실제로 또 하나의 엔드포인트를 추가하면서 다양한 HTTP 메서드와 라우팅 개념도 함께 살펴보겠습니다.

 

 

6. 엔드포인트 추가 실습: 버전 정보 API 구현 🛠️

이전 단계에서 GET /으로 기본 응답을 반환하는 간단한 API를 만들어봤죠.

이번에는 FastAPI 앱에 새로운 엔드포인트를 하나 더 추가해보겠습니다.

예를 들어,

프로젝트의 버전 정보를 클라이언트에게 알려주는 API가 있다고 가정해볼게요.

📄 main.py에 엔드포인트 추가

@app.get("/version")
def get_version():
  return {"version": "0.1.0"}

 

이 함수는 /version 경로에 GET 요청이 들어왔을 때 버전 정보를 JSON 형식으로 반환합니다.

예를 들어,

클라이언트가 API의 버전 관리를 하고자 할 때 유용하게 사용할 수 있어요.

🔎 실행 후 테스트하기

📌 엔드포인트를 추가하면서 익히는 FastAPI의 핵심

  • 라우팅 – 경로(URL)마다 함수 하나씩 대응시켜 구조적으로 관리
  • 함수 기반 뷰(View) – 각각의 엔드포인트는 Python 함수로 표현
  • 자동 문서화 – 개발자가 문서를 별도로 작성하지 않아도 FastAPI가 Swagger 문서를 자동 생성

 

이번 실습을 통해 RESTful API의 핵심인 라우팅 설계를 직접 해보셨고, FastAPI가 얼마나 직관적이고 강력한지 직접 체험하셨을 거예요. 엔드포인트 추가는 이제 식은 죽 먹기죠?

점점 실전 API에 가까워지고 있죠? 😊

지금까지 우리는 REST API의 개념부터 FastAPI 설치, 기본 예제 구현까지 한 걸음씩 따라와 봤습니다.

처음에는 다소 낯설 수 있었던 REST 구조도, FastAPI의 직관적인 문법과 자동 문서화 덕분에 훨씬 더 쉽게 접근할 수 있었죠.

 

RESTful한 방식으로 API를 설계하면,

확장성과 유지보수성을 동시에 확보할 수 있다는 점에서 그 가치가 분명합니다.

FastAPI는 여기에 더해 빠른 속도, 간결한 코드, 뛰어난 생산성까지 갖추고 있어 Python 개발자라면 반드시 익혀야 할 도구예요.

 

다음 글에서는 POST 요청 처리입력 데이터 검증을 다룰 예정입니다.

본격적으로 사용자의 입력을 받아 처리하고, Pydantic을 활용한 스키마 기반 검증도 함께 배워볼 거예요.

기대되시죠? 😊

 

이 글이 도움이 되셨다면 댓글이나 공유도 환영합니다.

앞으로도 쉽고 실용적인 Python 백엔드 개발 이야기, 계속 함께해 주세요!

반응형
반응형

Flask 웹 애플리케이션 배포 및 전체 개발 여정 마무리

이제 Flask로 만든 내 앱을 세상 밖으로 꺼내놓을 시간입니다.
코딩만으로는 끝나지 않는 웹 개발의 진짜 마무리,
배포의 세계로 함께 떠나볼까요?

 

 

안녕하세요! 어느덧 8일 간의 Flask 웹 개발 여정이 끝을 향해 가고 있습니다.

오늘은 정말 중요한 시간이자, 실제 서비스를 꿈꾸는 개발자라면 반드시 짚고 넘어가야 할 Flask 애플리케이션의 배포 과정을 다룹니다.

개발 환경에서만 돌아가던 코드가 운영 환경에서,

즉 ‘실제 인터넷 사용자’에게 서비스되기 위해 어떤 과정이 필요한지, 어떤 요소들을 점검해야 하는지 차근차근 살펴볼 거예요.

그리고 지난 8일 동안 우리가 배운 기술들을 돌아보며, 앞으로 어떤 방향으로 더 성장할 수 있을지도 함께 이야기해 보겠습니다.

실전 배포의 긴장감 속에서도, 마무리의 뿌듯함과 성취감을 느끼실 수 있을 거예요 😊

 

1. 개발 환경 vs 운영 환경 설정 차이 이해하기 ⚙️

웹 애플리케이션을 개발하면서 가장 흔하게 저지르는 실수 중 하나는 개발 환경운영 환경을 동일하게 다룬다는 점입니다.

개발은 말 그대로 테스트와 디버깅이 용이하게 구성되어 있지만, 운영 환경은 실제 사용자와의 만남이기 때문에 보안, 성능, 안정성 등 모든 면에서 더욱 철저하게 대비해야 합니다.

🔐 DEBUG 모드 해제와 그 중요성

개발할 때는 DEBUG=True로 설정해서 코드 수정 후 바로 반영되도록 하고, 에러가 발생하면 상세한 디버거 화면을 확인할 수 있어서 편하죠.

하지만 운영에서는 반드시 DEBUG=False로 변경해야 합니다.

이유는 간단해요.

에러 메시지를 통해 시스템 내부 구조나 경로, 변수명이 노출될 수 있기 때문이에요.

이것만으로도 보안에 심각한 구멍이 생길 수 있죠.

  • DEBUG=False 설정은 사용자에게 민감한 에러 정보를 숨겨주는 역할
  • 에러 추적은 logging 모듈을 통해 따로 처리

📦 환경 변수로 민감 정보 분리하기

운영 환경에서는 SECRET_KEYDB 비밀번호 같은 민감한 정보가 절대 코드에 직접 노출되어선 안 됩니다.

이를 위해 .env 파일을 사용하거나, 운영체제의 환경 변수에 등록해 관리하는 것이 일반적이에요.

Flask에서는 python-dotenv 같은 라이브러리를 사용하면 .env 파일을 쉽게 로드할 수 있죠.

예시 - .env 파일

SECRET_KEY=mysecretkey123
DATABASE_URL=mysql://user:password@localhost/db
FLASK_ENV=production

🛠️ 개발 DB vs 운영 DB

개발 환경에서는 SQLite 같은 간단한 파일 기반 데이터베이스를 사용하는 경우가 많지만,

운영 환경에서는 MySQL, PostgreSQL 같은 상용 또는 오픈소스 DB를 사용해야 합니다.

특히 트래픽이 많아질 경우 DB 성능이 앱의 전체 성능에 큰 영향을 끼치기 때문에,

개발 단계부터 운영 DB로의 전환을 염두에 두고 설계하는 습관이 중요해요.

📈 로그 기록과 모니터링 준비

운영 환경에서는 예기치 않은 문제를 발견하기 위해 로그 설정이 중요합니다.

Python의 logging 모듈을 활용해서 파일로 저장하거나, 콘솔/서버 스트림으로 출력하게 설정할 수 있죠.

Sentry, Datadog 같은 SaaS 기반 모니터링 도구도 함께 사용하면 더 좋습니다.

추후 확장 가능성까지 고려한다면 꼭 챙겨야 할 요소입니다.

간단한 logging 설정 예시

import logging

logging.basicConfig(
    filename='app.log',
    level=logging.INFO,
    format='%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
)

 

이처럼 운영 환경은 단순히 서버에 올리는 것 이상입니다.

전반적인 시스템의 신뢰성과 보안을 고려한 전략이 필요하죠.

다음 섹션에서는 본격적으로 배포를 위해 어떤 준비가 필요한지 살펴보겠습니다.

 

 

2. 배포를 위한 필수 설정 및 준비물 🔧

이제 운영 환경으로 옮길 준비를 본격적으로 해야겠죠.

Flask 애플리케이션을 배포하려면 몇 가지 반드시 준비해야 할 요소들이 있어요.

이 항목들은 단순히 앱을 ‘올린다’는 행위를 넘어, 안정적이고 유지보수가 가능한 형태로 운영하는 데 핵심이 됩니다.

🗂 필수 파일 구성

운영 배포를 위해 다음과 같은 파일들을 준비해두면 좋습니다.

특히 Heroku나 Railway 같은 플랫폼에서는 Procfilerequirements.txt가 필수입니다.

  • requirements.txt: 필요한 패키지 목록을 나열한 파일 (예: Flask, gunicorn 등)
  • Procfile: 실행 명령어를 담은 파일 (예: web: gunicorn app:app)
  • .env: 환경 변수 파일 (운영 시 실제 민감 정보 포함)
  • runtime.txt: 사용하는 파이썬 버전 지정 (Heroku에서 권장됨)

🌐 WSGI 서버 준비 - Gunicorn

Flask는 기본적으로 내장 개발용 서버(flask run)를 사용하지만, 운영용 서버로는 성능과 안정성이 부족해요.

그래서 WSGI(Web Server Gateway Interface)를 사용하는 서버로 전환해야 하죠.

대표적인 선택이 Gunicorn입니다.

pip install gunicorn
gunicorn app:app

 

이 명령은 현재 디렉토리에 있는 app.py 파일 안의 app 객체를 실행합니다.

Gunicorn은 멀티 스레드와 멀티 프로세스를 지원해서, 더 많은 요청을 처리할 수 있도록 해줍니다.

Windows 사용자를 위한 대안

Gunicorn은 Windows에서는 제대로 작동하지 않기 때문에 Waitress 서버를 대안으로 사용할 수 있어요.

pip install waitress
waitress-serve --port=8080 app:app

📁 정적 파일 서빙 준비

Flask도 정적 파일 서빙이 가능하지만,

실제 서비스에서는 Nginx 같은 웹 서버를 활용하는 것이 효율적이에요.

CSS, JS, 이미지 파일은 모두 Nginx가 처리하고, 동적인 라우팅 요청만 Flask로 전달되게 설정하는 게 일반적인 방식입니다.

이렇게 하면 정적 리소스의 로딩 속도도 빨라지고, Flask의 부하도 줄일 수 있어서 훨씬 쾌적한 서비스가 가능해집니다.

 

이제 기본적인 배포 준비물들을 정리했으니,

다음 단계에서는 실제로 Gunicorn과 Nginx를 활용해 애플리케이션을 배포하는 과정을 실습해볼게요!

 

 

3. Gunicorn과 Nginx를 이용한 배포 실습 🚀

이제 본격적으로 Flask 애플리케이션을 운영 환경에 배포하는 실습을 진행해보겠습니다.

이번에는 가장 널리 쓰이는 조합인 Gunicorn + Nginx 환경을 기반으로 설명할게요.

이 조합은 성능과 확장성, 안정성 측면에서 많은 Flask 프로젝트에서 사용됩니다.

🧱 Gunicorn으로 Flask 실행하기

Gunicorn은 Python WSGI 애플리케이션을 실행할 수 있는 고성능 WSGI 서버입니다.

먼저 아래와 같이 애플리케이션을 실행해보세요:

gunicorn -w 4 -b 127.0.0.1:8000 app:app

 

이 명령은 4개의 워커(worker)를 생성하여 127.0.0.1:8000 포트에서 애플리케이션을 실행합니다.

로컬에서 테스트할 경우 웹 브라우저에서 http://127.0.0.1:8000으로 접속하면 됩니다.

🌐 Nginx 설치 및 설정

이제 웹 서버인 Nginx를 설치하고 Gunicorn과 연동해봅니다.

Nginx는 클라이언트 요청을 받아 Gunicorn으로 전달해주는 프록시 역할을 수행하며, 정적 파일 서빙까지 맡을 수 있죠.

sudo apt update
sudo apt install nginx

기본 설정 예시

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /static/ {
        alias /home/ubuntu/yourproject/static/;
    }
}

 

위 설정은 yourdomain.com으로 들어온 요청을 127.0.0.1:8000으로 프록시 전달하고,

/static/ 경로의 파일은 Nginx가 직접 서빙합니다.

설정 후에는 다음 명령어로 Nginx를 재시작합니다:

sudo systemctl restart nginx

🔒 HTTPS 적용 개요

서비스를 인터넷에 공개한다면 HTTPS는 선택이 아닌 필수입니다.

무료 SSL 인증서인 Let’s Encrypt를 사용하면 쉽게 HTTPS를 적용할 수 있어요.

certbot을 사용하여 도메인을 인증하고 Nginx 설정을 자동으로 갱신할 수 있습니다.

다음과 같이 실행해보세요:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx

 

이 과정을 통해 인증서가 자동 설치되며, Nginx 설정도 자동으로 변경됩니다.

이제 HTTPS 기반으로 보안된 연결을 제공할 수 있습니다.

이제 로컬 개발용 서버에서 벗어나, 실제 운영 환경에서 Flask 앱을 동작시킬 준비가 끝났습니다.

다음 섹션에서는 Heroku와 같은 클라우드 플랫폼을 이용한 배포 방법을 알아볼게요!

 

 

4. 클라우드 서비스에 앱 배포하기 (Heroku 예시) ☁️

운영 환경에 배포하는 가장 쉬운 방법 중 하나는 클라우드 PaaS(Platform as a Service)를 이용하는 거예요.

오늘은 대표적인 서비스인 Heroku를 예로 들어, Flask 앱을 단계별로 배포하는 과정을 소개할게요.

Heroku 외에도 Render, Railway, PythonAnywhere, AWS EC2 등이 있지만, 초심자 입문용으로는 Heroku가 최고죠!

🧰 Heroku 배포 준비물

  • requirements.txt – 설치할 패키지 목록
  • Procfile – 실행 명령어 (예: web: gunicorn app:app)
  • runtime.txt – Python 버전 명시 (예: python-3.10.5)

🧑‍💻 Heroku CLI 설치 및 로그인

# CLI 설치 (공식 사이트 참고)
https://devcenter.heroku.com/articles/heroku-cli

# 로그인
heroku login

 

로그인을 하면 브라우저가 열리면서 인증이 완료되고, CLI에서 Heroku를 조작할 수 있게 됩니다.

📦 Flask 앱 배포하기

  1. Git 저장소 초기화
git init
git add .
git commit -m "Initial commit"
  1. Heroku 앱 생성
heroku create your-app-name
  1. Git으로 배포
git push heroku master
  1. 웹 브라우저로 접속 확인
heroku open

🔐 환경 변수 설정

SECRET_KEYDATABASE_URL 등 민감한 정보는 Heroku Dashboard나 CLI를 통해 설정할 수 있어요.

heroku config:set SECRET_KEY=mysecretvalue

 

여기까지 설정을 완료하면, Flask 앱이 Heroku를 통해 전 세계 어디서나 접근 가능한 서비스로 탈바꿈하게 됩니다!

다음 섹션에서는 운영 시 고려해야 할 보안과 성능 팁들을 소개할게요.

 

 

5. 실서비스를 위한 운영 환경 고려사항 🛡️

이제 애플리케이션이 세상에 나왔습니다. 하지만 진짜 중요한 건 지금부터예요.

서비스 운영은 단순히 앱을 “돌리는 것” 그 이상이에요.

사용자의 신뢰를 얻기 위한 보안, 성능, 확장성까지 두루 고려해야 하죠.

🔒 필수 보안 수칙

  • DEBUG=False 설정 유지 – 민감한 에러 정보 노출 방지
  • SECRET_KEY와 DB 암호는 코드에 절대 직접 작성하지 않기 – 환경 변수로 분리
  • API 요청에 JWT, OAuth, Token 기반 인증 적용하기 – 공개 API 방지

⚙️ 성능과 확장성 확보

처음에는 사용자가 적을 수 있지만, 언젠가 수천 명이 동시 접속하는 날이 올 수도 있죠.

그때를 대비해 확장성과 성능 튜닝 전략을 알아둬야 해요.

  • Gunicorn 워커 수 조절 – 서버 CPU 수와 트래픽에 따라 조정
  • DB 튜닝 및 연결 수 제한 – SQLAlchemy에서 pool_size 설정
  • Redis 캐시 도입 – 반복 쿼리 응답 속도 개선
  • CDN 적용 – 정적 파일 전송 속도 개선

🔍 다른 프레임워크와의 비교

Flask를 통해 웹 개발의 흐름을 이해했지만, 세상에는 다른 선택지도 많아요.

아래는 Flask와 자주 비교되는 프레임워크입니다:

프레임워크 특징 추천 용도
Flask 마이크로, 자유로운 구조 작은 서비스, 빠른 프로토타입
Django 풀스택, Admin 포함 중대형 프로젝트, 팀 협업
FastAPI 비동기 지원, 자동 문서화 고성능 API 서버

중요한 건, 어떤 프레임워크든 웹 서비스의 기본 개념은 비슷하다는 점이에요.

이번 과정을 통해 익힌 개념들은 다른 도구에서도 그대로 활용 가능하다는 점, 꼭 기억해주세요!

 

 

6. 전체 과정 정리 및 Q&A 🧭

어느덧 여덟 번째 날, 이번 Flask 웹 개발 여정의 마지막 단계에 도달했습니다.

그동안 배우고 실습했던 모든 내용을 정리하며, 마무리의 의미를 되새겨볼 시간이에요.

그리고 이제는 여러분이 직접 웹 개발자로서 첫 발을 내디딜 차례입니다 😊

🧩 8일간의 핵심 기술 요약

  • Flask 라우팅, 요청/응답 처리 및 템플릿 렌더링
  • Blueprint로 구조화된 웹 애플리케이션 설계
  • SQLAlchemy ORM을 활용한 데이터베이스 연동 및 CRUD
  • REST API 설계 및 JSON 형태의 응답 처리
  • Flask-WTF, Flask-Login 등 확장을 통한 기능 고도화
  • Gunicorn, Nginx, Heroku 등을 통한 배포 실습과 운영 고려사항

🙋 질의응답 & 개별 피드백

질문 있으신가요? 오늘은 자유롭게 질의응답을 진행합니다.

아직도 헷갈리는 부분이나 추가 설명이 필요한 개념이 있다면 지금 질문해 주세요.

시간이 허락된다면 간단한 데모나 그림으로도 다시 설명해드릴 수 있어요.

또한 각자 만든 프로젝트나 진행 중인 개인 작업에 대해 피드백을 받고 싶다면 언제든 공유해 주세요.

Flask를 사용한 실무적인 팁이나 코드 리뷰도 가능합니다 💬

🔮 차후 학습 방향 제안

Flask를 마스터했다고 해서 끝은 아니에요. 오히려 시작점일 수도 있어요.

아래는 다음 단계로 나아가기 위한 추천 학습 주제입니다:

  • Flask-RESTX를 활용한 API 문서 자동화 및 Swagger UI
  • JavaScript 프론트엔드 프레임워크(React, Vue 등)와의 연동 프로젝트
  • Django를 통한 풀스택 웹 앱 구축 실습

무엇보다 중요한 건, 작은 프로젝트라도 직접 만들어보는 것이에요.

CRUD 게시판, 블로그 API, 포트폴리오 웹사이트 등 본인만의 결과물을 만드는 과정이 진짜 실력이 되는 길입니다.

 

 

📝  Flask 개발 여정의 끝, 그리고 새로운 시작

여기까지 따라오신 여러분, 진심으로 고생 많으셨습니다 🙌

처음 Flask의 간단한 라우팅부터 시작해서 ORM, REST API, 배포까지, 쉽지 않은 여정을 함께 해냈다는 건 정말 대단한 일이에요.

이번 8일 과정은 단순한 코드 학습이 아닌, 웹 서비스라는 전체 흐름을 한 번 체험해보는 기회였습니다.

 

앞으로 여러분은 더 복잡한 웹 서비스도 구현할 수 있을 거예요.

그리고 이 경험은 분명히 현업 개발자나 사이드 프로젝트를 꿈꾸는 여러분에게 튼튼한 기반이 될 겁니다.

이제는 혼자서도 서버를 만들고, 클라이언트와 소통하고, 진짜로 세상에 서비스 하나를 내놓을 수 있으니까요.

그리고... 끝은 곧 시작입니다. 여기서 멈추지 말고 계속해서 나아가세요.

더 깊이 있는 백엔드 기술, 더 나은 UX를 위한 프론트엔드 기술, 클라우드 인프라까지 여러분의 세계는 점점 넓어질 거예요. 감사합니다! 💙

반응형
반응형

Flask 확장으로 게시판 프로젝트 고도화하기

단순한 게시판 웹앱에서 한 단계 더!
Flask 확장 기능으로 편리성과 보안성을 동시에 잡아보세요.

 

 

안녕하세요, Flask 웹 개발을 함께 배워가고 있는 여러분!

오늘은 우리가 지금까지 만들어온 게시판 프로젝트를 더 깔끔하고, 더 안전하게, 더 고급스럽게 만드는 방법을 소개하려 해요.

특히 Flask-WTFFlask-Login 같은 인기 확장 기능을 다루면서, 개발 효율성과 유지 보수성까지 한층 업그레이드할 수 있도록 실습 중심으로 진행할 거예요.

이제 여러분의 게시판이 진짜 '서비스다운' 모습으로 거듭날 준비를 해볼까요? 😎

 

1. Flask-WTF로 폼 처리 개선하기 ✍️

지금까지 우리는 게시글 작성 폼을 HTML로 직접 만들어 사용해왔죠.

그런데 이 방식은 반복 코드가 많고, 검증 처리도 직접 구현해야 해서 번거롭습니다.

그래서 Flask-WTF라는 확장 모듈을 사용하면 폼 관련 작업을 훨씬 더 간단하고 안전하게 처리할 수 있어요.

📌 Flask-WTF란?

Flask-WTF는 WTForms라는 폼 유효성 검사 라이브러리를 Flask에서 쉽게 사용할 수 있도록 도와주는 확장입니다.

입력 필드에 대한 클래스 기반 정의, 자동 CSRF 보호, 내장된 다양한 Validator를 지원하여 폼 처리를 안전하고 일관되게 만들어 줘요.

✔ 설치 및 설정

pip install flask-wtf

설치 후에는 Flask 설정에 SECRET_KEY를 추가해서 CSRF 보호 기능을 켜줘야 합니다.

app.config['SECRET_KEY'] = 'mysecretkey'

🛠 PostForm 클래스 만들기

게시글 작성에 필요한 폼 클래스를 다음과 같이 정의할 수 있어요.

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField
from wtforms.validators import DataRequired, Length

class PostForm(FlaskForm):
    title = StringField('제목', validators=[DataRequired(), Length(max=100)])
    content = TextAreaField('내용', validators=[DataRequired()])

✅ Flask-WTF의 장점은?

  • HTML 입력 폼을 Python 코드로 간결하게 표현 가능
  • Validator로 유효성 검사를 쉽게 추가할 수 있음
  • CSRF 보호가 자동으로 적용되어 보안성 향상

폼 유효성 검사를 별도로 구현하지 않아도 되고, 입력값이 잘못됐을 때의 메시지도 자동 출력되기 때문에 사용자 경험도 좋아져요.

 

사실 이걸 한 번 적용해보고 나면, 앞으로는 순수 HTML 폼으로 되돌아가기 싫어질지도 몰라요. 🤭

 

 

2. 템플릿에서의 폼 렌더링과 에러 메시지 출력 💡

Flask-WTF를 도입했다면, 템플릿에서도 이제 폼 필드를 직접 출력하는 대신 WTForms의 필드 객체를 사용해 보다 동적으로 표현할 수 있어요.

게다가 오류 메시지를 자동으로 표시할 수 있어서 사용자 친화적인 폼을 아주 쉽게 구현할 수 있답니다.

🧩 기본 템플릿 구조

HTML 코드가 이렇게 바뀝니다.

form.csrf_token은 꼭 포함해야 해요. CSRF 보호를 위한 필수 요소니까요.

 


<form method="POST">{{ form.csrf_token }}
<div>
	{{ form.title.label }}
   	{{ form.title(size=40) }}
    {% for error in form.title.errors %}
       	<span style="color: red;">{{ error }}</span> 
    {% endfor %}
</div>
<div>
	{{ form.content.label }} 
    {{ form.content(rows=10, cols=50) }} 
    {% for error in form.content.errors %} 
    	<span style="color: red;">{{ error }}</span> 
    {% endfor %}
</div>
<button type="submit">작성하기</button>
</form>

기존에 우리가 직접 HTML 인풋을 만들던 방식보다 훨씬 간결하고, 유지보수도 쉬워지죠.

그리고 폼 오류 발생 시 메시지를 바로 보여줄 수 있어서 사용자 입장에서도 훨씬 직관적인 사용 경험을 제공할 수 있어요.

🔍 뷰 함수도 간단하게!

이제 뷰 함수에서는 request.form을 직접 다루는 대신 폼 인스턴스를 생성하고 검증만 하면 됩니다.

@app.route('/board/new', methods=['GET', 'POST'])
def create_post():
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title=form.title.data, content=form.content.data)
        db.session.add(post)
        db.session.commit()
        return redirect(url_for('board.list'))
    return render_template('board_form.html', form=form)

이 코드는 Flask-WTF의 validate_on_submit() 메서드 덕분에 훨씬 간단하고 안전해졌어요.

그리고 form 인스턴스를 템플릿에 넘기기만 하면 끝!

🔧 에러 발생 시 확인 포인트

  • CSRF 토큰을 템플릿에 넣었는지 확인
  • FlaskForm을 상속받았는지 확인
  • SECRET_KEY 설정이 누락되지 않았는지 확인

폼 유효성 검사를 제대로 처리하면 실수로 잘못된 데이터를 넣는 경우도 줄어들고, 사용자도 시스템을 더 신뢰하게 됩니다.

개발자 입장에서도 디버깅 시간이 줄어든다는 거, 진짜 큰 장점이에요! 🙌

 

 

3. Flask-Login으로 사용자 인증 개념 익히기 🔐

이제 게시판 프로젝트가 어느 정도 완성 단계에 이르렀다면, 사용자 인증을 고려할 시점이에요.

왜냐하면, 글을 작성하거나 수정, 삭제할 수 있는 권한을 아무에게나 줄 수는 없으니까요.

바로 여기서 Flask-Login이라는 강력한 확장 기능이 등장합니다.

🔑 Flask-Login 소개

Flask-Login은 로그인 상태를 세션에 저장하고, 인증이 필요한 페이지에 접근할 때 로그인 여부를 자동으로 체크해주는 편리한 인증 도구예요.

별도의 UI는 없고, 세션 관리, 현재 사용자 확인, 데코레이터를 이용한 보호 기능에 충실합니다.

🚀 핵심 기능

  • login_user()로 로그인 처리
  • logout_user()로 로그아웃 처리
  • login_required 데코레이터로 특정 라우트 보호

그리고 현재 로그인한 사용자를 확인할 때는 current_user 객체를 사용해요.

User 모델에는 몇 가지 필수 메서드와 속성을 추가해줘야 정상 동작합니다.

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(200), nullable=False)

Flask-Login은 UserMixin 클래스를 상속하면 필요한 메서드들을 자동으로 구현해주기 때문에 아주 편리해요.

물론, 비밀번호는 반드시 해시처리를 해야겠죠?

📝 적용 시나리오

  1. User 모델을 설계하고, 로그인/회원가입 폼을 만들기
  2. 로그인 상태를 세션으로 유지하고, 필요 시 인증 검사하기
  3. 게시글 작성, 수정, 삭제 뷰에 @login_required 추가

이번 강의에서는 시간이 부족하니 직접 구현은 생략하고, 인증 시스템의 구조와 적용 포인트만 개념적으로 익히면 충분합니다.

더 자세히 배우고 싶다면 Flask-Login 공식 문서Flask 튜토리얼의 인증 챕터를 꼭 참고해보세요.

 

중요한 건, 인증은 '기능 추가' 그 이상의 가치가 있다는 거예요.

서비스의 신뢰도와 확장성을 동시에 끌어올려주는 필수 요소니까요. 😊

 

 

4. 게시판 기능 마무리 및 UI 개선 🎨

게시판 기능이 얼추 완성되었죠?

이제는 사용자 경험(UX)을 고려한 마무리 작업을 해볼 차례예요.

그동안 구현했던 CRUD 기능들이 자연스럽게 연결되도록 테스트하고, UI 개선까지 살짝 손대보는 거죠.

💅 Bootstrap으로 UI 다듬기 (선택사항)

Bootstrap은 CSS 프레임워크 중에서도 가장 보편적으로 사용되는 도구예요.

특히 테이블, 폼, 버튼 등 기본 UI 요소들을 빠르게 스타일링할 수 있어서 초보자도 쉽게 적용할 수 있습니다.


  • <table class="table table-hover"> 형태로 목록 페이지 개선
  • form-control, btn btn-primary 등으로 폼 및 버튼 꾸미기

🔁 기능 흐름 점검

다음 시나리오를 한 번 따라가 보세요.

실제 사용자가 프로젝트를 어떻게 사용할지를 기준으로 흐름을 확인하는 거예요.

  1. 새 글 작성 → 제출
  2. 목록 페이지에서 작성한 글 확인
  3. 상세 페이지에서 글 내용 조회
  4. 글 수정 → 다시 확인
  5. 글 삭제 → 목록에서 사라졌는지 확인

🧼 자잘한 버그 및 UX 개선

  • 글이 없을 때 ‘게시글이 없습니다’ 문구 표시
  • 너무 긴 제목은 text-overflow: ellipsis로 잘라내기
  • 삭제 확인 버튼에 JavaScript confirm() 사용

이런 자잘한 디테일들이 모이면, 사용자들은 “오, 이 서비스 되게 잘 만들었네?”라는 느낌을 받게 되죠.

기술력은 기본, 디테일이 완성도를 결정한다는 말, 잊지 마세요. 😉

 

 

5. 전체 기능 점검과 코드 리팩토링 ⚙️

이제 우리가 만든 게시판 프로젝트의 전체 기능을 점검하고, 코드 구조를 한번 깔끔하게 정리해볼 차례입니다.

지금까지는 각 기능을 하나씩 배우고 붙이는 데 집중했다면,

이 단계에서는 전반적인 동작 흐름코드 일관성에 신경 써야 해요.

🧪 시나리오 테스트

실제 사용자가 게시판을 사용할 상황을 가정해서 테스트해보세요.

아래와 같은 항목을 체크리스트로 삼는 것도 좋습니다.

  • 새 글 작성 후 목록 반영 확인
  • 글 상세 페이지에서 제목과 내용 확인
  • 글 수정 및 삭제 후 정상 반영 여부 확인

🧹 코드 정리 (리팩토링)

플라스크 앱의 구조가 깔끔하게 정리되어야 유지보수가 쉬워집니다.

Blueprint로 분리한 파일들을 다음처럼 정리해보세요.

/app
├── __init__.py         # 앱 생성 및 구성
├── models.py           # DB 모델
├── routes.py           # 라우터
├── forms.py            # Flask-WTF 폼 클래스
├── templates/          # HTML 템플릿들
└── static/             # 정적 파일(CSS, JS 등)

이 구조는 규모가 커질 때 유지보수를 훨씬 수월하게 해줘요.

추후 Flask 애플리케이션 팩토리 패턴을 적용하면 더 모듈화된 구조도 가능합니다.

🌐 API 엔드포인트 점검

RESTful API로 구현된 엔드포인트들이 잘 작동하는지도 꼭 확인해봅시다.

프론트엔드뿐 아니라 외부 앱에서도 이 API를 쓸 수 있게 설계한 것이니까요.

  • GET /api/posts → 게시글 목록 반환
  • POST /api/posts → 새 글 등록

만약 CORS 문제로 외부 클라이언트에서 API 호출이 안 된다면,

flask-cors 확장을 설치하고 아래처럼 설정해 주세요.

from flask_cors import CORS
CORS(app)

이제 여러분의 게시판 프로젝트는 기능, 구조, 디자인, API까지 모두 균형 잡힌 상태예요! 🎯

정말 멋지게 완성됐습니다.

 

 

6. 확장 기능과 보안 적용에 대한 심화 토론 🤔

이번 단계는 약간 자유로운 분위기에서 마무리해보는 시간이에요.

지금까지 우리가 만든 게시판을 바탕으로 앞으로 확장할 수 있는 방향이나 고급 기능에 대한 아이디어를 나눠볼게요.

💬 이런 기능도 추가해보면 어떨까요?

  • 댓글 기능: 각 게시글에 댓글을 달 수 있도록 DB와 템플릿을 확장
  • 검색 기능: 제목 또는 본문에서 키워드를 기반으로 검색
  • 페이지네이션: 게시글이 많아졌을 때 페이지 나누기
  • 파일 업로드: 이미지를 첨부하거나 업로드된 파일 관리 기능

🛡 Flask 보안 확장 소개

사용자 인증을 조금 더 제대로 구현하고 싶다면 Flask-Security 또는 Flask-User 같은 확장도 고려할 수 있어요.

회원 가입, 로그인, 비밀번호 리셋, 이메일 인증 등 흔히 필요한 기능들이 모두 포함되어 있습니다.

🙋‍♀️ 수업 마무리 및 토론 주제

이제 이 프로젝트를 마무리하면서,

다음과 같은 질문을 던져볼 수 있어요:

  • Flask 프로젝트에서 가장 어려웠던 점은 무엇인가요?
  • Flask를 활용한 다른 웹 서비스 아이디어가 있을까요?
  • 확장 기능 중 가장 유용했던 것은 무엇이었나요?

 

이런 질문들을 주제로 서로의 경험을 나누고, 앞으로 어떤 방향으로 발전시킬 수 있을지도 생각해보면 정말 좋은 학습 마무리가 될 거예요.

여기까지 따라오신 여러분, 진심으로 멋졌습니다! 👏👏👏

반응형
반응형

파이썬 REST API 개발 및 활용 완벽 가이드

웹 브라우저 말고도,
모바일 앱이나 외부 시스템에서도 우리 Flask 게시판을 사용할 수 있다면 얼마나 좋을까요?
그 해답은 바로 REST API입니다!

 

 

안녕하세요! 😊

Flask 기반 웹 게시판 프로젝트 여섯째 날입니다.

오늘은 웹 프론트엔드만이 아닌, 외부 클라이언트에서도 사용할 수 있는 REST API 개발을 함께 배워보려고 해요.

예를 들어,

우리가 만든 게시판 기능을 모바일 앱이나 다른 백엔드 서비스에서도 활용하고 싶다면 어떻게 해야 할까요?

그 해답은 바로 Flask를 이용한 RESTful API 구축이에요.

이번 시간에는 REST의 기본 개념부터 시작해 Flask에서 JSON 데이터를 주고받는 API 만들기,

그리고 게시판 CRUD API를 직접 구현해보는 실습까지 차근차근 진행할게요.

어렵지 않아요. 이미 우리가 만든 게시판 로직을 바탕으로 하니까 훨씬 수월하게 느껴질 거예요.

그럼 시작해볼까요?

 

1. RESTful API란 무엇인가요? 🤔

요즘 웹 개발에서 REST API라는 말을 자주 들어보셨을 거예요.

그만큼 백엔드 서비스를 설계할 때 꼭 알아야 할 중요한 개념이죠.

하지만 처음 들었을 땐 뭔가 복잡하고 어려워 보일 수 있어요.

그래서 이번에는 RESTful API의 핵심 개념을 쉽고 간단하게 정리해드릴게요!

REST의 핵심 개념은?

  • Resource(자원): REST는 웹의 모든 것을 자원으로 봅니다. 예를 들어 게시글(post), 사용자(user) 같은 것이죠.
  • URI(Uniform Resource Identifier): 자원을 식별하는 주소입니다. 예: /api/posts
  • HTTP 메서드: 자원에 대한 동작을 정의합니다. 예: GET(조회), POST(생성), PUT/PATCH(수정), DELETE(삭제)

REST API는 왜 이렇게 인기가 많을까요?

REST API가 널리 쓰이는 이유는 간단해요.

간결하고 직관적인 구조 덕분이죠.

복잡한 규약 없이 HTTP의 기본 동작만으로 다양한 클라이언트와 쉽게 통신할 수 있으니까요.

또한, REST API는 다음과 같은 특징들을 가지고 있어요:

  1. 🎯 Stateless(무상태성): 각 요청은 독립적이며, 서버는 이전 요청의 상태를 저장하지 않아요.
  2. 🔄 Cacheable: 응답은 캐싱이 가능하므로 성능을 높일 수 있어요.
  3. 🧩 Uniform Interface: 표준화된 인터페이스 덕분에 다양한 플랫폼에서 사용할 수 있어요.

RESTful API 설계시 고려사항은? 🧐

RESTful하게 API를 설계하기 위해 지켜야 할 몇 가지 규칙이 있어요.

특히 초보자들이 자주 놓치는 부분들을 짚어볼게요.

  • URL은 복수형 명사로: 예를 들어 게시글이면 /api/posts와 같이 작성하는 것이 좋습니다.
  • HTTP 상태 코드 정확히 사용: 예를 들어, 성공은 200, 생성은 201, 오류는 400 또는 404 등으로 클라이언트에게 명확히 알려줘야 해요.
  • JSON 응답은 일관성 있게: 예를 들어 항상 {"id": 1, "title": "...", "content": "..."}와 같은 형식 유지

REST API는 단순히 엔드포인트만 만든다고 되는 게 아니라,

일관성과 예측 가능성을 고려해 설계해야 클라이언트가 편하게 쓸 수 있어요.

이게 바로 진짜 백엔드 개발자의 센스겠죠? 😉

 

 

2. Flask에서 REST API 만들기 🔧

자, 이제 본격적으로 Flask로 REST API를 만들어볼까요?

사실 Flask에서는 별도의 라이브러리 없이도 API를 쉽게 만들 수 있어요.

jsonify() 함수와 request.get_json() 메서드만 잘 활용하면 되거든요.

기본 API 예제

가장 기본적인 API는 이렇습니다.

“/api/hello”에 GET 요청을 보내면 JSON 형식으로 인사 메시지를 돌려주는 간단한 코드예요.

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/api/hello", methods=["GET"])
def hello_api():
    return jsonify(message="안녕하세요! 여기는 Flask REST API입니다.")

 

이렇게 간단한 코드만으로도 API가 완성돼요.

응답은 자동으로 Content-Type: application/json을 포함하므로 별도 설정도 필요 없답니다.

POST 요청 처리 - 클라이언트로부터 JSON 받기

POST 요청은 클라이언트가 서버에 데이터를 보낼 때 사용합니다.

아래처럼 request.get_json()으로 JSON 데이터를 받아 처리할 수 있어요.

from flask import request

@app.route("/api/echo", methods=["POST"])
def echo_api():
    data = request.get_json()
    name = data.get("name", "익명")
    return jsonify(message=f"{name}님, 안녕하세요!")

 

  • request.get_json(): JSON 데이터를 받아서 Python dict로 변환해줘요.
  • jsonify(): dict를 JSON으로 변환해서 응답해줍니다.

 

💡 확장 라이브러리는 쓸까 말까?

Flask는 기본 기능만으로도 API 작성이 가능하지만, 더 복잡한 구조나 반복을 줄이려면 Flask-RESTful 같은 확장을 쓸 수도 있어요.

하지만 지금은 기초 개념을 제대로 익히는 것이 중요하므로, 확장 없이 직접 구현해볼게요.

 

즉, JSON 처리에 필요한 최소한의 도구만으로도 강력한 API를 만들 수 있다는 점!

Flask의 가장 큰 매력이자 초보자에게 추천하는 이유이기도 해요.

 

 

3. JSON 응답 및 요청 처리 방식 📦

REST API에서 데이터를 주고받을 때 가장 흔히 사용하는 형식이 바로 JSON입니다.

Flask에서는 JSON 처리가 매우 간단하면서도 직관적이에요.

이번엔 JSON 응답을 어떻게 만들고, 클라이언트의 JSON 요청 데이터를 어떻게 처리하는지를 정리해볼게요.

1️⃣ JSON 응답 만들기

HTML을 반환하던 일반 웹 뷰와 달리,

API에서는 Python의 딕셔너리 데이터를 JSON 문자열로 변환해서 반환합니다.

Flask에서는 jsonify()를 사용하면 딕셔너리를 자동으로 JSON 형식으로 바꿔주고,

Content-Typeapplication/json으로 지정해줍니다.

from flask import jsonify

@app.route("/api/greet")
def greet():
    return jsonify({
        "status": "success",
        "message": "API가 정상 작동 중입니다."
    })
  • jsonify()는 자동으로 application/json MIME 타입과 200 OK 상태 코드를 설정합니다.

2️⃣ 클라이언트 JSON 요청 처리하기

클라이언트가 보내는 JSON 데이터를 받으려면 request.get_json() 메서드를 사용합니다.

이때 Content-Typeapplication/json으로 설정되어 있어야 해요.

from flask import request

@app.route("/api/submit", methods=["POST"])
def submit_data():
    data = request.get_json()
    name = data.get("name")
    age = data.get("age")
    if not name or not age:
        return jsonify(error="Missing fields"), 400
    return jsonify(message=f"{name}님({age}세), 데이터가 잘 수신되었습니다.")

 

get_json()을 통해 클라이언트의 JSON 요청을 dict로 파싱하고, 데이터 유효성 검사까지 함께 처리할 수 있어요.

3️⃣ 잘못된 요청 처리하기

API는 언제나 올바르지 않은 요청을 고려해야 합니다.

예를 들어

JSON 필드가 빠졌거나 잘못된 값이 들어오면, 클라이언트에게 명확한 메시지와 함께 400 Bad Request를 반환해야 하죠.

 

이런 식으로 API의 응답 일관성을 유지하는 것이 사용자 경험을 높이는 핵심 포인트입니다!

🔓 참고: CORS(Cross-Origin Resource Sharing)

프론트엔드가 다른 도메인(예: React 앱)에서 API를 호출할 경우 CORS 문제가 발생할 수 있어요.

이럴 땐 Flask-CORS 확장을 사용하면 아주 쉽게 해결할 수 있습니다.

# 설치
pip install flask-cors

# 사용 예
from flask_cors import CORS
app = Flask(__name__)
CORS(app)

 

 

4. 게시판 CRUD API 설계 및 구현 📝

이제 본격적으로 게시판 기능을 REST API 형태로 구현해볼 차례입니다.

이전까지는 HTML 페이지를 렌더링해 사용자에게 보여주는 방식이었다면,

이번에는 데이터를 JSON으로 주고받는 API 방식으로 확장합니다.

같은 기능이지만 'API형 백엔드'로 구현한다는 점에서 의미가 커요!

📌 API 엔드포인트 설계

Method URL 설명
POST /api/posts 게시글 생성
GET /api/posts 게시글 목록 조회
GET /api/posts/<id> 게시글 상세 조회
PUT /api/posts/<id> 게시글 수정
DELETE /api/posts/<id> 게시글 삭제

🔨 주요 기능별 구현 예제

1. 게시글 생성 - POST /api/posts

@app.route("/api/posts", methods=["POST"])
def create_post():
    data = request.get_json()
    title = data.get("title")
    content = data.get("content")
    if not title or not content:
        return jsonify(error="필수 입력값 누락"), 400
    new_post = Post(title=title, content=content)
    db.session.add(new_post)
    db.session.commit()
    return jsonify(post_id=new_post.id), 201

2. 게시글 목록 - GET /api/posts

@app.route("/api/posts", methods=["GET"])
def get_posts():
    posts = Post.query.order_by(Post.id.desc()).all()
    result = [post.to_dict() for post in posts]
    return jsonify(posts=result)

 

to_dict()는 모델 클래스에 미리 정의해두어야 합니다.

예: return {"id": self.id, "title": self.title, ...}

3. 게시글 상세조회 - GET /api/posts/<id>

@app.route("/api/posts/<int:post_id>", methods=["GET"])
def get_post(post_id):
    post = Post.query.get(post_id)
    if not post:
        return jsonify(error="존재하지 않는 게시글입니다."), 404
    return jsonify(post=post.to_dict())

4. 게시글 수정 - PUT /api/posts/<id>

@app.route("/api/posts/<int:post_id>", methods=["PUT"])
def update_post(post_id):
    post = Post.query.get(post_id)
    if not post:
        return jsonify(error="해당 게시글 없음"), 404
    data = request.get_json()
    post.title = data.get("title", post.title)
    post.content = data.get("content", post.content)
    db.session.commit()
    return jsonify(message="수정 완료", post=post.to_dict())

5. 게시글 삭제 - DELETE /api/posts/<id>

@app.route("/api/posts/<int:post_id>", methods=["DELETE"])
def delete_post(post_id):
    post = Post.query.get(post_id)
    if not post:
        return jsonify(error="해당 게시글 없음"), 404
    db.session.delete(post)
    db.session.commit()
    return jsonify(message="삭제 완료")

 

 

5. 실습: API 테스트와 사용 예시 🧪

API를 만들었으면 이제는 잘 작동하는지 테스트해봐야겠죠?

우리가 만든 REST API는 브라우저에서는 GET 요청 정도만 직접 확인할 수 있어요.

하지만 POST, PUT, DELETE 같은 요청은 curl이나 Postman 같은 도구로 테스트하는 게 일반적입니다.

🖥 curl 명령어로 API 테스트

  • GET 요청 - 게시글 목록 확인:
    curl -X GET http://localhost:5000/api/posts
  •  
  • POST 요청 - 새 게시글 생성:
    curl -X POST -H "Content-Type: application/json" -d '{"title":"첫 글", "content":"안녕하세요"}' http://localhost:5000/api/posts

🧪 Postman 사용 팁

Postman은 GUI 기반의 API 테스트 도구예요.

HTTP 메서드 선택, 요청 바디 JSON 입력, 응답 확인을 아주 편리하게 할 수 있습니다.

  1. Postman 실행 후 새 요청(Request) 생성
  2. Method: POST, URL: http://localhost:5000/api/posts
  3. Body 탭 → raw → JSON 선택 후 아래 내용 입력: 
  4.  
  5. { "title": "Postman 테스트", "content": "이건 Postman에서 보낸 글이에요." }
  6. Send 버튼 클릭 → 응답 확인!

✅ 예외 케이스 테스트하기

  • 존재하지 않는 게시글을 조회:
    GET /api/posts/99999 → 404 응답 확인
  •  
  • 필수 필드 누락 POST 요청 보내기 → 400 Bad Request 응답 확인

테스트를 반복하면서 우리 API가 얼마나 잘 짜여져 있는지 실감하게 되실 거예요.

오류 메시지도 일관성 있게 만들었다면 더더욱 멋지죠! 😉

 

 

6. 다양한 클라이언트와의 연동 시나리오 📱

지금까지 우리는 Flask 기반 게시판 API를 설계하고 구현하고, 테스트까지 마쳤어요.

이제는 다양한 클라이언트에서 이 API를 어떻게 활용할 수 있을지에 대해 살펴보는 시간입니다.

단순히 웹 브라우저뿐 아니라 모바일 앱, 프론트엔드 자바스크립트, 다른 백엔드 서비스 등과 연동되는 시나리오를 떠올려볼 수 있어요.

🧩 JavaScript에서 AJAX로 API 호출

가장 흔한 연동 방식은 웹 페이지에서 자바스크립트를 이용해 AJAX 방식으로 API를 호출하는 거예요.

예를 들어, HTML 페이지에서 아래처럼 Javascript의 fetch API를 사용할 수 있죠.

fetch("/api/posts")
  .then(response => response.json())
  .then(data => {
    console.log("게시글 목록:", data.posts);
  });

 

이렇게 하면 서버에서 JSON으로 반환한 게시글 목록을 자바스크립트에서 받아와 자유롭게 화면에 뿌릴 수 있어요.

📱 Flutter, React Native 같은 모바일 앱에서 연동

모바일 앱도 마찬가지예요.

백엔드에서 제공한 REST API를 HTTP 통신 모듈 (예: Flutter의 http 패키지, React Native의 axios)로 호출하면 앱에서도 동일하게 게시글을 CRUD할 수 있답니다.

  • Flutter에서는 http.post(), http.get()으로 JSON 데이터를 보내고 받아요.
  • React Native에서는 axios.post(), axios.get() 방식으로 쉽게 연동 가능해요.

🔗 다른 백엔드 시스템과의 연동

우리가 만든 Flask API는 마이크로서비스 구조에서도 활용할 수 있어요.

예를 들어,

사용자 인증은 Django가 담당하고, 게시판 기능은 Flask가 담당하는 식으로 서비스별 역할을 분리해서 통신하는 구조를 만들 수도 있어요.

🚀 실전 팁

  1. API 응답 형식은 항상 일관되게 유지하세요. (예: {"status": "success", "data": ...})
  2. 클라이언트가 다양한 환경에서 호출하는 만큼, CORS 설정은 반드시 고려해야 해요.
  3. API 문서화도 중요합니다! Swagger UI 또는 Postman Collection 등 활용해보세요.

 

이제 우리가 만든 Flask 백엔드는 웹, 앱, 서버 어디서든 활용 가능한 멋진 API 서비스가 되었어요.

여기까지 잘 따라오셨다면 이미 여러분은 초보 탈출 성공! 🎉

 

 

마무리 ✨

이번 여섯째 날 학습에서는 우리가 만든 게시판 웹 애플리케이션을 RESTful API로 확장하는 방법을 익혔습니다.

단순히 기능을 구현하는 것에서 한 걸음 더 나아가, 다양한 클라이언트와 연동 가능한 백엔드 서비스로 발전시키는 데 성공했어요.

REST API의 핵심 개념부터 Flask에서의 실제 구현, JSON 데이터 처리 방식, 그리고 Postman과 curl을 통한 실전 테스트까지 모든 과정을 실습하며 직접 체험해보셨을 텐데요, 어떠셨나요?

좀 더 현업 백엔드 개발자처럼 느껴지지 않으셨나요?

 

이제 여러분의 Flask 웹 서비스는 단지 브라우저에서만 동작하는 게 아니라, 앱, JS 프론트, 외부 시스템 등 어디에서든 자유롭게 사용할 수 있는 API 플랫폼이 된 거예요.

다음 시간에는 이 API를 어떻게 응용할 수 있을지, 또 성능 향상이나 인증 같은 고급 기능도 배워보면 좋겠죠?

 

혹시 아직 익숙하지 않은 부분이 있다면, 실습한 코드를 천천히 다시 보면서 정리해보세요.

반복이 최고의 선생님이니까요 😊

다음 단계에서 또 만나요!

반응형
반응형

파이썬과 Flask로 만드는 게시판 CRUD 완벽 가이드 💻🛠️

게시글을 웹에서 직접 작성하고 수정하고 삭제까지?
Flask와 SQLAlchemy만 있으면 가능합니다!
이 글 하나면 게시판 CRUD 구현은 완전 정복할 수 있어요.

 

 

안녕하세요, 여러분 😊

오늘은 Flask 웹 애플리케이션에서 가장 많이 사용되는 기능 중 하나인 CRUD(Create, Read, Update, Delete)를 직접 구현해볼 거예요.

단순히 이론 설명에서 그치는 게 아니라, 실제 웹 브라우저에서 게시글을 작성하고 목록을 보고, 수정하거나 삭제하는 완전한 게시판 시스템을 만들 거예요.

이 과정을 통해 Flask와 ORM(SQLAlchemy)의 활용 방법을 보다 실제적인 방식으로 배워보게 될 거예요.

기초부터 차근차근, 코드 한 줄 한 줄 이해하며 따라올 수 있게 구성했으니, 초보자도 걱정 말고 끝까지 함께 해봐요! 😉

 

1. 게시글 작성 기능 구현 (Create 기능) 📝

웹 게시판에서 가장 먼저 구현할 기능은 게시글 작성입니다.

사용자가 직접 제목과 내용을 입력하고 저장 버튼을 누르면, 해당 내용이 데이터베이스에 저장되고 목록이나 상세 페이지로 이동하게 만들 거예요.

우선 기본적인 흐름을 코드와 함께 살펴볼게요.

① 글 작성 폼 구현

`/board/new` 경로로 GET 요청 시 사용자에게 제목과 내용 입력란을 보여주는 HTML 폼을 렌더링합니다.

아래는 템플릿 예시입니다:

<form method="POST">
  <label>제목</label><br>
  <input type="text" name="title"><br>
  <label>내용</label><br>
  <textarea name="content"></textarea><br>
  <button type="submit">작성하기</button>
</form>

 

CSRF 방지와 보안은 이후 단계에서 `Flask-WTF` 같은 라이브러리를 통해 보완할 예정입니다.

지금은 기본적인 기능 흐름을 먼저 구현해보는 데 집중할게요!

② POST 요청 처리 및 DB 저장

사용자가 작성한 제목과 내용을 받아 `Post` 모델을 통해 DB에 저장합니다.

아래는 Flask 라우트 코드 예시예요:

@app.route('/board/new', methods=['GET', 'POST'])
def create_post():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        if not title or not content:
            flash('제목과 내용을 모두 입력해주세요.')
            return redirect(url_for('create_post'))

        post = Post(title=title, content=content)
        db.session.add(post)
        db.session.commit()

        flash('게시글이 작성되었습니다.')
        return redirect(url_for('post_detail', post_id=post.id))

    return render_template('create_post.html')

③ 피드백과 리다이렉트

작성 후에는 목록 페이지 또는 상세 페이지로 이동하며 flash를 통해 사용자에게 피드백 메시지를 전달할 수 있습니다.

템플릿에서 아래와 같이 메시지를 출력할 수 있어요:

{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

🧪 실습 포인트

  • 제목 또는 내용을 비워서 제출했을 때 적절히 에러 처리가 되는지 확인해보세요.
  • 글 작성 후 DB에 제대로 저장되었는지 SQLAlchemy로 직접 쿼리하거나 목록 페이지에서 확인해보세요.
  • 작성 후 이동된 페이지에서 flash 메시지가 출력되는지 확인해보세요.

여기까지 구현하면 웹을 통해 실제로 글을 작성하고, 그것이 DB에 저장되는 전체 흐름을 직접 체험해볼 수 있어요.

다음은 작성된 글들을 조회해보는 기능, 목록 페이지로 넘어가볼게요!

 

 

2. 게시글 목록 조회 페이지 구현 (Read - 목록) 📋

게시판의 중심이 되는 페이지는 바로 목록 페이지입니다.

작성된 게시글들을 사용자에게 보여주는 역할을 하며, 각 게시글의 제목을 클릭하면 상세 페이지로 이동할 수 있어야 해요.

오늘은 이 목록 페이지를 Flask와 SQLAlchemy로 어떻게 구성할 수 있는지 하나씩 살펴보겠습니다.

① 목록 라우트 구현

/board 경로에서 모든 게시글을 조회할 수 있도록 라우트를 구현합니다.

작성일자를 기준으로 내림차순 정렬해 가장 최신 글이 위로 오도록 할 거예요.

@app.route('/board')
def post_list():
    posts = Post.query.order_by(Post.created_at.desc()).all()
    return render_template('post_list.html', posts=posts)

② 템플릿에서 게시글 목록 출력

게시글 데이터를 HTML에서 반복문을 통해 출력합니다.

게시글 제목을 클릭하면 상세 페이지로 이동하도록 링크를 걸어주세요.

<h2>게시글 목록</h2>
<ul>
  {% for post in posts %}
    <li>
      <a href="{{ url_for('post_detail', post_id=post.id) }}">{{ post.title }}</a>
      ({{ post.created_at.strftime('%Y-%m-%d %H:%M') }})
    </li>
  {% else %}
    <li>작성된 게시글이 없습니다.</li>
  {% endfor %}
</ul>

 

작성일이 보이도록 strftime을 활용해 날짜 포맷을 지정하는 것도 사용자 편의에 도움이 돼요.

③ (선택) 페이지네이션 기능 소개

글이 많아졌을 경우 모든 글을 한 페이지에 보여주는 것은 비효율적이에요.

이럴 땐 페이지네이션을 도입할 수 있습니다.

Flask-SQLAlchemy에서는 아래와 같이 페이지당 몇 개의 게시글을 보여줄지 설정할 수 있어요:

@app.route('/board')
def post_list():
    page = request.args.get('page', 1, type=int)
    posts = Post.query.order_by(Post.created_at.desc()).paginate(page=page, per_page=10)
    return render_template('post_list.html', posts=posts.items, pagination=posts)

 

실습에서는 구현하지 않아도 되지만, 프로젝트가 커질 경우 꼭 필요한 기능이에요.

Flask-Paginate 확장도 한 번 찾아보세요!

🧪 실습 포인트

  • 게시글 여러 개를 작성한 뒤, 최신 순으로 잘 정렬되는지 확인해보세요.
  • 게시글 제목을 클릭했을 때 해당 글의 상세 페이지로 잘 이동되는지 확인해보세요.
  • 아무 글도 없을 때 ‘게시글이 없습니다’ 문구가 표시되는지도 테스트해보세요.

이제 목록 페이지도 완성되었네요!

다음은 하나의 게시글을 눌렀을 때 나오는 상세 보기와 수정 기능을 구현해볼 차례입니다. 💪

 

 

3. 게시글 상세 보기 및 수정 기능 구현 (Read/Update) ✏️

게시글 목록에서 사용자가 특정 게시글을 클릭했을 때, 그 게시글의 내용을 보여주는 상세 보기 페이지와, 이후 수정할 수 있는 기능까지 구현해볼 거예요.

이 단계에서는 동적 라우팅, 데이터 조회, 그리고 POST 기반 수정 처리를 중심으로 배워보겠습니다.

① 게시글 상세 보기 페이지 구현

사용자가 게시글 제목을 클릭하면 /board/<post_id> 경로로 이동하도록 구현합니다.

이때 post_id를 기준으로 해당 게시글을 데이터베이스에서 조회해 보여줍니다.

@app.route('/board/<int:post_id>')
def post_detail(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post_detail.html', post=post)

 

get_or_404()는 ID에 해당하는 데이터가 없을 경우 자동으로 404 에러를 반환해주기 때문에 안전한 처리 방식입니다.

② 수정 폼 페이지 구현

게시글 상세 페이지에서 ‘수정’ 버튼을 누르면 /board/<post_id>/edit 경로로 이동합니다.

이 경로에서는 기존 게시글 데이터를 폼의 초기값으로 미리 넣어 보여줍니다.

@app.route('/board/<int:post_id>/edit', methods=['GET', 'POST'])
def edit_post(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        post.title = request.form['title']
        post.content = request.form['content']

        if not post.title or not post.content:
            flash('빈 칸 없이 입력해주세요.')
            return redirect(url_for('edit_post', post_id=post.id))

        db.session.commit()
        flash('게시글이 수정되었습니다.')
        return redirect(url_for('post_detail', post_id=post.id))

    return render_template('edit_post.html', post=post)

 

POST 요청 시 사용자가 입력한 값으로 기존 객체의 속성을 업데이트하고, db.session.commit()으로 저장합니다.

③ 수정 폼 템플릿 예시

<form method="POST">
  <label>제목</label><br>
  <input type="text" name="title" value="{{ post.title }}"><br>
  <label>내용</label><br>
  <textarea name="content">{{ post.content }}</textarea><br>
  <button type="submit">수정하기</button>
</form>

🧪 실습 포인트

  • 존재하지 않는 게시글 ID로 상세 페이지나 수정 페이지에 접근했을 때 404 오류가 잘 출력되는지 확인해보세요.
  • 수정 후 변경 사항이 목록 페이지나 상세 페이지에 즉시 반영되는지 확인해보세요.
  • 빈 입력값에 대한 처리와 사용자 피드백 메시지가 적절히 작동하는지 체크해보세요.

게시글 수정까지 완료했으니, 이제 남은 마지막 핵심 기능은 삭제입니다!

다음 단계에서는 게시글 삭제 처리 및 후속 처리까지 깔끔하게 구현해볼게요.

 

 

4. 게시글 삭제 기능 구현 (Delete) 🗑️

이제 게시글을 삭제하는 기능을 구현해볼 차례예요.

사용자 인터페이스에서 삭제 버튼을 클릭하면 해당 게시글이 실제로 데이터베이스에서 제거되고, 목록 페이지로 되돌아가게 만들 거예요.

삭제 전 확인 메시지를 띄우는 방법도 함께 알아봅시다.

① 삭제 버튼 추가하기

게시글 상세 페이지나 수정 페이지에서 삭제 버튼을 추가해 사용자가 클릭할 수 있도록 합니다.

아래는 HTML 폼을 사용한 간단한 예시예요:

<form method="POST" action="{{ url_for('delete_post', post_id=post.id) }}" 
      onsubmit="return confirm('정말 삭제하시겠습니까?');">
  <button type="submit">삭제하기</button>
</form>

 

자바스크립트의 confirm() 함수로 간단한 삭제 확인창도 띄워줍니다.

이렇게 하면 실수로 삭제하는 것을 방지할 수 있어요.

② Flask 라우트에서 삭제 처리

삭제 처리는 POST 방식으로 요청을 받아 처리하는 것이 일반적입니다.

아래는 Flask에서 삭제를 처리하는 라우트 예시입니다.

@app.route('/board/<int:post_id>/delete', methods=['POST'])
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    db.session.delete(post)
    db.session.commit()
    flash('게시글이 삭제되었습니다.')
    return redirect(url_for('post_list'))

 

get_or_404()를 통해 유효하지 않은 접근은 방지하고, 삭제 후에는 목록 페이지로 리다이렉트하며 플래시 메시지로 피드백을 줍니다.

③ 삭제 후 상태 확인

삭제가 완료되면 목록 페이지에서 해당 글이 사라져야 하고,

삭제된 게시글의 상세 주소로 접근할 경우 404 오류가 발생해야 해요.

이를 통해 삭제가 확실히 이루어졌는지 확인할 수 있습니다.

🧪 실습 포인트

  • 게시글 삭제 시 confirm 창이 잘 뜨는지 확인해보세요.
  • 삭제 후 목록 페이지에서 해당 글이 사라지는지 확인해보세요.
  • 삭제된 게시글 URL에 다시 접근했을 때 404 페이지가 나오는지도 꼭 확인해보세요.

이제 CRUD의 마지막 조각까지 완성되었습니다!

다음 단계에서는 전체 흐름을 다시 정리하고, CRUD를 어떻게 확장할 수 있을지에 대한 인사이트도 함께 나눠볼게요. 😊

 

 

5. CRUD 전체 사이클 정리 및 테스트 💡

지금까지 우리는 Flask와 SQLAlchemy를 활용해 게시판 CRUD 기능을 하나하나 구현해봤어요.

작성(Create), 조회(Read), 수정(Update), 삭제(Delete)까지 모든 사이클을 직접 경험하면서 웹 애플리케이션의 기본기를 탄탄하게 익혔습니다.

이 섹션에서는 전체 흐름을 다시 정리해보고, 실습 시 꼭 확인해야 할 테스트 항목들도 체크해볼게요.

① CRUD 사이클 요약 정리

기능 라우팅 설명
작성 (Create) /board/new 폼을 통해 게시글 생성, DB에 저장
조회 (Read - 목록) /board 모든 게시글을 리스트로 조회
조회 (Read - 상세) /board/<id> 단일 게시글을 ID로 조회
수정 (Update) /board/<id>/edit 기존 글을 수정하여 업데이트
삭제 (Delete) /board/<id>/delete 해당 게시글을 DB에서 삭제

② 전체 기능 테스트 체크리스트 ✅

  • 글 작성 시 제목과 내용이 비어 있으면 경고 메시지가 표시되는가?
  • 목록에서 최신 글이 가장 위에 오는가?
  • 상세 페이지에서 제목, 내용, 작성 시간이 제대로 보이는가?
  • 수정 시 기존 내용이 폼에 미리 채워지고, 수정 후 정상 반영되는가?
  • 삭제 후 목록에서 글이 사라지고, 삭제된 글 URL 접근 시 404가 나오는가?

③ 다음 단계 미리 보기 👀

CRUD 기능은 웹 애플리케이션의 핵심 기초라고 할 수 있어요.

이후에는 이 기능들을 REST API 형태로 제공하거나, JavaScript를 활용해 비동기로 처리하는 등 다양한 확장으로 이어질 수 있어요. 예를 들어:

  • 게시판을 API 형태로 만들어 React, Vue 같은 프론트엔드 프레임워크와 연동하기
  • JWT 로그인 기능과 결합해 작성자 인증/권한 부여 기능 추가하기
  • 댓글 기능, 파일 첨부 기능 등 게시판 고도화

이번 CRUD 구현이 단순한 끝이 아니라, 앞으로 나아갈 가능성의 출발점이라는 걸 꼭 기억해 주세요!

 

 

6. 마무리 및 다음 단계 안내 🚀

여기까지 따라오신 여러분 정말 수고 많으셨습니다! 👏

Flask와 SQLAlchemy를 사용한 게시판 CRUD 기능 구현을 하나하나 직접 해보며, 웹 애플리케이션 개발의 핵심 흐름을 완전히 익히셨을 거예요.

처음에는 생소했던 라우팅, 폼 데이터 처리, 데이터베이스 모델, 커밋과 쿼리 등 다양한 개념이 이제는 자연스럽게 느껴지실 겁니다.

이 CRUD 구현 경험은 이후의 모든 웹 개발 여정에 있어서 든든한 뼈대가 되어줄 거예요.

 

이번 실습을 통해 익힌 기술은 단순한 기능 구현을 넘어서, 실제 서비스를 만들고 유지보수하는 데에도 큰 도움이 됩니다.

 

그리고 여기서 끝이 아니에요!

다음 단계에서는 이 CRUD 기능들을 RESTful API로 전환하고, 프론트엔드와 연결하거나, 인증 기능을 넣는 방법도 배울 수 있어요.

웹 개발자로서 한 단계 더 도약할 수 있는 기반이 다져진 거죠. 😎

 

꼭 한 번 CRUD 기능을 자신만의 프로젝트에 적용해보세요.

예를 들어,

  • 나만의 블로그 만들기
  • 간단한 TODO 앱이나 일기장 만들기
  • Flask와 React를 연동한 게시판 프로젝트 도전하기

앞으로도 꾸준히 연습하고 발전해 나가면, 어떤 웹 서비스도 직접 만들 수 있게 될 거예요.

우리는 이제 시작입니다! 😄

반응형

+ Recent posts