๐Ÿ”ฅFastAPI MySQL ์—ฐ๋™ ๋ฐ CRUD๊นŒ์ง€

๐Ÿ•’ ์•ฝ 3๋ถ„ ์ฝ๋Š” ๋ฐ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค

์ด์ „ ๊ธ€ ๋ณด๊ธฐ(FastAPI ๋ณด์•ˆ OAuth2, JWT)

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด, ๊ฒŒ์‹œ๊ธ€, ๋Œ“๊ธ€, ์ƒํ’ˆ ๋“ฑ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์ธ DB ์—ฐ๋™์ด ํ•„์ˆ˜๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” FastAPI์—์„œ MySQL๊ณผ SQLAlchemy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๋™ํ•˜๊ณ , CRUD(์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ) ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐจ๊ทผ์ฐจ๊ทผ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ Windows, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” MySQL, ORM์€ SQLAlchemy๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.


โš™๏ธ ํ™˜๊ฒฝ ์„ค์ •ํ•˜๊ธฐ

๋จผ์ € ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„(cmd ํ˜น์€ PowerShell)์—์„œ ์‹คํ–‰ํ•˜์ž.

pip install fastapi[all] sqlalchemy pymysql
  • sqlalchemy: ORM์„ ์œ„ํ•œ ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • pymysql: MySQL ๋“œ๋ผ์ด๋ฒ„ ์—ญํ• 
  • fastapi[all]: FastAPI์™€ ํ•จ๊ป˜ Uvicorn ๋“ฑ ํ•„์š”ํ•œ ์ข…์†์„ฑ ํฌํ•จ

MySQL ์„œ๋ฒ„๋Š” ๋กœ์ปฌ ํ˜น์€ ์›๊ฒฉ์— ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, user, password, database, host, port ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.


๐Ÿงฉ DB ์—ฐ๊ฒฐ ์„ค์ • – database.py

๋‹ค์Œ์€ SQLAlchemy์˜ ์—”์ง„๊ณผ ์„ธ์…˜์„ ์„ค์ •ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

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

# MySQL ์ ‘์† ๋ฌธ์ž์—ด
DATABASE_URL = "mysql+pymysql://username:password@localhost:3306/testdb"

engine = create_engine(DATABASE_URL, echo=True)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

์—ฌ๊ธฐ์„œ username, password, testdb๋Š” ๊ฐ์ž ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ๋ฐ”๊พธ์–ด์•ผ ํ•œ๋‹ค. echo=True๋Š” SQL ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•ด์ค˜์„œ ๋””๋ฒ„๊น…์— ์œ ์šฉํ•˜๋‹ค.


๐Ÿ“ฆ ๋ชจ๋ธ ์ •์˜ – models.py

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ํด๋ž˜์Šค๋ฅผ SQLAlchemy๋กœ ์ •์˜ํ•˜์ž.

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

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True, nullable=False)

์ด์ œ User ๋ชจ๋ธ์€ users๋ผ๋Š” ํ…Œ์ด๋ธ”๋กœ ๋ณ€ํ™˜๋˜์–ด MySQL์— ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿš€ ์ดˆ๊ธฐํ™” ๋ฐ DB ํ…Œ์ด๋ธ” ์ƒ์„ฑ

main.py ๋˜๋Š” ๋ณ„๋„์˜ ์ดˆ๊ธฐํ™” ์Šคํฌ๋ฆฝํŠธ์—์„œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜์ž.

from database import engine
from models import Base

Base.metadata.create_all(bind=engine)

์ด ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด MySQL์˜ testdb ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— users ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค.


๐Ÿ”จ CRUD ๊ตฌํ˜„ – crud.py

ORM์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ํ•จ์ˆ˜๋“ค์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

from sqlalchemy.orm import Session
from models import User

def create_user(db: Session, name: str, email: str):
    db_user = User(name=name, email=email)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

def get_user(db: Session, user_id: int):
    return db.query(User).filter(User.id == user_id).first()

def get_users(db: Session, skip: int = 0, limit: int = 10):
    return db.query(User).offset(skip).limit(limit).all()

def update_user(db: Session, user_id: int, name: str):
    user = db.query(User).filter(User.id == user_id).first()
    if user:
        user.name = name
        db.commit()
        db.refresh(user)
    return user

def delete_user(db: Session, user_id: int):
    user = db.query(User).filter(User.id == user_id).first()
    if user:
        db.delete(user)
        db.commit()
    return user

์ด์ œ ๊ธฐ๋ณธ์ ์ธ ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ์ž‘์—…์€ ๋ชจ๋‘ ์ค€๋น„๋˜์—ˆ๋‹ค.


