FastAPI로 나만의 게시판 만들기: 2단계 - 데이터베이스 연동
Ethan Miller
Product Engineer · Leapcell

이전 게시물에서 우리는 아무것도 없는 상태에서 FastAPI를 사용하여 게시판 프로토타입을 빠르게 구축했습니다. 기능은 기본적이었지만 이미 게시글 작성과 목록 표시라는 게시판의 핵심 기능을 갖추고 있었습니다.
이 프로토타입에는 한 가지 중요한 문제가 있습니다: 인메모리 데이터베이스로 파이썬 리스트를 사용했습니다. 이는 서버가 재시작될 때마다 사용자가 게시한 모든 게시물이 사라진다는 것을 의미합니다.
이 문제를 해결하기 위해 이번 게시물에서는 게시판에 실제 데이터베이스인 PostgreSQL을 도입하고 SQLAlchemy ORM을 통해 이를 운영하여 영구적인 데이터 저장을 달성할 것입니다.
시작해 봅시다!
PostgreSQL 준비
튜토리얼을 시작하기 전에 PostgreSQL 데이터베이스가 준비되어 있어야 합니다. 로컬에 설치할 수 있으며, 공식 PostgreSQL 웹사이트에서 설치 방법을 찾을 수 있습니다.
더 쉬운 대안은 Leapcell을 사용하여 클릭 한 번으로 무료 온라인 데이터베이스를 얻는 것입니다.
웹사이트에 계정을 등록한 후 "데이터베이스 생성"을 클릭합니다.
데이터베이스 이름을 입력하고 배포 영역을 선택하면 PostgreSQL 데이터베이스를 생성할 수 있습니다.
새로운 페이지가 나타나면 데이터베이스 연결에 필요한 정보가 표시됩니다. 하단에는 제어판이 제공되어 웹페이지에서 직접 데이터베이스를 읽고 수정할 수 있습니다.
이 연결 정보를 사용하면 추가적인 로컬 구성 없이도 다양한 도구에서 데이터베이스에 직접 액세스할 수 있습니다.
1단계: 새 의존성 설치
파이썬이 PostgreSQL과 통신할 수 있도록 몇 가지 새로운 라이브러리가 필요합니다. 가상 환경이 활성화되어 있는지 확인한 다음 다음 명령을 실행하십시오:
pip install "sqlalchemy[asyncio]" "psycopg[binary]"
sqlalchemy
는 파이썬 생태계에서 가장 인기 있는 객체 관계형 매핑(ORM) 도구입니다. 이를 통해 지루한 SQL 문을 작성하는 대신 파이썬 코드를 사용하여 데이터베이스를 조작할 수 있습니다.
psycopg[binary]
는 PostgreSQL과 파이썬을 연결하는 데 사용됩니다. SQLAlchemy는 이를 사용하여 데이터베이스와 통신합니다.
2단계: 데이터베이스 연결 설정
데이터베이스 연결 관련 모든 구성을 처리할 database.py
라는 새 파일을 만듭니다.
database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.orm import DeclarativeBase # 1. 데이터베이스 URL # 형식: "postgresql+psycopg://<사용자>:<비밀번호>@<호스트>:<포트>/<db이름>" DATABASE_URL = "postgresql+psycopg://your_user:your_password@localhost/fastapi_forum_db" # 2. 데이터베이스 엔진 생성 engine = create_async_engine(DATABASE_URL) SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine) # 3. Base 클래스 생성 # 우리의 ORM 모델은 나중에 이 클래스를 상속받습니다. class Base(DeclarativeBase): pass
create_async_engine
은 데이터베이스와의 통신을 위한 핵심인 SQLAlchemy 엔진을 생성합니다.SessionLocal
은 데이터베이스 작업(생성, 읽기, 업데이트, 삭제)을 수행하는 데 사용됩니다.Base
클래스는 이 튜토리얼의 모든 데이터베이스 모델(데이터 테이블)의 기본 클래스가 됩니다.
3단계: 데이터 테이블 모델 정의
이제 메모리를 데이터베이스로 사용하는 대신, 데이터베이스의 posts
테이블 구조를 실제로 정의하는 SQLAlchemy 모델을 만들어 보겠습니다.
models.py
라는 새 파일을 만듭니다:
models.py
from sqlalchemy import Column, Integer, String from .database import Base class Post(Base): __tablename__ = "posts" id = Column(Integer, primary_key=True, index=True) title = Column(String, index=True) content = Column(String)
이 Post
클래스는 posts
테이블 구조에 직접적으로 해당합니다:
__tablename__ = "posts"
: 데이터베이스의 해당 테이블 이름을 지정합니다.id
: 쿼리 속도를 높이기 위해 인덱스가 생성된 정수 기본 키입니다.title
및content
: 문자열 유형 필드입니다.
모델을 정의한다고 해서 테이블이 이미 데이터베이스에 존재하는 것은 아닙니다. 이 테이블을 수동으로 생성하기 위해 SQL 또는 유사한 명령을 실행해야 합니다.
해당 SQL은 다음과 같습니다:
CREATE TABLE posts ( id SERIAL PRIMARY KEY, title VARCHAR, content TEXT );
Leapcell로 데이터베이스를 생성했다면 해당 웹페이지에서 직접 SQL을 입력하여 데이터베이스를 수정할 수 있습니다.
4단계: API를 데이터베이스 사용하도록 리팩터링
이것이 가장 중요한 단계입니다. 인메모리 db
리스트를 완전히 제거하고 API 경로 함수를 SQLAlchemy 세션을 통해 PostgreSQL과 상호 작용하도록 수정해야 합니다.
먼저 database.py
에 종속성 함수를 추가하십시오:
database.py
(함수 추가)
# ... 이전 코드는 변경되지 않음 ... # 종속성: 데이터베이스 세션 가져오기 async def get_db(): async with SessionLocal() as session: yield session
이제 Depends(get_db)
를 사용하여 API 경로 작업 함수에서 데이터베이스 세션을 가져올 수 있습니다.
아래는 데이터베이스 사용으로 완전히 전환된 main.py
의 최종 완전 버전입니다.
main.py
(최종 완전 버전)
from fastapi import FastAPI, Form, Depends from fastapi.responses import HTMLResponse, RedirectResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from typing import List from . import models from .database import engine, get_db app = FastAPI() # --- HTML 템플릿 --- def generate_html_response(posts: List[models.Post]): posts_html = "" for post in posts: # 더 이상 reversed()가 필요 없습니다. 쿼리에서 정렬할 수 있습니다. posts_html += f""" <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;"> <h3>{post.title} (ID: {post.id})</h3> <p>{post.content}</p> </div> """ html_content = f""" <html> <head> <title>My FastAPI Forum</title> <style> body {{ font-family: sans-serif; margin: 2em; }} input, textarea {{ width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; }} button {{ padding: 10px 15px; background-color: #007BFF; color: white; border: none; cursor: pointer; }} button:hover {{ background-color: #0056b3; }} </style> </head> <body> <h1>Welcome to My Forum</h1> <h2>Create a New Post</h2> <form action="/api/posts" method="post"> <input type="text" name="title" placeholder="Post Title" required><br> <textarea name="content" rows="4" placeholder="Post Content" required></textarea><br> <button type="submit">Post</button> </form> <hr> <h2>Post List</h2> {posts_html} </body> </html> """ return HTMLResponse(content=html_content, status_code=200) # --- 라우트 --- @app.get("/", response_class=RedirectResponse) def read_root(): return "/posts" # 페이지 표시를 위한 라우트 @app.get("/posts", response_class=HTMLResponse) async def view_posts(db: AsyncSession = Depends(get_db)): # 1. 데이터베이스에서 모든 게시물 쿼리 result = await db.execute(select(models.Post).order_by(desc(models.Post.id))) posts = result.scalars().all() # 2. HTML 렌더링 return generate_html_response(posts) @app.post("/api/posts") async def create_post( title: str = Form(...), content: str = Form(...), db: AsyncSession = Depends(get_db) ): # 1. 새 Post 개체 생성 new_post = models.Post(title=title, content=content) # 2. 데이터베이스 세션에 추가 db.add(new_post) # 3. 커밋 및 데이터베이스에 저장 await db.commit() # 4. 새로 생성된 ID를 가져오기 위해 개체 새로 고침 await db.refresh(new_post) return RedirectResponse(url="/posts", status_code=303)
위 단계에서는 다음을 수행했습니다:
- 인메모리
db
리스트를 제거했습니다. - 데이터베이스와 상호 작용하는 모든 라우트 함수를
async def
로 변경하고, 데이터베이스 작업 전에await
를 사용했습니다. 이는 비동기 데이터베이스 드라이버 및 엔진을 선택했기 때문입니다. GET /posts
및POST /api/posts
를 데이터베이스에서 읽고 쓰는 데 사용하도록 수정했습니다.
실행 및 검증
uvicorn
서버를 다시 시작하십시오:
uvicorn main:app --reload
브라우저를 열고 http://127.0.0.1:8000
으로 이동하십시오. 빈 게시물 목록이 표시됩니다 (데이터베이스가 새 것이기 때문).
몇 가지 새 게시물을 게시해 보십시오. 이전과 같이 표시될 것입니다.
이제 데이터 지속성을 테스트해 보겠습니다:
- 터미널에서
Ctrl+C
를 눌러uvicorn
서버를 종료하십시오. - 서버를 다시 시작하십시오.
http://127.0.0.1:8000
을 다시 방문하십시오.
이전에 게시한 게시물이 여전히 있는 것을 볼 수 있습니다! 이제 게시판 데이터가 PostgreSQL에 저장되어 영구 저장을 달성했습니다.
프로젝트 온라인 배포
첫 번째 튜토리얼과 마찬가지로, 이 단계의 결과를 온라인에 배포하여 친구들이 프로젝트의 변경 사항과 진행 상황을 경험하게 할 수 있습니다.
간단한 배포 솔루션은 Leapcell을 사용하는 것입니다.
이전에 배포했다면 단순히 코드를 Git 저장소에 푸시하면 Leapcell이 자동으로 최신 코드를 다시 배포합니다.
Leapcell의 배포 서비스는 처음 사용하는 경우 이 게시물의 튜토리얼을 참조할 수 있습니다.
요약
이 튜토리얼에서는 게시판의 백엔드 스토리지를 안정적이지 못한 인메모리 리스트에서 안정적인 PostgreSQL 데이터베이스로 성공적으로 마이그레이션했습니다.
하지만 main.py
파일에 큰 HTML 문자열이 너무 많아 코드를 유지 관리하기 어렵다는 점을 알아차렸을 수 있습니다.
다음 게시물에서는 "템플릿 엔진" 개념을 소개하고 Jinja2를 사용하여 HTML 코드를 독립적인 템플릿 파일로 분리하여 프런트엔드 코드의 가독성과 유지 관리성을 단순화할 것입니다.