DEV Community

DevCorner2
DevCorner2

Posted on

πŸš€ FastAPI CRUD with MySQL/MariaDB β€” Enterprise-Grade Architecture

🧠 What You'll Learn

  • πŸ—οΈ How to structure a real-world FastAPI project
  • πŸ›’οΈ Use MySQL/MariaDB with SQLAlchemy ORM
  • 🧾 Perform full CRUD operations
  • πŸ“¦ Handle exceptions globally
  • βœ… Standardize all HTTP responses
  • 🐍 Use .env config, dependency injection, and clean code practices

πŸ“ Folder Structure (Production-Ready)

fastapi_crud_app/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   └── v1/
β”‚   β”‚       β”œβ”€β”€ __init__.py
β”‚   β”‚       └── routes/
β”‚   β”‚           └── item.py
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   └── config.py
β”‚   β”œβ”€β”€ crud/
β”‚   β”‚   └── item.py
β”‚   β”œβ”€β”€ db/
β”‚   β”‚   β”œβ”€β”€ base.py
β”‚   β”‚   β”œβ”€β”€ base_class.py
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   └── item.py
β”‚   β”‚   └── session.py
β”‚   β”œβ”€β”€ exceptions/
β”‚   β”‚   β”œβ”€β”€ custom_exceptions.py
β”‚   β”‚   └── handlers.py
β”‚   β”œβ”€β”€ schemas/
β”‚   β”‚   └── item.py
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   └── response.py
β”‚   └── main.py
β”œβ”€β”€ .env
β”œβ”€β”€ requirements.txt
└── README.md
Enter fullscreen mode Exit fullscreen mode

βš™οΈ Setup and Dependencies

βœ… Install

pip install fastapi uvicorn sqlalchemy pymysql python-dotenv
Enter fullscreen mode Exit fullscreen mode

βœ… .env

DATABASE_URL=mysql+pymysql://user:password@localhost:3306/fastapi_db
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Configuration – app/core/config.py

from dotenv import load_dotenv
import os

load_dotenv()

class Settings:
    DATABASE_URL: str = os.getenv("DATABASE_URL")

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

πŸ›’οΈ Database – app/db/session.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.config import settings

engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Base Class – app/db/base_class.py

from sqlalchemy.ext.declarative import as_declarative, declared_attr

@as_declarative()
class Base:
    id: int
    __name__: str

    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()
Enter fullscreen mode Exit fullscreen mode

πŸ“ Models – app/db/models/item.py

from sqlalchemy import Column, Integer, String
from app.db.base_class import Base

class Item(Base):
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(255), nullable=False)
    description = Column(String(255), nullable=True)
Enter fullscreen mode Exit fullscreen mode

🧬 Base Import – app/db/base.py

from app.db.models import item
from app.db.base_class import Base
Enter fullscreen mode Exit fullscreen mode

πŸ“‘ Schemas – app/schemas/item.py

from pydantic import BaseModel

class ItemBase(BaseModel):
    title: str
    description: str

class ItemCreate(ItemBase):
    pass

class ItemUpdate(ItemBase):
    pass

class ItemOut(ItemBase):
    id: int

    class Config:
        orm_mode = True
Enter fullscreen mode Exit fullscreen mode

πŸ› οΈ CRUD Logic – app/crud/item.py

from sqlalchemy.orm import Session
from app.db.models.item import Item
from app.schemas.item import ItemCreate, ItemUpdate

def create_item(db: Session, item: ItemCreate):
    db_item = Item(**item.dict())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

def get_item(db: Session, item_id: int):
    return db.query(Item).filter(Item.id == item_id).first()

def get_items(db: Session, skip=0, limit=100):
    return db.query(Item).offset(skip).limit(limit).all()

def update_item(db: Session, item_id: int, item: ItemUpdate):
    db_item = get_item(db, item_id)
    if db_item:
        for key, value in item.dict().items():
            setattr(db_item, key, value)
        db.commit()
        db.refresh(db_item)
    return db_item

