Welcome to the ultimate Python FastAPI async CRUD operations tutorial! Whether you’re a seasoned developer looking to modernize your API stack or a newcomer eager to build high‑performance web services, FastAPI’s asynchronous capabilities make it the perfect choice. In this guide, we’ll walk through everything you need to know—from setting up a FastAPI project to implementing fully asynchronous create, read, update, and delete (CRUD) endpoints backed by an async‑compatible database. By the end, you’ll have a production‑ready template that you can extend for any real‑world application.
Why Choose FastAPI for Async CRUD?
FastAPI has quickly become the go‑to framework for building RESTful APIs in Python, and for good reasons:
- Native async support: Built on top of Starlette, FastAPI lets you write
async defendpoints that run concurrently, dramatically improving throughput. - Automatic OpenAPI documentation: Swagger UI and ReDoc are generated out of the box, making your API self‑documenting.
- Type‑safety and validation: Pydantic models enforce data schemas, catching errors before they hit your database.
- Performance: Benchmarks show FastAPI rivals Node.js and Go for request latency.
Prerequisites
Before diving into code, make sure you have the following installed:
- Python 3.9 or newer
- Git (optional, for version control)
- A terminal or command prompt with
pipaccess
We’ll also use uvicorn as the ASGI server and SQLModel (a SQLAlchemy‑based ORM) for async database interactions.
Project Setup
1. Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows use: venv\Scripts\activate
2. Install dependencies
pip install fastapi uvicorn[standard] sqlmodel aiosqlite
3. Project structure
Organize your files for clarity and scalability:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ ├── schemas.py
│ └── crud.py
└── requirements.txt
Defining the Data Model
We’ll build a simple Item resource with id, name, description, and price. Using SQLModel gives us both Pydantic validation and SQLAlchemy ORM features.
app/models.py
from sqlmodel import SQLModel, Field
from typing import Optional
class Item(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, max_length=100)
description: Optional[str] = Field(default=None, max_length=255)
price: float
Creating Pydantic Schemas
Separate schemas for request payloads and response models keep your API clean.
app/schemas.py
from pydantic import BaseModel, Field
from typing import Optional
class ItemCreate(BaseModel):
name: str = Field(..., max_length=100)
description: Optional[str] = Field(None, max_length=255)
price: float = Field(..., gt=0)
class ItemRead(BaseModel):
id: int
name: str
description: Optional[str]
price: float
class ItemUpdate(BaseModel):
name: Optional[str] = Field(None, max_length=100)
description: Optional[str] = Field(None, max_length=255)
price: Optional[float] = Field(None, gt=0)
Async CRUD Functions
All database interactions are async, leveraging SQLModel’s async engine.
app/crud.py
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from .models import Item
from typing import List, Optional
async def get_item(session: AsyncSession, item_id: int) -> Optional[Item]:
result = await session.exec(select(Item).where(Item.id == item_id))
return result.first()
async def get_items(session: AsyncSession, skip: int = 0, limit: int = 100) -> List[Item]:
result = await session.exec(select(Item).offset(skip).limit(limit))
return result.all()
async def create_item(session: AsyncSession, item_data) -> Item:
db_item = Item.from_orm(item_data)
session.add(db_item)
await session.commit()
await session.refresh(db_item)
return db_item
async def update_item(session: AsyncSession, db_item: Item, updates) -> Item:
item_data = updates.dict(exclude_unset=True)
for key, value in item_data.items():
setattr(db_item, key, value)
session.add(db_item)
await session.commit()
await session.refresh(db_item)
return db_item
async def delete_item(session: AsyncSession, db_item: Item) -> None:
await session.delete(db_item)
await session.commit()
FastAPI Application Entry Point
app/main.py
import uvicorn
from fastapi import FastAPI, HTTPException, Depends, status
from sqlmodel import SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel.ext.asyncio.engine import create_async_engine
from .models import Item
from .schemas import ItemCreate, ItemRead, ItemUpdate
from . import crud
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
engine = create_async_engine(DATABASE_URL, echo=True)
app = FastAPI(
title="FastAPI Async CRUD Tutorial",
description="A step‑by‑step guide to building async CRUD endpoints with FastAPI and SQLModel.",
version="1.0.0"
)
# Dependency to get async DB session
async def get_session() -> AsyncSession:
async with AsyncSession(engine) as session:
yield session
@app.on_event("startup")
async def on_startup():
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
@app.post("/items/", response_model=ItemRead, status_code=status.HTTP_201_CREATED)
async def create_item_endpoint(item: ItemCreate, session: AsyncSession = Depends(get_session)):
return await crud.create_item(session, item)
@app.get("/items/", response_model=List[ItemRead])
async def read_items(skip: int = 0, limit: int = 100, session: AsyncSession = Depends(get_session)):
return await crud.get_items(session, skip=skip, limit=limit)
@app.get("/items/{item_id}", response_model=ItemRead)
async def read_item(item_id: int, session: AsyncSession = Depends(get_session)):
db_item = await crud.get_item(session, item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
@app.patch("/items/{item_id}", response_model=ItemRead)
async def update_item_endpoint(item_id: int, item: ItemUpdate, session: AsyncSession = Depends(get_session)):
db_item = await crud.get_item(session, item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
return await crud.update_item(session, db_item, item)
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item_endpoint(item_id: int, session: AsyncSession = Depends(get_session)):
db_item = await crud.get_item(session, item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
await crud.delete_item(session, db_item)
return None
if __name__ == "__main__":
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
Testing the API with HTTPie or cURL
Once the server is running (uvicorn app.main:app --reload), you can interact with the endpoints:
- Create an item:
http POST http://localhost:8000/items/ name="FastAPI Book" price:=29.99 description="Learn async FastAPI" - Read all items:
curl -X GET "http://localhost:8000/items/" - Update an item:
http PATCH http://localhost:8000/items/1 price:=24.99 - Delete an item:
curl -X DELETE "http://localhost:8000/items/1"
Best Practices for Production‑Ready Async CRUD APIs
Building a functional prototype is just the first step. To ensure reliability and scalability, consider the following recommendations:
1. Use a robust database engine
SQLite is great for demos, but for production use PostgreSQL or MySQL with async drivers (asyncpg, aiomysql).
2. Implement pagination and filtering
Instead of returning all rows, add query parameters for limit, offset, and field‑based filters. This reduces memory usage and improves response times.
Leave a Reply