Python/Python 웹프로그래밍

[FastAPI-⑦] 파이썬 FastAPI 실전 프로젝트 (1) – 블로그 API/Todo API 설계 및 구현

코딩 코디네이터 2025. 4. 16. 19:00
반응형

파이썬 FastAPI 실전 프로젝트 (1) – 블로그 API/Todo API 설계 및 구현 

단순한 예제만으로는 실력이 늘지 않아요.
진짜로 내가 만든 FastAPI 서비스,
지금부터 직접 설계하고 구현해보는 시간입니다!

 

 

안녕하세요 여러분 😊

드디어 지금까지 배운 FastAPI 내용을 바탕으로 직접 프로젝트를 시작할 차례입니다!

이제는 단순한 이론이나 짧은 실습이 아니라, 사용자 관리부터 게시글 또는 할일 등록까지 기능이 갖춰진 나만의 API 서비스를 만들게 될 거예요.

이번 실전 프로젝트는 두 가지 주제 중 하나를 선택해서 진행할 수 있어요:

1) 블로그 API 또는 2) Todo 관리 API.

둘 다 FastAPI + SQLAlchemy를 활용해 RESTful 백엔드 API를 직접 설계하고 구현하는 경험을 하게 됩니다.

각 기능별 CRUD 구현뿐 아니라, 모델 설계, 프로젝트 구조 구성, 예외 처리, 테스트까지 고려한 구조로 만들어볼 거예요.

지금부터 7일차 여정을 함께 출발해볼까요? 🚀

 

1. 최종 프로젝트 개요 및 설계 🧭

여러분, 여기까지 따라오시느라 정말 고생 많으셨어요. 😊

지금까지 FastAPI의 기본적인 사용법, SQLAlchemy 연동, 라우팅, Pydantic 모델링, 예외 처리 등을 하나씩 차근차근 배워오셨죠?

이제는 그 모든 퍼즐을 모아서 직접 하나의 프로젝트로 완성해볼 차례입니다.

1.1 프로젝트 주제와 목표 🎯

이번 프로젝트에서는 두 가지 주제 중 하나를 선택할 수 있어요:

  • 블로그 API: 사용자 인증, 글 작성, 글 목록 조회, 상세 보기, 댓글 작성/삭제, 태그 기능 포함
  • Todo API: 사용자 생성, 할일 CRUD, 우선순위 설정, 완료 상태 필터링 기능 제공

물론 두 프로젝트 모두 공통적으로 사용자 모델, 데이터베이스 연동, CRUD 기능이 핵심이 되며, 선택한 주제에 따라 약간의 기능 차이가 있을 뿐입니다.

1.2 이번 실습의 핵심 목표 🧩

  1. FastAPI의 주요 기능을 실제 API에 적용
  2. SQLAlchemy를 통한 모델 정의와 DB 연동 실습
  3. Pydantic을 활용한 데이터 검증
  4. 기본적인 예외 처리와 테스트 코드 작성
  5. REST API의 CRUD 기능 전반 구현

즉, 이번 실습의 핵심은 단순히 동작하는 코드를 작성하는 것이 아니라, 정리된 구조와 유지보수 가능한 코드를 직접 설계하고 구현하는 것입니다.

실무에서 백엔드를 처음 시작할 때 어떤 흐름으로 개발을 진행해야 하는지, 그 뼈대를 이번 실습을 통해 직접 익히게 될 거예요.

 

이제 다음 단계로 넘어가서, 구체적인 데이터 모델을 어떻게 설계할지 알아보겠습니다.

 

 

2. 데이터 모델 설계 📐

서비스를 만들기 전, 가장 먼저 해야 할 일은 데이터 구조를 어떻게 설계할 것인가입니다.

바로 모델링이죠.

데이터베이스 구조가 깔끔하게 설계되어야 API도 명확하게 작동하고, 유지보수도 수월해집니다.

이번엔 선택한 프로젝트 유형에 따라 각기 다른 모델을 설계하게 됩니다.

2.1 블로그 API 모델 예시 ✍️

블로그 API를 선택한 경우, 보통 다음과 같은 세 가지 모델이 필요해요:

