FastAPI Tutorial: Build Modern Python APIs
Master FastAPI for building high-performance Python APIs. Learn async endpoints, validation, authentication, database integration, and deployment.
Moshiour Rahman
Advertisement
What is FastAPI?
FastAPI is a modern, high-performance Python web framework for building APIs. It’s based on standard Python type hints and provides automatic API documentation, validation, and async support.
Why FastAPI?
| Feature | Benefit |
|---|---|
| Speed | One of the fastest Python frameworks |
| Type hints | Automatic validation and documentation |
| Async | Built-in async/await support |
| OpenAPI | Auto-generated Swagger docs |
| Modern | Python 3.8+ features |
Installation
pip install fastapi uvicorn[standard]
pip install pydantic sqlalchemy python-jose passlib
Getting Started
Basic Application
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="A sample FastAPI application",
version="1.0.0"
)
@app.get("/")
def read_root():
return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "query": q}
# Run with: uvicorn main:app --reload
Path Parameters
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(
user_id: int = Path(..., title="User ID", ge=1)
):
return {"user_id": user_id}
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}
Query Parameters
from fastapi import FastAPI, Query
from typing import List, Optional
app = FastAPI()
@app.get("/items/")
def get_items(
skip: int = Query(0, ge=0),
limit: int = Query(10, le=100),
q: Optional[str] = Query(None, min_length=3, max_length=50),
tags: List[str] = Query([])
):
return {
"skip": skip,
"limit": limit,
"query": q,
"tags": tags
}
Request Body with Pydantic
Define Models
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List
from datetime import datetime
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr
password: str = Field(..., min_length=8)
full_name: Optional[str] = None
class UserResponse(BaseModel):
id: int
username: str
email: EmailStr
full_name: Optional[str]
created_at: datetime
class Config:
from_attributes = True
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
full_name: Optional[str] = None
Use Models in Endpoints
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
users_db = {}
@app.post("/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
if user.username in users_db:
raise HTTPException(
status_code=400,
detail="Username already exists"
)
user_data = {
"id": len(users_db) + 1,
"username": user.username,
"email": user.email,
"full_name": user.full_name,
"created_at": datetime.now()
}
users_db[user.username] = user_data
return user_data
@app.put("/users/{user_id}", response_model=UserResponse)
def update_user(user_id: int, user: UserUpdate):
# Update logic
pass
Async Endpoints
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/async-example")
async def async_endpoint():
await asyncio.sleep(1) # Simulate async operation
return {"message": "Async response"}
@app.get("/fetch-data")
async def fetch_external_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()
Database Integration
SQLAlchemy Setup
# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "postgresql://user:password@localhost/dbname"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Models
# models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
posts = relationship("Post", back_populates="author")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
content = Column(String)
author_id = Column(Integer, ForeignKey("users.id"))
created_at = Column(DateTime, default=datetime.utcnow)
author = relationship("User", back_populates="posts")
CRUD Operations
# crud.py
from sqlalchemy.orm import Session
from models import User, Post
from schemas import UserCreate, PostCreate
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_user(db: Session, user_id: int):
return db.query(User).filter(User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(User).filter(User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(User).offset(skip).limit(limit).all()
def create_user(db: Session, user: UserCreate):
hashed_password = pwd_context.hash(user.password)
db_user = User(
username=user.username,
email=user.email,
hashed_password=hashed_password
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def create_post(db: Session, post: PostCreate, user_id: int):
db_post = Post(**post.dict(), author_id=user_id)
db.add(db_post)
db.commit()
db.refresh(db_post)
return db_post
API Endpoints
# main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db, engine
import models
import crud
import schemas
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.post("/users/", response_model=schemas.UserResponse)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.UserResponse])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.UserResponse)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
Authentication with JWT
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
# Get user from database
user = get_user_from_db(username=token_data.username)
if user is None:
raise credentials_exception
return user
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password"
)
access_token = create_access_token(
data={"sub": user.username},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_user)):
return current_user
File Uploads
from fastapi import FastAPI, File, UploadFile
from typing import List
import shutil
app = FastAPI()
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
with open(f"uploads/{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"filename": file.filename, "size": file.size}
@app.post("/upload-multiple/")
async def upload_files(files: List[UploadFile] = File(...)):
filenames = []
for file in files:
with open(f"uploads/{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
filenames.append(file.filename)
return {"filenames": filenames}
Background Tasks
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def send_email(email: str, message: str):
# Simulate sending email
import time
time.sleep(5)
print(f"Email sent to {email}")
@app.post("/send-notification/")
async def send_notification(
email: str,
background_tasks: BackgroundTasks
):
background_tasks.add_task(send_email, email, "Welcome!")
return {"message": "Notification will be sent"}
Middleware
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import time
app = FastAPI()
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Custom middleware
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
Error Handling
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
app = FastAPI()
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
@app.exception_handler(CustomException)
async def custom_exception_handler(request, exc: CustomException):
return JSONResponse(
status_code=418,
content={"message": f"Error: {exc.name}"}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"detail": exc.errors(), "body": exc.body}
)
API Documentation
FastAPI auto-generates documentation:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="API description with **markdown** support",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
Summary
| Feature | Description |
|---|---|
| Type hints | Auto validation and docs |
| Async | High performance |
| Pydantic | Data validation |
| OpenAPI | Auto documentation |
| Dependency Injection | Clean architecture |
FastAPI is ideal for building modern, high-performance APIs with Python.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
FastAPI Tutorial Part 1: Introduction and Setup - Build Modern Python APIs
Start your FastAPI journey with this comprehensive guide. Learn installation, create your first API, understand async Python, and explore automatic documentation.
PythonFastAPI Tutorial Part 2: Path and Query Parameters - Complete Guide
Master FastAPI path and query parameters. Learn parameter validation, type conversion, optional parameters, and advanced patterns for building flexible APIs.
PythonFastAPI Tutorial Part 4: Response Models and Status Codes - Control Your API Output
Learn to control FastAPI responses with response models, status codes, and headers. Master data filtering, multiple response types, and proper HTTP semantics.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.