Learn / Frameworks / FastAPI / Async, Databases and Deployment

FastAPI · Lesson 5 of 5

Async, Databases and Deployment

When to use async def, talking to a database and running in production.

  • Advanced
  • 16 min read
  • 3 objectives

Before this lessonLesson 4: Dependency Injection and Auth

What you will learn

  • Choose async vs sync
  • Connect SQLAlchemy
  • Run with uvicorn/Docker

You may write endpoints with def or async def. Choosing correctly affects performance, and databases plus deployment are what turn a demo into a service.

async or not?

  • async def: use when everything you call inside is awaitable (async database drivers, httpx.AsyncClient). The event loop can serve other requests while you wait.
  • def: use when you call blocking code (a regular database driver, requests, heavy computation). FastAPI runs these in a thread pool so they do not freeze the server.
import httpx

@app.get("/proxy")
async def proxy():
    async with httpx.AsyncClient() as client:
        r = await client.get("https://api.github.com")
    return {"status": r.status_code}

A database with SQLAlchemy

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

engine = create_engine("sqlite:///./app.db", connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine, autoflush=False)

class Base(DeclarativeBase):
    pass

# models.py
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

class Todo(Base):
    __tablename__ = "todos"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(100))
    done: Mapped[bool] = mapped_column(default=False)

Base.metadata.create_all(engine)
@app.post("/todos", status_code=201)
def create(t: TodoIn, db: Session = Depends(get_db)):
    todo = Todo(title=t.title)
    db.add(todo)
    db.commit()
    db.refresh(todo)
    return todo

@app.get("/todos/{tid}")
def read(tid: int, db: Session = Depends(get_db)):
    todo = db.get(Todo, tid)
    if not todo:
        raise HTTPException(404)
    return todo

For production use PostgreSQL, and manage schema changes with Alembic migrations instead of create_all.

Background tasks

from fastapi import BackgroundTasks

def send_email(to: str):
    print("emailing", to)

@app.post("/signup")
def signup(email: str, bg: BackgroundTasks):
    bg.add_task(send_email, email)      # runs after the response is sent
    return {"ok": True}

Deploying

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
  • Read secrets and the database URL from environment variables (pydantic-settings helps).
  • Run behind a process manager or several workers: uvicorn main:app --workers 4.
  • Add CORS middleware if a browser front end on another origin calls your API.
  • Write tests with TestClient from fastapi.testclient.
# Write your solution here
Course completeYou finished FastAPIReview the full course or pick your next one.