모델 필드 설명
User id, username, email, hashed_password 회원 정보 (비밀번호는 해싱 처리)
Post id, title, content, created_at, author_id 글 제목, 본문, 작성 시간 및 작성자
Comment id, content, created_at, post_id, author_id 댓글 내용과 작성자, 연결된 게시글

여기서 중요한 건 외래키 관계입니다.

Post와 Comment는 모두 User와 연결되고, Comment는 Post에도 연결되죠.

SQLAlchemy에서는 ForeignKeyrelationship으로 이를 구현할 수 있어요.

2.2 Todo API 모델 예시 ✅

Todo 프로젝트는 더 단순합니다. 모델은 아래 두 개만 필요하죠.

모델 필드 설명
User id, username 사용자 이름 (unique 설정 권장)
Todo id, title, description, priority, completed, user_id 할일 제목, 설명, 우선순위, 완료 여부, 사용자

💡 모델 설계 팁

  • 문자열 길이 제한을 String(length)으로 명확하게 지정하세요.
  • nullable=False는 필수 필드에 반드시 지정해줘야 합니다.
  • 고유성이 필요한 필드는 unique=True로 중복 방지!

이제 데이터 모델이 명확하게 정리되었으니,

다음 단계는 실제 프로젝트 디렉토리 구조를 만들고 빈 틀을 잡아보는 것입니다.

프로젝트가 커질수록 구조가 중요해지니까요.

계속해서 다음 단계에서 프로젝트 뼈대를 만들어볼게요!

 

 

3. 프로젝트 구조 뼈대 만들기 🧱

FastAPI 프로젝트를 시작할 때 가장 먼저 해야 할 일은 전체 폴더 구조를 체계적으로 잡는 것이에요.

한 파일 안에 모든 코드를 몰아넣는 방식은 소규모 예제에선 괜찮지만, 프로젝트가 커지면 유지보수가 거의 불가능하죠.

이번 프로젝트에서는 아래와 같은 구조를 기준으로 설계를 시작합니다.

3.1 기본 디렉토리 구조

myapi/
├── main.py               # FastAPI 앱 실행 진입점
├── models.py             # SQLAlchemy ORM 모델 정의
├── schemas.py            # Pydantic 데이터 모델 정의
├── database.py           # DB 설정 (engine, session, get_db)
├── routers/
│   ├── users.py          # 사용자 관련 라우터
│   └── posts.py          # 블로그 글 관련 라우터 (or todos.py)
└── __init__.py           # 패키지 초기화 파일 (선택 사항)

✅ 핵심 원칙은 다음과 같아요:

  • 각 기능을 독립적인 모듈로 구성 → 관심사의 분리(SOC) 원칙 준수
  • DB 관련 코드는 database.py에 모아서 일원화
  • Pydantic 모델은 schemas.py에서 관리하여 API 스키마 명확화

🛠 main.py 기본 틀

from fastapi import FastAPI
from routers import users, posts  # 또는 todos

app = FastAPI()

app.include_router(users.router)
app.include_router(posts.router)

🔌 database.py 기본 구성

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

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

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

Base = declarative_base()

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

이처럼 프로젝트 초기 구조를 잘 설계하면, 나중에 기능을 추가할 때도 훨씬 덜 헤매고 유지보수가 쉬워집니다.

앞으로 posts.py, todos.py, comments.py 등을 기능별로 확장할 수 있도록 유연한 구조로 시작하세요!

 

이제 구조가 준비되었으니, 본격적으로 사용자 기능부터 구현해볼까요?

다음 섹션에서는 사용자 생성과 조회를 구현하면서 실전 코딩을 시작합니다!

 

 

4. 사용자 기능 구현 🔐

프로젝트에서 사용자(User)는 모든 기능의 출발점이에요.

할일이든 게시글이든 결국 ‘누가 작성했는지’가 중요하니까요.

이번 단계에서는 사용자 등록(POST)과 조회(GET) API를 구현합니다.

이 두 기능만 제대로 작동해도 프로젝트의 절반은 성공한 셈이에요!

4.1 Pydantic 스키마 만들기

