FastAPI로 나만의 포럼 만들기: 3단계 - HTML 템플릿
James Reed
Infrastructure Engineer · Leapcell

이전 글에서 포럼에 PostgreSQL 데이터베이스를 도입하여 영구적인 데이터 저장 기능을 구현했으며, 서버가 재시작되어도 데이터가 손실되지 않습니다.
이제 더 자신 있게 개선할 수 있습니다. 하지만 현재 모든 인터페이스 스타일(HTML)이 main.py
에 직접 작성되어 있다는 점을 눈치챘을 수 있습니다. 이것이 미래에 새로운 기능이 추가될 때마다 main.py
에 더 많은 HTML을 넣어야 한다는 뜻일까요?
이는 작성하기 번거로울 뿐만 아니라, Python 코드에 많은 HTML 문자열이 섞여 코드를 읽고 유지보수하기 어렵게 만듭니다.
이 문제를 해결하기 위해 이 글에서는 Jinja2 템플릿 엔진을 도입하여 백엔드 로직(Python)과 프론트엔드 프레젠테이션(HTML)을 분리함으로써 프로젝트 구조를 더 명확하고 유지보수하기 쉽게 만들 것입니다.
1단계: Jinja2 설치
FastAPI에서 공식적으로 권장하는 템플릿 엔진은 Jinja2입니다. 가상 환경이 활성화되어 있는지 확인한 다음 다음 명령을 실행하세요.
pip install jinja2
2단계: templates 디렉토리 및 파일 생성
프로젝트를 더 잘 구성하기 위해 HTML 템플릿 파일을 저장할 전용 디렉토리를 만들어야 합니다.
프로젝트의 루트 디렉토리에서 templates
라는 새 폴더를 만듭니다.
fastapi-forum/
├── templates/ <-- 새 디렉토리
│ └── posts.html <-- 새 템플릿 파일
├── main.py
├── database.py
├── models.py
└── venv/
다음으로, main.py
의 generate_html_response
함수에 있는 HTML 코드를 새 templates/posts.html
파일로 이동하고 Jinja2 구문을 사용하여 수정하겠습니다.
templates/posts.html
<!DOCTYPE html> <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> {% for post in posts %} <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;"> <h3>{{ post.title }} (ID: {{ post.id }})</h3> <p>{{ post.content }}</p> </div> {% endfor %} </body> </html>
주요 변경 사항은 Post list
섹션입니다.
- Python for 루프를 대체하기 위해
{% for post in posts %}
와{% endfor %}
를 사용합니다. {{ post.title }}
와 같은 이중 중괄호 구문을 사용하여 동적으로 변수를 삽입합니다.
posts
변수는 템플릿을 렌더링할 때 FastAPI 백엔드에서 전달됩니다.
3단계: FastAPI에서 템플릿 설정 및 사용
이제 HTML이 분리되었으므로 FastAPI가 이러한 템플릿 파일을 찾고 사용하는 방법을 알려주도록 main.py
를 수정해야 합니다.
main.py
(최종 버전)
from fastapi import FastAPI, Form, Depends, Request from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from typing import List import models from database import get_db app = FastAPI() # 1. Jinja2Templates 설정 templates = Jinja2Templates(directory="templates") # --- 라우트 --- @app.get("/", response_class=RedirectResponse) def read_root(): return RedirectResponse(url="/posts", status_code=303) # 페이지 표시를 위한 라우트 @app.get("/posts", response_class=HTMLResponse) async def view_posts(request: Request, db: AsyncSession = Depends(get_db)): # 데이터베이스에서 모든 게시물 쿼리 result = await db.execute(select(models.Post).order_by(desc(models.Post.id))) posts = result.scalars().all() # 2. 템플릿을 사용하여 렌더링 return templates.TemplateResponse("posts.html", {"request": request, "posts": posts}) @app.post("/api/posts") async def create_post( title: str = Form(...), content: str = Form(...), db: AsyncSession = Depends(get_db) ): # 새 Post 객체 생성 new_post = models.Post(title=title, content=content) # 데이터베이스 세션에 추가 db.add(new_post) # 데이터베이스에 커밋 및 저장 await db.commit() # 새로 생성된 ID를 가져오기 위해 객체 새로고침 await db.refresh(new_post) return RedirectResponse(url="/posts", status_code=303)
어떤 핵심 변경 사항을 만들었습니까?
fastapi.templating
에서Jinja2Templates
를 가져왔습니다.templates = Jinja2Templates(directory="templates")
로 템플릿 엔진 인스턴스를 생성하여 템플릿 파일이templates
디렉토리에 저장되어 있음을 알렸습니다.- HTML 문자열 연결에 사용되던 이전
generate_html_response
함수를 삭제하고,HTMLResponse
를 반환하는 대신templates.TemplateResponse()
를 호출합니다. TemplateResponse
는 템플릿 파일 이름("posts.html"
)과 템플릿에 전달될 모든 데이터를 포함하는 컨텍스트 딕셔너리를 매개변수로 받습니다.request
객체와 데이터베이스에서 쿼리한posts
목록을 전달했습니다.
4단계: 실행 및 확인
uvicorn 서버를 다시 시작하세요.
uvicorn main:app --reload
브라우저를 열고 http://127.0.0.1:8000
으로 이동합니다. 이전과 동일하게 보이며 모든 기능이 정상적으로 작동하는 것을 확인할 수 있습니다.
하지만 프로젝트의 내부 구조는 완전히 달라졌습니다. 이제 Python 코드는 데이터 및 논리 처리에만 책임이 있으며 HTML 코드는 콘텐츠 표시를 중심으로 합니다. 이렇게 하면 향후 페이지 스타일을 수정하거나 새 기능을 추가하는 것이 훨씬 쉽고 효율적입니다.
프로젝트 온라인 배포
첫 번째 튜토리얼과 마찬가지로 이 단계의 결과를 온라인에 배포하여 친구들에게 프로젝트의 변경 사항과 진행 상황을 경험하게 할 수 있습니다.
간단한 배포 솔루션은 Leapcell을 사용하는 것입니다.
이전에 배포한 적이 있다면 코드를 Git 리포지토리에 푸시하기만 하면 Leapcell에서 최신 코드를 자동으로 다시 배포합니다.
Leapcell의 배포 서비스를 사용한 적이 없다면 이 글의 튜토리얼을 참조할 수 있습니다.
요약
축하합니다! FastAPI 프로젝트에 Jinja2 템플릿 엔진을 성공적으로 통합했습니다.
현재 포럼은 누구나 익명으로 게시할 수 있지만, 이것을 진정한 포럼이라고 보기는 어렵습니다. 실제 커뮤니티는 다양한 사용자가 있을 뿐만 아니라 각 사용자도 자신의 신원을 가져야 합니다.
다음 글에서는 포럼에 사용자 시스템을 추가하여 사용자 등록 및 로그인 기능을 구현하고 각 사용자가 자신의 신원으로 포럼에 액세스할 수 있도록 할 것입니다.