def delete_item(db: Session, item_id: int):
    db_item = get_item(db, item_id)
    if db_item:
        db.delete(db_item)
        db.commit()
    return db_item
Enter fullscreen mode Exit fullscreen mode

🌐 API Routes – app/api/v1/routes/item.py

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.schemas.item import ItemCreate, ItemOut, ItemUpdate
from app.crud import item as crud
from app.db.session import SessionLocal
from app.utils.response import APIResponse
from app.exceptions.custom_exceptions import NotFoundException

router = APIRouter()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/", response_model=APIResponse)
def create_item(data: ItemCreate, db: Session = Depends(get_db)):
    item = crud.create_item(db, data)
    return APIResponse(status="success", message="Item created", data=item)

@router.get("/", response_model=APIResponse)
def get_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    items = crud.get_items(db, skip, limit)
    return APIResponse(status="success", message="Item list", data=items)

@router.get("/{item_id}", response_model=APIResponse)
def get_item(item_id: int, db: Session = Depends(get_db)):
    item = crud.get_item(db, item_id)
    if not item:
        raise NotFoundException(name="Item")
    return APIResponse(status="success", message="Item found", data=item)

@router.put("/{item_id}", response_model=APIResponse)
def update_item(item_id: int, data: ItemUpdate, db: Session = Depends(get_db)):
    updated = crud.update_item(db, item_id, data)
    if not updated:
        raise NotFoundException(name="Item")
    return APIResponse(status="success", message="Item updated", data=updated)

@router.delete("/{item_id}", response_model=APIResponse)
def delete_item(item_id: int, db: Session = Depends(get_db)):
    deleted = crud.delete_item(db, item_id)
    if not deleted:
        raise NotFoundException(name="Item")
    return APIResponse(status="success", message="Item deleted", data=deleted)
Enter fullscreen mode Exit fullscreen mode

🚨 Custom Exceptions – app/exceptions/custom_exceptions.py

class NotFoundException(Exception):
    def __init__(self, name: str):
        self.name = name
Enter fullscreen mode Exit fullscreen mode

🌐 Global Exception Handler – app/exceptions/handlers.py

from fastapi import Request
from fastapi.responses import JSONResponse
from app.exceptions.custom_exceptions import NotFoundException

def not_found_exception_handler(request: Request, exc: NotFoundException):
    return JSONResponse(
        status_code=404,
        content={
            "status": "error",
            "message": f"{exc.name} not found",
            "data": None
        }
    )
Enter fullscreen mode Exit fullscreen mode

πŸ”„ Response Utility – app/utils/response.py

from typing import Optional, Any
from pydantic import BaseModel

class APIResponse(BaseModel):
    status: str
    message: str
    data: Optional[Any] = None
Enter fullscreen mode Exit fullscreen mode

πŸš€ Main App – app/main.py

from fastapi import FastAPI
from app.api.v1.routes import item
from app.db.base import Base
from app.db.session import engine
from app.exceptions.handlers import not_found_exception_handler
from app.exceptions.custom_exceptions import NotFoundException

app = FastAPI(title="FastAPI CRUD App")

# Register exception handlers
app.add_exception_handler(NotFoundException, not_found_exception_handler)

# Create tables
Base.metadata.create_all(bind=engine)

# Routes
app.include_router(item.router, prefix="/api/v1/items", tags=["Items"])
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Test It!

uvicorn app.main:app --reload
Enter fullscreen mode Exit fullscreen mode

Open docs: http://localhost:8000/docs


🧰 Ready for Production?

  • βœ… Add Alembic for migrations
  • βœ… Use Gunicorn + UvicornWorker
  • βœ… Add JWT Auth for secure APIs
  • βœ… Containerize with Docker
  • βœ… Use logging and monitoring tools

Top comments (0)