Skip to content

FastAPI 살펴보기

🗺️ 전체 학습 로드맵

📚 목차별 상세 내용

📘 1장: FastAPI 시작하기

1.4 FastAPI 설치 및 프로젝트 생성

🎯 첫 번째 FastAPI 앱 만들기

  1. 터미널 열기
  2. Windows: PowerShell
  3. Mac: Terminal

  4. 프로젝트 폴더 만들기

mkdir my-first-api
cd my-first-api
  1. 가상환경 만들기 (프로젝트별 독립 공간)
python -m venv venv

# Windows에서 활성화
venv\Scripts\activate

# Mac/Linux에서 활성화
source venv/bin/activate
  1. FastAPI 설치하기
pip install fastapi
pip install "uvicorn[standard]"
  1. 첫 번째 코드 작성 (main.py)
from fastapi import FastAPI

# FastAPI 앱 만들기
app = FastAPI()

# 첫 번째 API 엔드포인트
@app.get("/")
def read_root():
    return {"message": "안녕하세요! FastAPI입니다!"}
  1. 서버 실행하기
uvicorn main:app --reload
  • 브라우저에서 http://localhost:8000 접속
  • API 문서는 http://localhost:8000/docs

📗 2장: FastAPI로 기본 API 만들기

2.1 기본 라우팅

🎯 라우팅이란?

  • URL 주소에 따라 다른 함수를 실행하는 것
  • 예: / → 홈페이지, /about → 소개 페이지

📝 실습 코드

from fastapi import FastAPI

app = FastAPI()

# GET 요청: 데이터 가져오기
@app.get("/")
def home():
    return {"message": "홈페이지입니다"}

@app.get("/about")
def about():
    return {"message": "소개 페이지입니다"}

# POST 요청: 데이터 보내기
@app.post("/create")
def create():
    return {"message": "새로운 데이터를 만들었습니다"}

💡 HTTP 메서드 이해하기 - GET: 데이터 조회 (읽기) - POST: 데이터 생성 (쓰기) - PUT: 데이터 수정 (업데이트) - DELETE: 데이터 삭제 (지우기)

2.2 매개변수 라우팅

🎯 URL에서 값 받기

  • URL의 일부를 변수로 사용
  • 예: /users/1 → 1번 사용자 정보

📝 실습 코드

# 숫자 매개변수
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id, "name": f"사용자{user_id}"}

# 문자열 매개변수
@app.get("/blog/{category}")
def get_blog_by_category(category: str):
    return {"category": category, "posts": f"{category} 관련 글들"}

# 여러 개의 매개변수
@app.get("/blog/{year}/{month}")
def get_blog_archive(year: int, month: int):
    return {"year": year, "month": month, "posts": f"{year}{month}월 글들"}

💡 타입 힌트의 장점 - user_id: int → 자동으로 숫자로 변환 - 잘못된 타입이 오면 에러 메시지 자동 생성

2.3 요청 본문 처리

🎯 POST 요청으로 데이터 받기

  • 사용자가 보낸 데이터를 처리
  • Pydantic으로 데이터 모델 정의

📝 실습 코드

from pydantic import BaseModel

# 데이터 모델 정의 (설계도)
class User(BaseModel):
    name: str
    email: str
    age: int = 0  # 기본값 설정 가능

# 회원가입 API
@app.post("/signup")
def signup(user: User):
    # user.name, user.email 등으로 접근 가능
    return {
        "message": f"{user.name}님 환영합니다!",
        "user_info": user
    }

# 블로그 글 작성 모델
class BlogPost(BaseModel):
    title: str
    content: str
    tags: list[str] = []  # 태그는 선택사항

@app.post("/blog/create")
def create_blog(post: BlogPost):
    return {
        "message": "글이 작성되었습니다",
        "post": post
    }

💡 Pydantic의 장점

  • 자동으로 데이터 검증
  • 타입이 맞지 않으면 친절한 에러 메시지
  • 자동으로 JSON ↔ Python 객체 변환

2.4 쿼리 매개변수 처리

🎯 URL 뒤의 ? 파라미터

  • 예: /search?q=python&limit=10
  • 선택적인 파라미터 처리

📝 실습 코드

# 기본 쿼리 파라미터
@app.get("/search")
def search(q: str = None):
    if q:
        return {"query": q, "results": f"{q}에 대한 검색 결과"}
    return {"message": "검색어를 입력하세요"}