우선 schemas.py에 사용자 관련 데이터 구조를 정의합니다:

from pydantic import BaseModel, EmailStr

class UserBase(BaseModel):
    username: str
    email: EmailStr

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int

    class Config:
        orm_mode = True

4.2 SQLAlchemy 모델 정의

models.py에는 사용자 모델을 다음과 같이 정의해요:

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

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(30), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    hashed_password = Column(String(128), nullable=False)

💡 비밀번호는 평문 저장 No!

여기선 해시 없이 저장하겠지만, 실무에서는 꼭 bcrypt 같은 해싱 알고리즘을 사용해야 해요.

4.3 사용자 생성 API

# routers/users.py
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from database import get_db
import models, schemas

router = APIRouter(prefix="/users", tags=["Users"])

@router.post("/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    existing_user = db.query(models.User).filter(
        (models.User.username == user.username) | (models.User.email == user.email)
    ).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="Username or email already exists")

    db_user = models.User(
        username=user.username,
        email=user.email,
        hashed_password=user.password  # 실무에서는 해싱 필수!
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

4.4 사용자 조회 API

@router.get("/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

🧪 테스트 팁

  • Swagger UI(/docs)에서 POST → 바로 GET으로 테스트 가능!
  • uvicorn main:app --reload로 서버 실행하세요.

이제 사용자가 등록되고 조회되는 API가 완성되었습니다!

이 데이터를 기반으로 다음 단계에서는 본격적인 게시글(Post) 혹은 할일(Todo) 기능을 구현해보겠습니다.

 

 

5. 글쓰기 or 할일 등록 기능 구현 ✏️

이제 사용자 기능이 잘 작동하는 걸 확인했다면, 다음은 본 서비스의 중심 기능인 게시글(Post) 또는 할일(Todo)을 구현할 차례입니다.

블로그 API를 선택한 분은 글쓰기 기능을, Todo API를 선택한 분은 할일 등록 기능부터 차근차근 구현해보면 됩니다.

5.1 블로그 글쓰기 기능 (POST /posts/) 📝

# schemas.py
from datetime import datetime

class PostBase(BaseModel):
    title: str
    content: str

class PostCreate(PostBase):
    author_id: int

class Post(PostBase):
    id: int
    created_at: datetime
    author_id: int

    class Config:
        orm_mode = True
# models.py
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship
from datetime import datetime

class Post(Base):
    __tablename__ = "posts"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(100), nullable=False)
    content = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    author_id = Column(Integer, ForeignKey("users.id"), nullable=False)

    author = relationship("User", backref="posts")
# routers/posts.py
@router.post("/", response_model=schemas.Post)
def create_post(post: schemas.PostCreate, db: Session = Depends(get_db)):
    db_post = models.Post(
        title=post.title,
        content=post.content,
        author_id=post.author_id
    )
    db.add(db_post)
    db.commit()
    db.refresh(db_post)
    return db_post

5.2 글 목록 및 상세 조회 (GET /posts/, /posts/{id}) 📚

@router.get("/", response_model=List[schemas.Post])
def read_posts(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    posts = db.query(models.Post).offset(skip).limit(limit).all()
    return posts

@router.get("/{post_id}", response_model=schemas.Post)
def read_post(post_id: int, db: Session = Depends(get_db)):
    post = db.query(models.Post).filter(models.Post.id == post_id).first()
    if not post:
        raise HTTPException(status_code=404, detail="Post not found")
    return post

쿼리 파라미터로 skiplimit을 받아 간단한 페이징 기능도 적용해봤어요.

물론 실제 서비스라면 페이지 번호, 정렬 기준 등 더 다양한 필터가 필요하겠지만, 지금은 핵심 흐름만 익히는 게 중요합니다.

5.3 할일 등록 기능 (POST /todos/) ✅

# schemas.py
class TodoBase(BaseModel):
    title: str
    description: str

class TodoCreate(TodoBase):
    user_id: int

class Todo(TodoBase):
    id: int
    completed: bool
    priority: int

    class Config:
        orm_mode = True
# models.py
class Todo(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(100), nullable=False)
    description = Column(String, nullable=False)
    completed = Column(Boolean, default=False)
    priority = Column(Integer, default=1)
    user_id = Column(Integer, ForeignKey("users.id"))

    user = relationship("User", backref="todos")
# routers/todos.py
@router.post("/", response_model=schemas.Todo)
def create_todo(todo: schemas.TodoCreate, db: Session = Depends(get_db)):
    db_todo = models.Todo(
        title=todo.title,
        description=todo.description,
        user_id=todo.user_id
    )
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

5.4 할일 목록 및 상세 조회 (GET /todos/, /todos/{id}) 📋

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

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

여기까지 완성되면 사용자 - 게시글, 또는 사용자 - 할일 구조의 기본 CRUD의 반 이상이 만들어진 셈입니다. 다음 단계에선 이 모든 기능들이 실제로 잘 작동하는지 테스트해보는 실습을 진행할 거예요!

 

 

6. 서버 실행 및 결과 테스트 ⚙️

이제 우리 프로젝트의 기본 기능은 모두 구현이 끝났습니다!

이제는 서버를 실행해보고, 실제 요청을 보내보며 테스트하는 단계입니다.

FastAPI의 가장 큰 장점 중 하나가 바로 Swagger UI를 통한 인터랙티브 테스트 환경이죠.

하나씩 점검하면서 내가 만든 API가 잘 작동하는지 직접 확인해봅시다.

6.1 서버 실행하기

uvicorn main:app --reload

이 명령어로 로컬 서버를 실행하면 http://127.0.0.1:8000/docs 에서 자동 생성된 Swagger 문서에 접속할 수 있습니다.

이제 각 API 기능을 직접 클릭해보며 테스트해볼 수 있어요.

6.2 주요 테스트 시나리오 🧪

  • POST /users/ : 사용자 등록 요청 → 성공 시 200 반환 및 ID 확인
  • GET /users/{id} : 등록한 사용자 조회 → 사용자 정보 반환
  • POST /posts/ 또는 /todos/ : 글 또는 할일 등록
  • GET /posts/ 또는 /todos/ : 전체 목록 확인
  • GET /posts/{id} 또는 /todos/{id} : 상세 페이지 확인

🚨 자주 발생하는 오류

  • IntegrityError: 이미 존재하는 username 또는 email → 400 예외 처리 필요
  • AttributeError: 'dict' object has no attribute → Pydantic 모델에 orm_mode = True 빠짐

6.3 마무리 점검 리스트 ✅

항목 확인 여부
DB 모델 생성 완료
Pydantic 스키마 정의
CRUD API 작동 확인
Swagger UI 테스트

축하합니다! 여기까지 완성했다면 여러분은 이제 FastAPI 기반의 실제 동작하는 백엔드 서비스를 손수 구현해본 경험을 쌓으신 거예요.

 

 

마무리 🎯

지금까지 FastAPI와 SQLAlchemy를 활용한 실전 API 프로젝트를 함께 구현해보았습니다.

단순한 Hello World 수준을 넘어, 사용자 생성부터 게시글/할일 등록, 조회 API까지 완성된 상태의 백엔드가 완성되었죠.

직접 설계한 데이터 모델, 모듈화된 프로젝트 구조, 그리고 인터랙티브한 Swagger UI 테스트까지. 이건 단순한 튜토리얼이 아니라, 실무 감각을 기를 수 있는 제대로 된 훈련이었어요.

 

이번 실습이 끝이 아니라는 점, 기억해주세요!

이후에는 글 수정 및 삭제 기능, 댓글 기능, JWT 인증 및 권한 관리, 에러 핸들링 구조화, pytest를 활용한 테스트 자동화 등 보다 확장된 기능을 다룰 예정입니다.

 

실제로 서비스를 만들고 싶다면 지금 만든 코드를 바탕으로 Docker, GitHub Actions, AWS EC2 배포까지 이어가볼 수 있겠죠? 이제 여러분 손에 달렸습니다.

지금 당장 터미널을 열고 새로운 아이디어로 코딩을 시작해보세요.

세상에 단 하나뿐인 여러분만의 API 서비스가 될 수도 있으니까요!

반응형