Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Build modern REST APIs with FastAPI ๐๏ธ
- Implement automatic API documentation ๐
- Use type hints for validation ๐ฏ
- Deploy high-performance APIs โก
๐ฏ Introduction
Welcome to the world of FastAPI - the modern, fast, and incredibly fun way to build APIs in Python! ๐
Imagine youโre building the backend for the next big app - maybe a food delivery service ๐, a social media platform ๐ฑ, or a game leaderboard ๐ฎ. You need something thatโs:
- Lightning fast โก
- Easy to write and maintain ๐
- Automatically generates documentation ๐
- Validates data for you ๐ก๏ธ
Thatโs exactly what FastAPI delivers! Built on modern Python features like type hints and async/await, itโs like having a supercharged API framework that does the heavy lifting for you. Letโs dive in and build something awesome! ๐
๐ Understanding FastAPI
Think of FastAPI as your smart API assistant ๐ค. While traditional frameworks are like manual cars (you control everything), FastAPI is like a Tesla with autopilot - it handles many things automatically while giving you full control when needed.
What Makes FastAPI Special? โจ
# Traditional Flask approach
@app.route('/user/<user_id>')
def get_user(user_id):
# Hope user_id is valid... ๐ค
# Manually validate everything
# Write your own docs
return {"user_id": user_id}
# FastAPI approach ๐
@app.get('/user/{user_id}')
def get_user(user_id: int): # ๐ Automatic validation!
# user_id is guaranteed to be an int
# Docs generated automatically
# Type hints = superpowers!
return {"user_id": user_id}
FastAPI uses Pythonโs type hints to:
- Validate request data automatically ๐ก๏ธ
- Serialize responses properly ๐ฆ
- Generate interactive API docs ๐
- Provide editor support and autocomplete ๐ก
๐ง Basic Syntax and Usage
Letโs build our first FastAPI application - a simple pizza ordering API! ๐
Installing FastAPI
# Install FastAPI and an ASGI server
pip install fastapi uvicorn
Your First API
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
# ๐จ Create your FastAPI app
app = FastAPI(title="Pizza Palace API ๐")
# ๐ Define data models with Pydantic
class Pizza(BaseModel):
name: str
size: str # small, medium, large
toppings: list[str]
extra_cheese: Optional[bool] = False
# ๐ Home route
@app.get("/")
def read_root():
return {"message": "Welcome to Pizza Palace! ๐"}
# ๐ Get menu
@app.get("/menu")
def get_menu():
return {
"pizzas": ["Margherita", "Pepperoni", "Hawaiian"],
"sizes": ["small", "medium", "large"],
"toppings": ["mushrooms", "olives", "peppers"]
}
# ๐ Order a pizza
@app.post("/order")
def create_order(pizza: Pizza): # ๐ Automatic validation!
# FastAPI validates the request body matches Pizza model
price = calculate_price(pizza)
return {
"order": pizza,
"price": price,
"status": "preparing ๐จโ๐ณ"
}
def calculate_price(pizza: Pizza):
base_prices = {"small": 10, "medium": 15, "large": 20}
price = base_prices[pizza.size]
price += len(pizza.toppings) * 1.5
if pizza.extra_cheese:
price += 2
return price
Running Your API
# Start the server ๐
uvicorn main:app --reload
# Your API is now live at http://localhost:8000
# Interactive docs at http://localhost:8000/docs ๐
๐ก Practical Examples
Example 1: Building a Task Manager API ๐
Letโs create a more complex API for managing tasks:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
from enum import Enum
app = FastAPI(title="TaskMaster API ๐")
# ๐ฏ Define enums for better validation
class Priority(str, Enum):
low = "low"
medium = "medium"
high = "high"
urgent = "urgent"
# ๐ฆ Task model with validation
class Task(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = None
priority: Priority = Priority.medium
completed: bool = False
due_date: Optional[datetime] = None
class Config:
schema_extra = {
"example": {
"title": "Learn FastAPI",
"description": "Complete the FastAPI tutorial",
"priority": "high",
"due_date": "2024-12-31T23:59:59"
}
}
# ๐๏ธ In-memory database (for demo)
tasks_db = {}
task_counter = 0
# ๐ Get all tasks
@app.get("/tasks", response_model=List[Task])
def get_tasks(
priority: Optional[Priority] = None,
completed: Optional[bool] = None
):
"""Get all tasks with optional filters ๐"""
tasks = list(tasks_db.values())
# Apply filters if provided
if priority:
tasks = [t for t in tasks if t.priority == priority]
if completed is not None:
tasks = [t for t in tasks if t.completed == completed]
return tasks
# โ Create a task
@app.post("/tasks", response_model=dict, status_code=status.HTTP_201_CREATED)
def create_task(task: Task):
"""Create a new task ๐จ"""
global task_counter
task_counter += 1
task_id = f"task_{task_counter}"
tasks_db[task_id] = task
return {
"id": task_id,
"task": task,
"message": "Task created successfully! ๐"
}
# ๐ Get specific task
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: str):
"""Get a specific task by ID ๐ฏ"""
if task_id not in tasks_db:
raise HTTPException(
status_code=404,
detail=f"Task {task_id} not found ๐"
)
return tasks_db[task_id]
# โ
Complete a task
@app.patch("/tasks/{task_id}/complete")
def complete_task(task_id: str):
"""Mark a task as completed โ
"""
if task_id not in tasks_db:
raise HTTPException(
status_code=404,
detail=f"Task {task_id} not found ๐"
)
tasks_db[task_id].completed = True
return {"message": "Task completed! Great job! ๐"}
Example 2: Game Leaderboard API ๐ฎ
from fastapi import FastAPI, Query
from pydantic import BaseModel, validator
from typing import List, Optional
import uuid
app = FastAPI(title="Game Leaderboard API ๐")
# ๐ฎ Player score model
class PlayerScore(BaseModel):
player_name: str
score: int
game_mode: str
@validator('score')
def score_must_be_positive(cls, v):
if v < 0:
raise ValueError('Score must be positive! ๐ซ')
return v
@validator('player_name')
def name_must_be_valid(cls, v):
if len(v) < 3:
raise ValueError('Name must be at least 3 characters! ๐')
if len(v) > 20:
raise ValueError('Name too long! Max 20 characters ๐')
return v
# ๐ Leaderboard entry
class LeaderboardEntry(BaseModel):
rank: int
player_name: str
score: int
game_mode: str
achievement: Optional[str] = None
# ๐๏ธ Store scores
leaderboard_db = []
# ๐ฏ Submit a score
@app.post("/score/submit")
def submit_score(score: PlayerScore):
"""Submit a new high score ๐"""
# Add to leaderboard
leaderboard_db.append(score)
# Sort by score (highest first)
leaderboard_db.sort(key=lambda x: x.score, reverse=True)
# Find player's rank
rank = next(
i for i, s in enumerate(leaderboard_db, 1)
if s.player_name == score.player_name and s.score == score.score
)
# Award achievements ๐
achievement = None
if score.score >= 10000:
achievement = "Legend ๐"
elif score.score >= 5000:
achievement = "Master ๐ฏ"
elif score.score >= 1000:
achievement = "Pro ๐ช"
return {
"rank": rank,
"achievement": achievement,
"message": f"Score submitted! You're rank #{rank}! ๐"
}
# ๐ Get leaderboard
@app.get("/leaderboard", response_model=List[LeaderboardEntry])
def get_leaderboard(
game_mode: Optional[str] = None,
top: int = Query(default=10, ge=1, le=100)
):
"""Get the top players ๐"""
# Filter by game mode if specified
scores = leaderboard_db
if game_mode:
scores = [s for s in scores if s.game_mode == game_mode]
# Create leaderboard entries
entries = []
for i, score in enumerate(scores[:top], 1):
# Award achievements
achievement = None
if score.score >= 10000:
achievement = "Legend ๐"
elif score.score >= 5000:
achievement = "Master ๐ฏ"
elif score.score >= 1000:
achievement = "Pro ๐ช"
entries.append(LeaderboardEntry(
rank=i,
player_name=score.player_name,
score=score.score,
game_mode=score.game_mode,
achievement=achievement
))
return entries
# ๐ฎ Get player stats
@app.get("/player/{player_name}/stats")
def get_player_stats(player_name: str):
"""Get statistics for a specific player ๐"""
player_scores = [s for s in leaderboard_db if s.player_name == player_name]
if not player_scores:
return {"message": "No scores found for this player ๐"}
return {
"player": player_name,
"total_games": len(player_scores),
"highest_score": max(s.score for s in player_scores),
"average_score": sum(s.score for s in player_scores) // len(player_scores),
"favorite_mode": max(
set(s.game_mode for s in player_scores),
key=lambda m: sum(1 for s in player_scores if s.game_mode == m)
)
}
๐ Advanced Concepts
Async Support for Lightning Speed โก
FastAPI fully supports async/await for maximum performance:
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
# ๐๏ธ Async endpoints for concurrent operations
@app.get("/weather/{city}")
async def get_weather(city: str):
"""Get weather data asynchronously ๐ค๏ธ"""
async with httpx.AsyncClient() as client:
# Make multiple API calls concurrently
weather_task = client.get(f"https://api.weather.com/{city}")
forecast_task = client.get(f"https://api.weather.com/{city}/forecast")
# Wait for both to complete
weather, forecast = await asyncio.gather(
weather_task,
forecast_task
)
return {
"city": city,
"current": weather.json(),
"forecast": forecast.json()
}
# ๐๏ธ Async database operations
from typing import List
from sqlalchemy.ext.asyncio import AsyncSession
@app.get("/users/active")
async def get_active_users(db: AsyncSession):
"""Get all active users asynchronously ๐ฅ"""
# Async database query
result = await db.execute(
"SELECT * FROM users WHERE active = true"
)
users = result.fetchall()
# Process in parallel
tasks = [process_user(user) for user in users]
processed_users = await asyncio.gather(*tasks)
return processed_users
Dependency Injection Magic ๐ช
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
# ๐ Authentication dependency
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
):
"""Verify and return current user ๐ค"""
token = credentials.credentials
# Verify token (simplified)
if token != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials ๐ซ"
)
return {"username": "player1", "user_id": 123}
# ๐ฎ Use dependencies in endpoints
@app.post("/game/save")
async def save_game(
save_data: dict,
current_user: dict = Depends(get_current_user)
):
"""Save game progress (authenticated) ๐พ"""
return {
"message": f"Game saved for {current_user['username']}! ๐ฎ",
"save_id": "save_12345"
}
# ๐๏ธ Dependency chains
async def get_db():
"""Database connection dependency ๐๏ธ"""
db = AsyncDatabase()
try:
yield db
finally:
await db.close()
async def get_game_service(
db = Depends(get_db),
user = Depends(get_current_user)
):
"""Game service with dependencies ๐ฏ"""
return GameService(db, user)
@app.get("/game/stats")
async def get_game_stats(
service: GameService = Depends(get_game_service)
):
"""Get game statistics using service ๐"""
return await service.get_user_stats()
Background Tasks ๐โโ๏ธ
from fastapi import BackgroundTasks
import time
# ๐ง Email notification function
def send_email_notification(email: str, message: str):
"""Simulate sending email (runs in background) ๐ฌ"""
time.sleep(2) # Simulate email sending
print(f"Email sent to {email}: {message}")
@app.post("/order/pizza")
async def order_pizza(
pizza: Pizza,
email: str,
background_tasks: BackgroundTasks
):
"""Order pizza with email confirmation ๐"""
# Process order immediately
order_id = f"order_{int(time.time())}"
# Schedule email notification for background
background_tasks.add_task(
send_email_notification,
email,
f"Your pizza order {order_id} is confirmed! ๐"
)
# Return immediately (email sends in background)
return {
"order_id": order_id,
"status": "confirmed",
"message": "Check your email for confirmation! ๐ง"
}
โ ๏ธ Common Pitfalls and Solutions
โ Wrong: Blocking operations in async endpoints
# โ BAD: Blocks the event loop
@app.get("/slow")
async def slow_endpoint():
time.sleep(5) # ๐ฑ Blocks everything!
return {"message": "Finally done..."}
โ Right: Use async operations or run in executor
# โ
GOOD: Non-blocking async sleep
@app.get("/fast")
async def fast_endpoint():
await asyncio.sleep(5) # ๐ Non-blocking!
return {"message": "Done efficiently!"}
# โ
GOOD: Run blocking code in executor
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor()
@app.get("/compute")
async def compute_endpoint():
# Run blocking operation in thread pool
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
executor,
expensive_computation # Blocking function
)
return {"result": result}
โ Wrong: Not handling validation errors properly
# โ BAD: Crashes on invalid data
@app.post("/user")
def create_user(data: dict):
age = int(data["age"]) # ๐ฅ Crashes if age isn't a number
return {"age": age}
โ Right: Use Pydantic models for validation
# โ
GOOD: Automatic validation with helpful errors
from pydantic import BaseModel, validator
class User(BaseModel):
name: str
age: int
email: str
@validator('age')
def age_must_be_valid(cls, v):
if v < 0 or v > 150:
raise ValueError('Age must be between 0 and 150! ๐')
return v
@app.post("/user")
def create_user(user: User): # ๐ก๏ธ Validated automatically!
return {"message": f"User {user.name} created!"}
๐ ๏ธ Best Practices
1. Use Type Hints Everywhere ๐ฏ
# โ
Type hints = automatic validation + documentation
@app.get("/items/{item_id}", response_model=Item)
async def get_item(
item_id: int, # Path parameter
q: Optional[str] = Query(None, max_length=50), # Query parameter
user: User = Depends(get_current_user) # Dependency
) -> Item: # Return type
return await fetch_item(item_id, q, user)
2. Structure Your Project ๐
# project/
# โโโ app/
# โ โโโ __init__.py
# โ โโโ main.py # FastAPI app
# โ โโโ models.py # Pydantic models
# โ โโโ routers/ # API routes
# โ โ โโโ users.py
# โ โ โโโ items.py
# โ โโโ services/ # Business logic
# โ โโโ dependencies.py # Shared dependencies
# main.py
from fastapi import FastAPI
from app.routers import users, items
app = FastAPI(title="My Awesome API ๐")
# Include routers
app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(items.router, prefix="/items", tags=["items"])
3. Handle Errors Gracefully ๐ก๏ธ
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
# Custom exception handler
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content={
"message": str(exc),
"type": "validation_error",
"tip": "Check your input values! ๐"
}
)
# Global error handling
@app.middleware("http")
async def catch_exceptions(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
return JSONResponse(
status_code=500,
content={
"message": "Something went wrong! ๐ฐ",
"type": "server_error"
}
)
4. Use Environment Variables ๐
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "FastAPI App"
admin_email: str
database_url: str
secret_key: str
class Config:
env_file = ".env"
# Create settings instance
settings = Settings()
# Use in your app
app = FastAPI(title=settings.app_name)
@app.get("/info")
def get_info():
return {
"app": settings.app_name,
"admin": settings.admin_email
}
๐งช Hands-On Exercise
Ready to put your FastAPI skills to the test? Letโs build a Book Library API! ๐
Your Challenge:
Create a complete API for managing a book library with these features:
- Book Model with title, author, ISBN, year, and available status
- Endpoints:
- List all books (with optional author filter)
- Add a new book
- Get book details by ISBN
- Borrow a book (mark as unavailable)
- Return a book (mark as available)
- Validation: ISBN must be 13 digits, year must be valid
- Background task: Log all borrow/return operations
Give it a try! Remember:
- Use Pydantic models for validation ๐
- Add proper error handling ๐ก๏ธ
- Include helpful response messages ๐ฌ
๐ก Click here for the solution
from fastapi import FastAPI, HTTPException, BackgroundTasks, Query
from pydantic import BaseModel, validator
from typing import Optional, List
from datetime import datetime
import re
app = FastAPI(title="Library API ๐")
# ๐ Book model
class Book(BaseModel):
title: str
author: str
isbn: str
year: int
available: bool = True
@validator('isbn')
def validate_isbn(cls, v):
# Check if ISBN is 13 digits
if not re.match(r'^\d{13}$', v):
raise ValueError('ISBN must be exactly 13 digits! ๐')
return v
@validator('year')
def validate_year(cls, v):
current_year = datetime.now().year
if v < 1000 or v > current_year:
raise ValueError(f'Year must be between 1000 and {current_year}! ๐
')
return v
# ๐๏ธ In-memory database
library_db = {}
# ๐ Logging function for background
def log_transaction(action: str, isbn: str, timestamp: datetime):
with open("library_log.txt", "a") as f:
f.write(f"{timestamp}: {action} - ISBN: {isbn}\n")
print(f"Logged: {action} for book {isbn} ๐")
# ๐ List all books
@app.get("/books", response_model=List[Book])
def list_books(
author: Optional[str] = Query(None, description="Filter by author"),
available_only: bool = Query(False, description="Show only available books")
):
"""Get all books in the library ๐"""
books = list(library_db.values())
# Apply filters
if author:
books = [b for b in books if author.lower() in b.author.lower()]
if available_only:
books = [b for b in books if b.available]
return books
# โ Add a new book
@app.post("/books", status_code=201)
def add_book(book: Book):
"""Add a new book to the library ๐"""
if book.isbn in library_db:
raise HTTPException(
status_code=400,
detail=f"Book with ISBN {book.isbn} already exists! ๐ซ"
)
library_db[book.isbn] = book
return {
"message": f"Book '{book.title}' added successfully! ๐",
"isbn": book.isbn
}
# ๐ Get book details
@app.get("/books/{isbn}", response_model=Book)
def get_book(isbn: str):
"""Get details of a specific book ๐"""
if isbn not in library_db:
raise HTTPException(
status_code=404,
detail=f"Book with ISBN {isbn} not found! ๐"
)
return library_db[isbn]
# ๐ค Borrow a book
@app.post("/books/{isbn}/borrow")
def borrow_book(
isbn: str,
background_tasks: BackgroundTasks
):
"""Borrow a book from the library ๐ค"""
if isbn not in library_db:
raise HTTPException(
status_code=404,
detail=f"Book with ISBN {isbn} not found! ๐"
)
book = library_db[isbn]
if not book.available:
raise HTTPException(
status_code=400,
detail=f"Book '{book.title}' is already borrowed! ๐"
)
# Mark as borrowed
book.available = False
# Log transaction in background
background_tasks.add_task(
log_transaction,
f"BORROWED: {book.title}",
isbn,
datetime.now()
)
return {
"message": f"You've borrowed '{book.title}'! Enjoy reading! ๐",
"due_date": "Please return within 14 days"
}
# ๐ฅ Return a book
@app.post("/books/{isbn}/return")
def return_book(
isbn: str,
background_tasks: BackgroundTasks
):
"""Return a borrowed book ๐ฅ"""
if isbn not in library_db:
raise HTTPException(
status_code=404,
detail=f"Book with ISBN {isbn} not found! ๐"
)
book = library_db[isbn]
if book.available:
raise HTTPException(
status_code=400,
detail=f"Book '{book.title}' is already in the library! ๐ค"
)
# Mark as available
book.available = True
# Log transaction in background
background_tasks.add_task(
log_transaction,
f"RETURNED: {book.title}",
isbn,
datetime.now()
)
return {
"message": f"Thank you for returning '{book.title}'! ๐",
"status": "Book is now available for others"
}
# ๐ Library statistics
@app.get("/stats")
def get_library_stats():
"""Get library statistics ๐"""
total_books = len(library_db)
available_books = sum(1 for book in library_db.values() if book.available)
borrowed_books = total_books - available_books
return {
"total_books": total_books,
"available_books": available_books,
"borrowed_books": borrowed_books,
"most_popular_author": max(
set(book.author for book in library_db.values()),
key=lambda a: sum(1 for book in library_db.values() if book.author == a)
) if library_db else None
}
# ๐ Welcome endpoint
@app.get("/")
def welcome():
return {
"message": "Welcome to the Library API! ๐",
"docs": "Visit /docs for interactive documentation",
"endpoints": [
"GET /books - List all books",
"POST /books - Add a new book",
"GET /books/{isbn} - Get book details",
"POST /books/{isbn}/borrow - Borrow a book",
"POST /books/{isbn}/return - Return a book",
"GET /stats - Library statistics"
]
}
๐ Key Takeaways
Youโve just mastered the fundamentals of FastAPI! Hereโs what youโve learned:
- Automatic Validation ๐ก๏ธ - Type hints provide free validation and documentation
- High Performance โก - Built for speed with async/await support
- Developer Friendly ๐ค - Auto-generated docs and great error messages
- Modern Python ๐ - Uses the latest Python features effectively
- Production Ready ๐ - Built-in security, testing, and deployment features
FastAPI isnโt just fast in performance - itโs fast in development too! You write less code, get more features, and build better APIs.
๐ค Next Steps
Ready to take your FastAPI skills to the next level? Hereโs what to explore next:
- Database Integration ๐๏ธ - Connect with SQLAlchemy or MongoDB
- Authentication & Security ๐ - JWT tokens, OAuth2, and permissions
- WebSocket Support ๐ - Real-time communication for chat apps
- Testing Your APIs ๐งช - Unit tests and integration tests
- Deployment ๐ข - Docker, Kubernetes, and cloud platforms
Remember, building great APIs is a journey, and youโre well on your way! Keep experimenting, keep building, and most importantly, have fun!
Happy API building! ๐โจ