# 페이지네이션 (페이지 나누기)
@app.get("/posts")
def get_posts(page: int = 1, size: int = 10):
    start = (page - 1) * size
    end = start + size
    return {
        "page": page,
        "size": size,
        "posts": f"{start}번째부터 {end}번째 글"
    }

# 여러 개의 쿼리 파라미터
@app.get("/filter")
def filter_items(
    category: str = None,
    min_price: int = 0,
    max_price: int = 1000000
):
    return {
        "category": category,
        "price_range": f"{min_price}원 ~ {max_price}원"
    }

2.5 응답 모델 사용하기

🎯 API 응답 형식 정의

  • 클라이언트에게 보낼 데이터 형식 지정
  • 민감한 정보 숨기기

📝 실습 코드

# 사용자 모델 (전체 정보)
class UserFull(BaseModel):
    id: int
    name: str
    email: str
    password: str  # 민감한 정보

# 응답용 모델 (공개 정보만)
class UserPublic(BaseModel):
    id: int
    name: str
    email: str
    # password는 제외!

# response_model로 응답 형식 지정
@app.post("/users/", response_model=UserPublic)
def create_user(user: UserFull):
    # 실제로는 DB에 저장
    return user  # password가 있어도 응답에서는 제외됨

# 목록 응답
@app.get("/users/", response_model=list[UserPublic])
def get_users():
    # 실제로는 DB에서 조회
    users = [
        {"id": 1, "name": "홍길동", "email": "hong@example.com", "password": "secret"},
        {"id": 2, "name": "김철수", "email": "kim@example.com", "password": "hidden"}
    ]
    return users  # password는 자동으로 제외

📙 3장: CRUD 애플리케이션 만들기

3.1 CRUD 구현하기

🎯 CRUD란?

  • Create: 생성 (새 데이터 만들기)
  • Read: 읽기 (데이터 조회하기)
  • Update: 수정 (데이터 바꾸기)
  • Delete: 삭제 (데이터 지우기)

📝 간단한 메모 앱 만들기

from typing import Dict

# 메모 데이터 모델
class Memo(BaseModel):
    title: str
    content: str

# 메모 저장소 (임시로 메모리에 저장)
memos: Dict[int, Memo] = {}
memo_counter = 0

# 1. Create - 메모 생성
@app.post("/memos/", response_model=dict)
def create_memo(memo: Memo):
    global memo_counter
    memo_counter += 1
    memos[memo_counter] = memo
    return {
        "id": memo_counter,
        "message": "메모가 생성되었습니다",
        "memo": memo
    }

# 2. Read - 전체 메모 조회
@app.get("/memos/")
def get_all_memos():
    return {
        "count": len(memos),
        "memos": [
            {"id": id, "memo": memo} 
            for id, memo in memos.items()
        ]
    }

# 3. Read - 특정 메모 조회
@app.get("/memos/{memo_id}")
def get_memo(memo_id: int):
    if memo_id not in memos:
        raise HTTPException(status_code=404, detail="메모를 찾을 수 없습니다")
    return {"id": memo_id, "memo": memos[memo_id]}

# 4. Update - 메모 수정
@app.put("/memos/{memo_id}")
def update_memo(memo_id: int, memo: Memo):
    if memo_id not in memos:
        raise HTTPException(status_code=404, detail="메모를 찾을 수 없습니다")
    memos[memo_id] = memo
    return {"message": "메모가 수정되었습니다", "memo": memo}

# 5. Delete - 메모 삭제
@app.delete("/memos/{memo_id}")
def delete_memo(memo_id: int):
    if memo_id not in memos:
        raise HTTPException(status_code=404, detail="메모를 찾을 수 없습니다")
    deleted_memo = memos.pop(memo_id)
    return {"message": "메모가 삭제되었습니다", "deleted": deleted_memo}

3.2 API 문서 살펴보기

🎯 자동 생성되는 API 문서

  • FastAPI의 킬러 기능!
  • 코드만 작성하면 문서는 자동 생성

📝 API 문서 활용하기

  1. Swagger UI (대화형 문서)
  2. http://localhost:8000/docs
  3. API를 직접 테스트 가능
  4. Try it out 버튼으로 실행

  5. ReDoc (읽기 전용 문서)

  6. http://localhost:8000/redoc
  7. 더 깔끔한 문서 형식

  8. 문서 커스터마이징

