FastAPI Tutorial 2026: Build a REST API with PostgreSQL, Auth, and Async
Why FastAPI?
FastAPI is now the most popular Python web framework for APIs, surpassing Flask in developer surveys. Key advantages:
- Automatic docs: Swagger UI generated from your code
- Type safety: Pydantic validation on all inputs and outputs
- Async: native async/await support
- Fast: performance comparable to Node.js
Installation
pip install fastapi uvicorn[standard] sqlalchemy asyncpg pydantic-settings python-jose passlib
First Endpoint
# main.py
from fastapi import FastAPI
app = FastAPI(title="My API", version="1.0.0")
@app.get("/")
async def root():
return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
async def get_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "query": q}
uvicorn main:app --reload
# Visit http://localhost:8000/docs for auto-generated Swagger UI
Pydantic Models for Request/Response
from pydantic import BaseModel, EmailStr
from datetime import datetime
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserResponse(BaseModel):
id: int
username: str
email: str
created_at: datetime
model_config = {"from_attributes": True} # allow ORM models
@app.post("/users/", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# FastAPI validates input automatically
# Returns 422 if validation fails
...
Database with SQLAlchemy Async
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
from sqlalchemy import String, DateTime, func
from typing import AsyncGenerator
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/mydb"
engine = create_async_engine(DATABASE_URL)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(50), unique=True)
email: Mapped[str] = mapped_column(String(200), unique=True)
hashed_password: Mapped[str] = mapped_column(String(200))
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSession(engine) as session:
yield session
Dependency Injection
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
async def get_user_by_id(
user_id: int,
db: AsyncSession = Depends(get_db)
) -> User:
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user: User = Depends(get_user_by_id)):
return user
JWT Authentication
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"])
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_token(user_id: int) -> str:
return jwt.encode({"sub": str(user_id)}, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
) -> User:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = int(payload["sub"])
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
return await get_user_by_id(user_id, db)
@app.post("/token")
async def login(form: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)):
# Verify credentials and return token
...
@app.get("/me", response_model=UserResponse)
async def get_me(current_user: User = Depends(get_current_user)):
return current_user
Background Tasks
from fastapi import BackgroundTasks
import smtplib
def send_welcome_email(email: str):
# runs after the response is sent
print(f"Sending welcome email to {email}")
@app.post("/users/", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate, background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db)):
# Create user in DB
db_user = User(username=user.username, email=user.email)
db.add(db_user)
await db.commit()
# Schedule email without blocking response
background_tasks.add_task(send_welcome_email, user.email)
return db_user
Deploy with Uvicorn and nginx
# Production: multiple workers
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}