๐ŸŒ FastAPI์™€ ์—ฐ๊ฒฐ – main.py

API ๋ผ์šฐํŒ…๊ณผ ์š”์ฒญ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import SessionLocal
from models import User
import crud
from pydantic import BaseModel

app = FastAPI()

# DB ์„ธ์…˜ ์ฃผ์ž…
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

๐Ÿ“ฌ ์œ ์ € ์ƒ์„ฑ

class UserCreate(BaseModel):
    name: str
    email: str

@app.post("/users/")
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    return crud.create_user(db, name=user.name, email=user.email)

๐Ÿ“ฅ ์œ ์ € ๋ชฉ๋ก ์กฐํšŒ

@app.get("/users/")
def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    return crud.get_users(db, skip=skip, limit=limit)

๐Ÿ“ค ์œ ์ € ๋‹จ๊ฑด ์กฐํšŒ

@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
    db_user = crud.get_user(db, user_id)
    if db_user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return db_user

โœ๏ธ ์œ ์ € ์ˆ˜์ •

class UserUpdate(BaseModel):
    name: str

@app.put("/users/{user_id}")
def update_user(user_id: int, user: UserUpdate, db: Session = Depends(get_db)):
    return crud.update_user(db, user_id, user.name)

โŒ ์œ ์ € ์‚ญ์ œ

@app.delete("/users/{user_id}")
def delete_user(user_id: int, db: Session = Depends(get_db)):
    return crud.delete_user(db, user_id)

โšก ๋น„๋™๊ธฐ DB ์—ฐ๋™์— ๋Œ€ํ•ด ์ž ๊น

SQLAlchemy๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™๊ธฐ ๋ฐฉ์‹์ด๋‹ค. FastAPI๋Š” ๋น„๋™๊ธฐ ์›น ํ”„๋ ˆ์ž„์›Œํฌ์ด์ง€๋งŒ, SQLAlchemy์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ฃผ๋กœ ๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. ๋งŒ์•ฝ ๋น„๋™๊ธฐ DB ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, SQLModel + async SQLAlchemy, ํ˜น์€ Tortoise ORM ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ ค๋ฉด async def ๋ผ์šฐํŠธ์™€ await ํ˜ธ์ถœ์ด ํ•„์š”ํ•˜๊ณ , DB ์„ธ์…˜๋„ async_sessionmaker๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค. ์„ค์ •์ด ๋ณต์žกํ•ด์ง€๋Š” ๋Œ€์‹  ๋†’์€ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ์„ ๊ธฐ๋Œ€ํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ”š ์ •๋ฆฌํ•˜๋ฉฐ

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” Windows ํ™˜๊ฒฝ์—์„œ FastAPI์™€ MySQL์„ ์—ฐ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  SQLAlchemy ORM์„ ํ†ตํ•ด ํ…Œ์ด๋ธ” ์ƒ์„ฑ๋ถ€ํ„ฐ CRUD ๊ตฌํ˜„๊นŒ์ง€ ์ „ ๊ณผ์ •์„ ์•Œ์•„๋ณด์•˜๋‹ค. FastAPI์˜ ์žฅ์ ์€ ๊ทธ ์œ ์—ฐ์„ฑ๊ณผ ํ™•์žฅ์„ฑ์— ์žˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ๋˜ํ•œ ๊ทธ ์ค‘ ํ•˜๋‚˜๋‹ค. ํŠนํžˆ ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ, ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ, ORM ํ™œ์šฉ ๋Šฅ๋ ฅ์ด ์ค‘์š”ํ•˜๋ฏ€๋กœ, ๊ธฐ๋ณธ๊ธฐ๋ฅผ ํƒ„ํƒ„ํžˆ ๋‹ค์ง€๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅํ•˜๊ณ  ๋ถˆ๋Ÿฌ์˜ค๋ฉฐ, ์›น ์„œ๋น„์Šค์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” ์ธ์ฆ ์ฒ˜๋ฆฌ, ํŽ˜์ด์ง•, ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ๊นŒ์ง€ ํ™•์žฅํ•ด๋ณด์ž.

FastAPI ๊ณต์‹ ๋ฌธ์„œ : https://fastapi.tiangolo.com/ko/

fastapi-logo,fastapi-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ-์„ค์ •, fastapi-์•ฑ-๋งŒ๋“ค๊ธฐ, fastapi-๋ผ์šฐํŒ…, fastapi-request-response, fastapi-์˜ˆ์™ธ-์ฒ˜๋ฆฌ, fastapi-mysql-์—ฐ๋™