app = FastAPI(
    title="나의 메모 API",
    description="메모를 관리하는 간단한 API입니다",
    version="1.0.0",
)

# API 설명 추가
@app.post("/memos/", summary="새 메모 생성", tags=["memos"])
def create_memo(memo: Memo):
    """
    새로운 메모를 생성합니다.

    - **title**: 메모 제목
    - **content**: 메모 내용
    """
    # ... 코드 ...

📕 4장: 데이터베이스와 인증

4.1 SQLite와 SQLAlchemy

🎯 데이터베이스란?

  • 데이터를 영구적으로 저장하는 창고
  • SQLite = 가장 간단한 데이터베이스 (파일 하나)

📝 데이터베이스 설정

# database.py
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime

# 데이터베이스 연결 설정
DATABASE_URL = "sqlite:///./blog.db"  # blog.db 파일에 저장

# 엔진 생성
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)

# 모델의 부모 클래스
Base = declarative_base()

# 블로그 글 테이블 정의
class BlogPost(Base):
    __tablename__ = "blog_posts"  # 테이블 이름

    id = Column(Integer, primary_key=True, index=True)  # 고유 번호
    title = Column(String(100), nullable=False)  # 제목 (필수)
    content = Column(String(1000), nullable=False)  # 내용 (필수)
    author = Column(String(50), nullable=False)  # 작성자
    created_at = Column(DateTime, default=datetime.now)  # 작성일

# 테이블 생성
Base.metadata.create_all(bind=engine)

4.2 FastAPI에 DB 적용하기

🎯 데이터베이스 연동

  • 의존성 주입으로 DB 연결 관리
  • 자동으로 연결 열고 닫기

📝 DB와 연동된 CRUD

from sqlalchemy.orm import Session

# DB 세션 가져오기
def get_db():
    db = SessionLocal()
    try:
        yield db  # DB 연결 제공
    finally:
        db.close()  # 자동으로 연결 종료

# Pydantic 모델 (API용)
class BlogPostCreate(BaseModel):
    title: str
    content: str
    author: str

class BlogPostResponse(BaseModel):
    id: int
    title: str
    content: str
    author: str
    created_at: datetime

    class Config:
        orm_mode = True  # SQLAlchemy 모델과 호환

# 블로그 글 작성
@app.post("/blog/", response_model=BlogPostResponse)
def create_blog_post(post: BlogPostCreate, db: Session = Depends(get_db)):
    # DB 모델 생성
    db_post = BlogPost(**post.dict())

    # DB에 저장
    db.add(db_post)
    db.commit()
    db.refresh(db_post)  # 생성된 ID 등 가져오기

    return db_post

# 블로그 글 목록
@app.get("/blog/", response_model=list[BlogPostResponse])
def get_blog_posts(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    posts = db.query(BlogPost).offset(skip).limit(limit).all()
    return posts

# 특정 블로그 글 조회
@app.get("/blog/{post_id}", response_model=BlogPostResponse)
def get_blog_post(post_id: int, db: Session = Depends(get_db)):
    post = db.query(BlogPost).filter(BlogPost.id == post_id).first()
    if not post:
        raise HTTPException(status_code=404, detail="글을 찾을 수 없습니다")
    return post

4.3 JWT 토큰이란?

🎯 JWT (JSON Web Token)

  • 사용자 인증을 위한 토큰
  • 마치 놀이공원 자유이용권 같은 것

💡 JWT의 구조

헤더.페이로드.서명
  • 헤더: 토큰 타입과 암호화 방식
  • 페이로드: 사용자 정보 (이름, 권한 등)
  • 서명: 위조 방지용 서명

🔐 인증 과정

  1. 로그인 → JWT 토큰 발급
  2. API 요청시 토큰 첨부
  3. 서버가 토큰 검증
  4. 올바른 토큰이면 요청 처리

4.4 JWT를 이용한 사용자 인증 구현

📝 간단한 로그인 시스템

from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext

# 비밀번호 암호화 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# JWT 설정
SECRET_KEY = "your-secret-key-change-this"  # 실제로는 환경변수로!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# 사용자 모델
class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    username = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)
    hashed_password = Column(String)

# 회원가입 모델
class UserCreate(BaseModel):
    username: str
    email: str
    password: str

# 로그인 모델
class UserLogin(BaseModel):
    username: str
    password: str

