10분 만에 코드 첫 줄부터 라이브 배포까지: 초고속 FastAPI 블로그 강좌
Wenhao Wang
Dev Intern · Leapcell

이것은 FastAPI의 속성 강좌입니다. 이 튜토리얼에서는 Python FastAPI를 사용하여 몇 가지 간단한 단계와 10분 안에 코드 첫 줄부터 배포까지 블로그를 구축할 것입니다.
매우 빠른 이유는 이 튜토리얼이 모든 개념을 깊이 있게 다루지 않기 때문입니다. 대신, 완성된 제품을 직접 구축하도록 안내할 것입니다. 제 생각에는 기존 제품을 자신의 아이디어에 맞게 수정하는 것이 새로운 프레임워크를 마스터하는 가장 효율적인 방법입니다.
이 블로그는 Python 웹 개발자를 위한 일반적인 기술 스택을 사용한 순수한 백엔드 논리 및 렌더링 프로젝트입니다:
- FastAPI + Uvicorn
- PostgreSQL
- SQLModel: 코드를 통해 데이터베이스와 상호 작용합니다.
- Jinja2: 프런트엔드 페이지를 렌더링하는 템플릿 엔진.
더 이상 지체하지 않고 시작하겠습니다:
1. 프로젝트 초기화
터미널에서 다음 명령을 실행하십시오:
# 1. 프로젝트 디렉토리 생성 및 접속 mkdir fastapi-personal-blog && cd fastapi-personal-blog # 2. 가상 환경 생성 및 활성화 python -m venv venv source venv/bin/activate
requirements.txt
파일을 만들고 다음 종속성을 추가하십시오:
# requirements.txt fastapi uvicorn[standard] sqlmodel psycopg2-binary jinja2 python-dotenv python-multipart
그런 다음 설치하십시오:
pip install -r requirements.txt
2. PostgreSQL 데이터베이스 연결
다음으로 PostgreSQL 데이터베이스를 통합할 것입니다.
데이터베이스 설정
튜토리얼 속도를 높이기 위해 로컬에서 데이터베이스를 설치하고 설정하는 단계는 건너뛸 것입니다. 대신 온라인 데이터베이스를 직접 프로비저닝할 것입니다.
Leapcell에서 클릭 한 번으로 무료 데이터베이스를 만들 수 있습니다.
웹사이트에서 계정을 등록한 후 "데이터베이스 생성"을 클릭하십시오.
데이터베이스 이름을 입력하고, 배포 지역을 선택하면 PostgreSQL 데이터베이스를 생성할 수 있습니다.
새로 나타나는 페이지에서 데이터베이스에 연결하는 데 필요한 정보를 찾을 수 있습니다. 웹 페이지에서 데이터베이스를 직접 읽고 수정할 수 있는 제어판이 하단에 제공됩니다.
새 페이지의 연결 정보에서 연결 문자열을 찾으십시오. 곧 사용할 것입니다.
데이터베이스 연결 구성
프로젝트 루트 디렉토리에 .env
파일을 만들어 민감한 연결 정보를 저장하십시오. Leapcell에서 얻은 URL을 붙여넣으십시오.
# .env DATABASE_URL="postgresql://user:password@host:port/dbname"
다음으로 database.py
파일을 만들어 데이터베이스 연결을 처리하십시오:
# database.py import os from sqlmodel import create_engine, SQLModel, Session from dotenv import load_dotenv load_dotenv() DATABASE_URL = os.getenv("DATABASE_URL") engine = create_engine(DATABASE_URL) def create_db_and_tables(): SQLModel.metadata.create_all(engine) def get_session(): with Session(engine) as session: yield session
3. 게시물 모듈 생성
다음으로 블로그 게시물과 관련된 논리를 구현해 보겠습니다.
프로젝트 루트 디렉토리에 models.py
파일을 만들어 데이터 모델을 정의하십시오.
# models.py import uuid from datetime import datetime from typing import Optional from sqlmodel import Field, SQLModel class Post(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True) title: str content: str createdAt: datetime = Field(default_factory=datetime.utcnow, nullable=False)
SQLModel은 이 클래스를 데이터베이스의 post
테이블로 자동 매핑합니다. create_db_and_tables
함수는 애플리케이션이 시작될 때 이 테이블이 생성되도록 하므로 SQL을 수동으로 실행할 필요가 없습니다.
4. 웹 렌더링을 위한 Jinja2 설정
Jinja2를 설정하여 HTML 웹사이트를 렌더링하십시오.
디렉토리 구조 생성
프로젝트 루트 디렉토리에 templates
및 public
폴더를 만듭니다. templates
폴더에는 HTML 파일을 저장하고 public
폴더에는 CSS 및 JavaScript와 같은 정적 자산을 저장합니다.
최종 프로젝트 구조는 다음과 같습니다:
- fastapi-personal-blog
- templates
- public
- .env
- database.py
- models.py
- requirements.txt
프런트엔드 페이지 구현
templates
폴더 안에 다음 파일을 만듭니다:
-
_header.html
(재사용 가능한 헤더)<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{{ title }}</title> <link rel="stylesheet" href="/static/css/style.css" /> </head> <body> <header> <h1><a href="/">My Blog</a></h1> <a href="/posts/new" class="new-post-btn">New Post</a> </header> <main></main> </body> </html>
-
_footer.html
(재사용 가능한 푸터)</main> <footer> <p>© 2025 My Blog</p> </footer> </body> </html>
-
index.html
(블로그 홈페이지){% include "_header.html" %} <div class="post-list"> {% for post in posts %} <article class="post-item"> <h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2> <p>{{ post.content[:150] }}...</p> <small>{{ post.createdAt.strftime('%Y-%m-%d') }}</small> </article> {% endfor %} </div> {% include "_footer.html" %}
-
post.html
(게시물 상세 페이지){% include "_header.html" %} <article class="post-detail"> <h1>{{ post.title }}</h1> <small>{{ post.createdAt.strftime('%Y-%m-%d') }}</small> <div class="post-content">{{ post.content | replace('\n', '<br />') | safe }}</div> </article> <a href="/" class="back-link">← Back to Home</a> {% include "_footer.html" %}
-
new-post.html
(새 게시물 페이지){% include "_header.html" %} <form action="/posts" method="POST" class="post-form"> <div class="form-group"> <label for="title">Title</label> <input type="text" id="title" name="title" required /> </div> <div class="form-group"> <label for="content">Content</label> <textarea id="content" name="content" rows="10" required></textarea> </div> <button type="submit">Submit</button> </form> {% include "_footer.html" %}
CSS 스타일 추가
public
디렉토리에 css
폴더를 만들고 그 안에 style.css
파일을 만듭니다. 스타일은 원본 기사와 일치합니다.
/* public/css/style.css */ body { font-family: sans-serif; line-height: 1.6; margin: 0; background-color: #f4f4f4; color: #333; } header { background: #333; color: #fff; padding: 1rem; display: flex; justify-content: space-between; align-items: center; } header a { color: #fff; text-decoration: none; } main { max-width: 800px; margin: 2rem auto; padding: 1rem; background: #fff; border-radius: 5px; } .post-item { margin-bottom: 2rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; } .post-item h2 a { text-decoration: none; color: #333; } .post-detail .post-content { margin-top: 1rem; } .new-post-btn { background: #5cb85c; padding: 0.5rem 1rem; border-radius: 5px; } .post-form .form-group { margin-bottom: 1rem; } .post-form label { display: block; margin-bottom: 0.5rem; } .post-form input, .post-form textarea { width: 100%; padding: 0.5rem; } .post-form button { background: #337ab7; color: #fff; padding: 0.7rem 1.5rem; border: none; cursor: pointer; } footer p { text-align: center; }
5. 메인 애플리케이션 로직 작성
마지막으로 프로젝트 루트 디렉토리에 main.py
파일을 만듭니다. 이 파일은 전체 애플리케이션의 진입점이며 데이터베이스, 라우팅 및 템플릿 로직을 통합합니다.
# main.py import uuid from contextlib import asynccontextmanager from fastapi import FastAPI, Request, Depends, Form from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from sqlmodel import Session, select from database import create_db_and_tables, get_session from models import Post # 애플리케이션 시작 시 데이터베이스 테이블 생성 @asynccontextmanager async def lifespan(app: FastAPI): print("Creating tables..") create_db_and_tables() yield app = FastAPI(lifespan=lifespan) # 정적 파일 디렉토리 마운트 app.mount("/static", StaticFiles(directory="public"), name="static") # Jinja2 템플릿 디렉토리 설정 templates = Jinja2Templates(directory="templates") @app.get("/", response_class=HTMLResponse) def root(): # 루트 경로를 게시물 목록으로 리디렉션 return RedirectResponse(url="/posts", status_code=302) @app.get("/posts", response_class=HTMLResponse) def get_all_posts(request: Request, session: Session = Depends(get_session)): # 모든 게시물을 생성 시간 역순으로 쿼리 statement = select(Post).order_by(Post.createdAt.desc()) posts = session.exec(statement).all() return templates.TemplateResponse("index.html", {"request": request, "posts": posts}) @app.get("/posts/new", response_class=HTMLResponse) def new_post_form(request: Request): # 새 게시물 작성을 위한 폼 페이지 표시 return templates.TemplateResponse("new-post.html", {"request": request, "title": "New Post"}) @app.post("/posts", response_class=HTMLResponse) def create_post( title: str = Form(...), content: str = Form(...), session: Session = Depends(get_session) ): # 폼에서 데이터를 받아 새 게시물 생성 new_post = Post(title=title, content=content) session.add(new_post) session.commit() return RedirectResponse(url="/posts", status_code=302) @app.get("/posts/{post_id}", response_class=HTMLResponse) def get_post_by_id(request: Request, post_id: uuid.UUID, session: Session = Depends(get_session)): # ID별 단일 게시물 쿼리 및 표시 post = session.get(Post, post_id) return templates.TemplateResponse("post.html", {"request": request, "post": post, "title": post.title})
6. 블로그 실행
이 시점에서 블로그 개발이 완료되었습니다. 블로그를 시작하려면 터미널에서 다음 명령을 실행하십시오:
uvicorn main:app --reload
--reload
매개변수는 코드를 수정할 때 서버를 자동으로 다시 시작하여 개발 및 디버깅에 편리합니다.
브라우저에서 http://localhost:8000
을 열어 블로그 홈페이지를 확인하십시오. 새 게시물을 만들어 모든 기능을 테스트할 수 있습니다!
7. 블로그 온라인 배포
이제 제가 만든 웹사이트를 다른 사람들에게 보여주어 모두가 접근할 수 있도록 하려면 어떻게 해야 할지 생각하고 있을 것입니다.
이전에 데이터베이스를 만드는 데 사용했던 Leapcell을 기억하십니까? Leapcell은 데이터베이스 생성 외에도 FastAPI를 포함하여 다양한 언어와 프레임워크의 프로젝트를 호스팅할 수 있는 웹 앱 호스팅 플랫폼입니다.
다음 단계를 따르십시오:
- 프로젝트를 GitHub에 커밋하십시오. 자세한 내용은 GitHub 공식 문서를 참조하십시오. Leapcell은 나중에 GitHub 저장소에서 코드를 가져옵니다.
- Leapcell 페이지에서 "서비스 생성"을 클릭합니다.
- FastAPI 저장소를 선택하면 Leapcell이 필요한 구성을 자동으로 채웁니다.
- 여전히 환경 변수를 직접 채워야 합니다.
.env
파일과 동일한 값으로DATABASE_URL
을 설정합니다. - 배포 홈페이지. 여기서 Leapcell이 도메인을 제공했음을 알 수 있습니다. 이것이 블로그의 온라인 주소입니다.
이제 이 링크를 친구들과 공유하면 모두가 온라인에서 블로그를 볼 수 있습니다!