# 비밀번호 암호화
def get_password_hash(password: str):
    return pwd_context.hash(password)

# 비밀번호 검증
def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)

# JWT 토큰 생성
def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# 회원가입
@app.post("/signup")
def signup(user: UserCreate, db: Session = Depends(get_db)):
    # 중복 확인
    db_user = db.query(User).filter(User.username == user.username).first()
    if db_user:
        raise HTTPException(status_code=400, detail="이미 사용중인 아이디입니다")

    # 사용자 생성
    hashed_password = get_password_hash(user.password)
    db_user = User(
        username=user.username,
        email=user.email,
        hashed_password=hashed_password
    )
    db.add(db_user)
    db.commit()

    return {"message": "회원가입이 완료되었습니다"}

# 로그인
@app.post("/login")
def login(user: UserLogin, db: Session = Depends(get_db)):
    # 사용자 찾기
    db_user = db.query(User).filter(User.username == user.username).first()
    if not db_user:
        raise HTTPException(status_code=400, detail="아이디 또는 비밀번호가 틀렸습니다")

    # 비밀번호 확인
    if not verify_password(user.password, db_user.hashed_password):
        raise HTTPException(status_code=400, detail="아이디 또는 비밀번호가 틀렸습니다")

    # 토큰 생성
    access_token = create_access_token(data={"sub": db_user.username})
    return {"access_token": access_token, "token_type": "bearer"}

📒 5장: 블로그 기능 구현

5.1 요구사항 분석

🎯 우리가 만들 블로그 시스템

  • 회원가입/로그인
  • 글 작성/수정/삭제 (본인 글만)
  • 글 목록/상세 보기
  • 웹 페이지 제공

5.2 CORS 설정과 미들웨어 구현

🎯 CORS란?

  • Cross-Origin Resource Sharing
  • 다른 도메인에서의 API 호출 허용

📝 CORS 설정

from fastapi.middleware.cors import CORSMiddleware

# CORS 미들웨어 추가
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 모든 도메인 허용 (개발용)
    allow_credentials=True,
    allow_methods=["*"],  # 모든 HTTP 메서드 허용
    allow_headers=["*"],  # 모든 헤더 허용
)

5.3 SPA로 블로그 구현하기

🎯 SPA (Single Page Application)

  • 한 페이지에서 모든 기능 동작
  • JavaScript로 동적으로 화면 변경

📝 간단한 SPA 예제

<!-- static/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>나의 블로그</title>
    <style>
        .container { max-width: 800px; margin: 0 auto; padding: 20px; }
        .post { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
        .button { padding: 10px 20px; margin: 5px; cursor: pointer; }
    </style>
</head>
<body>
    <div class="container">
        <h1>나의 블로그</h1>

        <!-- 로그인 폼 -->
        <div id="loginForm">
            <h2>로그인</h2>
            <input type="text" id="username" placeholder="아이디">
            <input type="password" id="password" placeholder="비밀번호">
            <button class="button" onclick="login()">로그인</button>
        </div>

        <!-- 글 목록 -->
        <div id="postList"></div>

        <!-- 글 작성 폼 -->
        <div id="writeForm" style="display:none;">
            <h2>새 글 작성</h2>
            <input type="text" id="title" placeholder="제목">
            <textarea id="content" placeholder="내용"></textarea>
            <button class="button" onclick="createPost()">작성</button>
        </div>
    </div>

    <script>
    let token = localStorage.getItem('token');

    // 로그인
    async function login() {
        const username = document.getElementById('username').value;
        const password = document.getElementById('password').value;

        const response = await fetch('/login', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({username, password})
        });

        if (response.ok) {
            const data = await response.json();
            token = data.access_token;
            localStorage.setItem('token', token);
            document.getElementById('loginForm').style.display = 'none';
            document.getElementById('writeForm').style.display = 'block';
            loadPosts();
        } else {
            alert('로그인 실패!');
        }
    }

    // 글 목록 불러오기
    async function loadPosts() {
        const response = await fetch('/blog/');
        const posts = await response.json();

        let html = '<h2>글 목록</h2>';
        posts.forEach(post => {
            html += `
                <div class="post">
                    <h3>${post.title}</h3>
                    <p>${post.content}</p>
                    <small>작성자: ${post.author}</small>
                </div>
            `;
        });

        document.getElementById('postList').innerHTML = html;
    }

    // 글 작성
    async function createPost() {
        const title = document.getElementById('title').value;
        const content = document.getElementById('content').value;

        const response = await fetch('/blog/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}`
            },
            body: JSON.stringify({title, content})
        });

        if (response.ok) {
            alert('글이 작성되었습니다!');
            loadPosts();
        }
    }

    // 페이지 로드시 글 목록 표시
    loadPosts();
    </script>
</body>
</html>

5.4 MPA로 블로그 구현하기

🎯 MPA (Multi Page Application)

  • 여러 개의 HTML 페이지
  • 서버에서 HTML 생성 (템플릿 엔진)

📝 Jinja2 템플릿 사용

from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles

# 템플릿 설정
templates = Jinja2Templates(directory="templates")

# 정적 파일 설정
app.mount("/static", StaticFiles(directory="static"), name="static")

# 블로그 목록 페이지
@app.get("/")
def home(request: Request, db: Session = Depends(get_db)):
    posts = db.query(BlogPost).all()
    return templates.TemplateResponse("index.html", {
        "request": request,
        "posts": posts
    })

템플릿 예제 (templates/index.html)

<!DOCTYPE html>
<html>
<head>
    <title>나의 블로그</title>
</head>
<body>
    <h1>블로그 글 목록</h1>
    {% for post in posts %}
        <div class="post">
            <h2>{{ post.title }}</h2>
            <p>{{ post.content }}</p>
            <small>작성자: {{ post.author }}</small>
        </div>
    {% endfor %}
</body>
</html>

5.5 DB 구성 및 static 파일 서빙

🎯 프로젝트 구조 정리

my-blog/
├── main.py              # FastAPI 메인 파일
├── database.py          # DB 설정
├── models.py            # DB 모델
├── schemas.py           # Pydantic 모델
├── auth.py              # 인증 관련
├── static/              # CSS, JS, 이미지
│   ├── style.css
│   └── script.js
├── templates/           # HTML 템플릿
│   ├── base.html
│   ├── index.html
│   └── post_detail.html
└── blog.db              # SQLite DB 파일

5.6 인증 구현하기

🎯 로그인이 필요한 기능 보호

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

# OAuth2 설정
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")

# 현재 사용자 가져오기
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    try:
        # 토큰 해독
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="인증 실패")
    except JWTError:
        raise HTTPException(status_code=401, detail="인증 실패")

    # 사용자 조회
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise HTTPException(status_code=401, detail="사용자를 찾을 수 없습니다")

    return user

# 보호된 엔드포인트 (로그인 필요)
@app.post("/blog/")
def create_post(
    post: BlogPostCreate,
    current_user: User = Depends(get_current_user),  # 로그인 확인
    db: Session = Depends(get_db)
):
    db_post = BlogPost(
        **post.dict(),
        author=current_user.username  # 현재 사용자를 작성자로
    )
    db.add(db_post)
    db.commit()
    return {"message": "글이 작성되었습니다"}

# 본인 글만 수정 가능
@app.put("/blog/{post_id}")
def update_post(
    post_id: int,
    post: BlogPostCreate,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    db_post = db.query(BlogPost).filter(BlogPost.id == post_id).first()
    if not db_post:
        raise HTTPException(status_code=404, detail="글을 찾을 수 없습니다")

    # 작성자 확인
    if db_post.author != current_user.username:
        raise HTTPException(status_code=403, detail="수정 권한이 없습니다")

    # 수정
    db_post.title = post.title
    db_post.content = post.content
    db.commit()

    return {"message": "글이 수정되었습니다"}

5.7 스웨거로 테스트하기

🎯 Swagger UI 활용법

  1. http://localhost:8000/docs 접속
  2. Authorize 버튼 클릭
  3. 로그인해서 받은 토큰 입력
  4. 각 API 테스트

💡 테스트 순서

  1. /signup - 회원가입
  2. /login - 로그인 (토큰 받기)
  3. Authorize에 토큰 설정
  4. /blog/ POST - 글 작성
  5. /blog/ GET - 글 목록 확인

📓 6장: FastAPI 실전 활용

6.1 에러 처리와 예외 관리

🎯 친절한 에러 메시지

# 커스텀 예외 만들기
class BlogException(Exception):
    def __init__(self, name: str, message: str):
        self.name = name
        self.message = message

# 예외 처리기
@app.exception_handler(BlogException)
async def blog_exception_handler(request: Request, exc: BlogException):
    return JSONResponse(
        status_code=400,
        content={
            "error": exc.name,
            "message": exc.message
        }
    )

# 사용 예시
@app.get("/blog/{post_id}")
def get_post(post_id: int, db: Session = Depends(get_db)):
    if post_id < 1:
        raise BlogException("INVALID_ID", "올바른 글 번호가 아닙니다")

    post = db.query(BlogPost).filter(BlogPost.id == post_id).first()
    if not post:
        raise BlogException("NOT_FOUND", f"{post_id}번 글을 찾을 수 없습니다")

    return post

6.2 파일 업로드 및 처리

🎯 이미지 업로드 기능

from fastapi import File, UploadFile
import shutil
import os

# 업로드 폴더 생성
os.makedirs("uploads", exist_ok=True)

# 파일 업로드 API
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
    # 파일 크기 확인 (5MB 제한)
    contents = await file.read()
    if len(contents) > 5 * 1024 * 1024:
        raise HTTPException(status_code=413, detail="파일이 너무 큽니다 (최대 5MB)")

    # 파일 확장자 확인
    allowed_extensions = ["jpg", "jpeg", "png", "gif"]
    file_extension = file.filename.split(".")[-1].lower()
    if file_extension not in allowed_extensions:
        raise HTTPException(status_code=400, detail="이미지 파일만 업로드 가능합니다")

    # 파일 저장
    file_path = f"uploads/{file.filename}"
    with open(file_path, "wb") as f:
        f.write(contents)

    return {
        "filename": file.filename,
        "size": len(contents),
        "url": f"/uploads/{file.filename}"
    }

# 업로드된 파일 제공
from fastapi.responses import FileResponse

@app.get("/uploads/{filename}")
async def get_uploaded_file(filename: str):
    file_path = f"uploads/{filename}"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="파일을 찾을 수 없습니다")
    return FileResponse(file_path)

6.3 패키지 관리

🎯 프로젝트 의존성 관리

requirements.txt 만들기

# 현재 설치된 패키지 목록 저장
pip freeze > requirements.txt

requirements.txt 예시

fastapi
uvicorn[standard]
sqlalchemy
python-jose[cryptography]
passlib[bcrypt]
python-multipart
jinja2

다른 컴퓨터에서 설치

pip install -r requirements.txt

.gitignore 파일

# 가상환경
venv/
env/

# 데이터베이스
*.db

# 캐시
__pycache__/
*.py[cod]

# 환경 설정
.env

# 업로드 파일
uploads/

# IDE
.vscode/
.idea/

🎓 학습 완료 후 만들 수 있는 것들

✅ 완성 프로젝트: 나만의 블로그

  • 회원가입/로그인 시스템
  • 글 작성/수정/삭제 (CRUD)
  • 이미지 업로드
  • 인증된 사용자만 글 작성
  • 자동 생성되는 API 문서

🚀 다음 단계로 발전시키기

  1. 댓글 기능 추가하기
  2. 좋아요 기능 만들기
  3. 검색 기능 구현하기
  4. 카테고리 분류 추가하기
  5. 관리자 페이지 만들기

💡 학습 팁

초보자를 위한 조언

  1. 에러 메시지를 무서워하지 마세요
  2. 에러는 뭐가 잘못됐는지 알려주는 친구입니다
  3. 구글에 에러 메시지를 검색하면 해결책이 나옵니다

  4. 코드를 복사-붙여넣기만 하지 마세요

  5. 직접 타이핑하면서 익숙해지세요
  6. 작은 부분씩 바꿔보면서 실험해보세요

  7. 문서를 활용하세요

  8. FastAPI 문서는 매우 친절합니다
  9. /docs에서 API를 테스트해보세요

  10. 작게 시작하세요

  11. 한 번에 모든 걸 만들려고 하지 마세요
  12. 작은 기능부터 하나씩 추가하세요

🔗 유용한 링크

🎉 축하합니다!

FastAPI 베이스캠프를 완주하신 것을 축하합니다! 이제 여러분은: - ✅ RESTful API를 만들 수 있습니다 - ✅ 데이터베이스를 다룰 수 있습니다 - ✅ 사용자 인증을 구현할 수 있습니다 - ✅ 파일 업로드를 처리할 수 있습니다

다음 프로젝트는 무엇을 만들어 보시겠어요? 🚀

Comments