Learning FastAPI: Getting Started

FastAPI has rapidly become one of the most popular Python web frameworks, and for good reason. It combines the ease of use of Flask with the power of modern Python features, automatic API documentation, and incredible performance. If you're building APIs in Python, FastAPI should be at the top of your list.
What is FastAPI?
FastAPI is a modern, high-performance web framework for building APIs with Python 3.8+ based on standard Python type hints. Created by Sebastián Ramírez in 2018, it has quickly gained traction for its developer experience and performance.
Key Features:
- Fast - One of the fastest Python frameworks available, on par with NodeJS and Go
- Type hints - Built on Python type hints for better IDE support and validation
- Automatic docs - Interactive API documentation generated automatically
- Modern Python - Uses async/await for high-performance async code
- Standards-based - Built on OpenAPI and JSON Schema
Why Choose FastAPI?
1. Incredible Developer Experience
FastAPI provides amazing autocomplete and validation out of the box:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
email: str
age: int
@app.post("/users/")
async def create_user(user: User):
# Automatic validation and parsing!
# Your IDE knows the exact types
return {"message": f"Created user {user.name}"}Benefits:
- Full IDE autocomplete support
- Automatic request validation
- Clear error messages
- No manual parsing needed
2. Automatic Interactive Documentation
The moment you run your app, you get beautiful, interactive API docs at /docs:
# Just run your app
# Visit http://localhost:8000/docs
# See Swagger UI with all your endpoints
# Test APIs right in the browser!You also get ReDoc documentation at /redoc - two documentation styles for free!
3. Performance Matters
FastAPI is built on Starlette and Pydantic, making it one of the fastest Python frameworks:
# Async support for high concurrency
@app.get("/items/{item_id}")
async def read_item(item_id: int):
# Handle thousands of concurrent requests
result = await fetch_from_database(item_id)
return resultPerformance comparison:
- FastAPI: ~60,000 requests/second
- Flask: ~3,000 requests/second
- Django: ~2,000 requests/second
Getting Started: Installation
Setting up FastAPI is straightforward:
# Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install FastAPI and Uvicorn (ASGI server)
pip install "fastapi[standard]"
# Or install separately
pip install fastapi uvicorn[standard]Your First FastAPI Application
Create a file called main.py:
from fastapi import FastAPI
# Create FastAPI instance
app = FastAPI()
# Define your first endpoint
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}Run your application:
# Start the development server
uvicorn main:app --reload
# Your API is now running at http://localhost:8000
# Docs available at http://localhost:8000/docsThat's it! You have a working API with automatic validation, documentation, and type safety.
Path Parameters and Validation
FastAPI makes path parameters type-safe and validated:
from fastapi import FastAPI, Path
from typing import Annotated
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)]
):
return {"item_id": item_id}What happens here:
item_idmust be an integer- Must be between 1 and 1000 (ge = greater or equal, le = less or equal)
- Automatic validation and error messages
- Documented in the interactive docs
Query Parameters
Query parameters work just as elegantly:
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
skip: int = 0,
limit: Annotated[int, Query(le=100)] = 10,
search: str | None = None
):
return {
"skip": skip,
"limit": limit,
"search": search
}
# Example: /items/?skip=20&limit=50&search=pythonRequest Body with Pydantic Models
Pydantic models provide powerful data validation:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
app = FastAPI()
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr # Validates email format
full_name: str | None = None
age: int = Field(..., ge=0, le=150)
created_at: datetime = Field(default_factory=datetime.now)
@app.post("/users/")
async def create_user(user: User):
return {
"message": "User created successfully",
"user": user
}Try this invalid request:
{
"username": "ab",
"email": "not-an-email",
"age": 200
}You'll get detailed validation errors automatically!
Response Models
Control exactly what your API returns:
from pydantic import BaseModel
class UserIn(BaseModel):
username: str
password: str
email: str
class UserOut(BaseModel):
username: str
email: str
@app.post("/users/", response_model=UserOut)
async def create_user(user: UserIn):
# Password won't be in the response!
return userDependency Injection
FastAPI has a powerful dependency injection system:
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
if credentials.credentials != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
return credentials.credentials
@app.get("/protected/")
async def protected_route(token: str = Depends(verify_token)):
return {"message": "You have access!", "token": token}Database Integration Example
Here's a practical example with SQLAlchemy:
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# Database setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Model
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
name = Column(String)
Base.metadata.create_all(bind=engine)
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# FastAPI app
app = FastAPI()
# Pydantic models
from pydantic import BaseModel
class UserCreate(BaseModel):
email: str
name: str
class UserResponse(BaseModel):
id: int
email: str
name: str
class Config:
from_attributes = True
@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = User(email=user.email, name=user.name)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@app.get("/users/{user_id}", response_model=UserResponse)
def read_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return userBackground Tasks
FastAPI makes background tasks simple:
from fastapi import BackgroundTasks
def send_email(email: str, message: str):
# Simulate sending email
print(f"Sending email to {email}: {message}")
@app.post("/send-notification/")
async def send_notification(
email: str,
background_tasks: BackgroundTasks
):
background_tasks.add_task(send_email, email, "Welcome to our service!")
return {"message": "Notification will be sent in background"}Error Handling
Custom exception handlers give you full control:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something wrong."}
)
@app.get("/custom/{name}")
async def read_custom(name: str):
if name == "error":
raise CustomException(name=name)
return {"name": name}Testing Your API
FastAPI apps are easy to test with TestClient:
from fastapi.testclient import TestClient
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
def test_create_user():
response = client.post(
"/users/",
json={
"username": "testuser",
"email": "test@example.com",
"age": 25
}
)
assert response.status_code == 200
data = response.json()
assert data["user"]["username"] == "testuser"Best Practices
1. Project Structure
myapi/
├── main.py # Entry point
├── api/
│ ├── __init__.py
│ ├── users.py # User routes
│ └── items.py # Item routes
├── models/
│ ├── __init__.py
│ ├── user.py # Database models
│ └── item.py
├── schemas/
│ ├── __init__.py
│ ├── user.py # Pydantic schemas
│ └── item.py
├── services/
│ ├── __init__.py
│ └── user_service.py # Business logic
└── tests/
├── __init__.py
└── test_users.py2. Use APIRouter for Modularity
# api/users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_users():
return []
# main.py
from api import users
app = FastAPI()
app.include_router(users.router)3. Environment Variables
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
api_key: str
class Config:
env_file = ".env"
settings = Settings()4. Add CORS Middleware
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)Common Pitfalls to Avoid
1. Mixing Sync and Async
# ❌ Bad - mixing sync and async
@app.get("/users/")
async def get_users():
users = blocking_database_call() # Blocks the event loop!
return users
# ✅ Good - keep it consistent
@app.get("/users/")
def get_users():
users = blocking_database_call()
return users
# ✅ Better - use async properly
@app.get("/users/")
async def get_users():
users = await async_database_call()
return users2. Not Using Response Models
# ❌ Bad - no control over response
@app.get("/users/{user_id}")
def get_user(user_id: int):
user = get_user_from_db(user_id)
return user # Might include password!
# ✅ Good - explicit response model
@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
user = get_user_from_db(user_id)
return user # Password automatically excludedWhen to Use FastAPI vs. Other Frameworks
Use FastAPI when:
- Building modern REST APIs
- Need automatic validation and documentation
- Want high performance with async support
- Building microservices
- Type safety is important
Consider alternatives:
- Flask: Simpler projects, more mature ecosystem
- Django: Full-featured web applications with admin panel
- Sanic: Pure performance focus, less features
Real-World Example: Todo API
Here's a complete, production-ready example:
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from typing import List
from datetime import datetime
import uuid
app = FastAPI(title="Todo API", version="1.0.0")
# In-memory storage (use a real database in production!)
todos = {}
class TodoCreate(BaseModel):
title: str
description: str | None = None
class TodoUpdate(BaseModel):
title: str | None = None
description: str | None = None
completed: bool | None = None
class Todo(BaseModel):
id: str
title: str
description: str | None
completed: bool
created_at: datetime
updated_at: datetime
@app.post("/todos/", response_model=Todo, status_code=201)
def create_todo(todo: TodoCreate):
todo_id = str(uuid.uuid4())
now = datetime.now()
new_todo = Todo(
id=todo_id,
title=todo.title,
description=todo.description,
completed=False,
created_at=now,
updated_at=now
)
todos[todo_id] = new_todo
return new_todo
@app.get("/todos/", response_model=List[Todo])
def list_todos(skip: int = 0, limit: int = 10):
return list(todos.values())[skip:skip + limit]
@app.get("/todos/{todo_id}", response_model=Todo)
def get_todo(todo_id: str):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
return todos[todo_id]
@app.patch("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: str, todo_update: TodoUpdate):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
stored_todo = todos[todo_id]
update_data = todo_update.model_dump(exclude_unset=True)
updated_todo = stored_todo.model_copy(update=update_data)
updated_todo.updated_at = datetime.now()
todos[todo_id] = updated_todo
return updated_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: str):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
del todos[todo_id]Next Steps
Now that you understand the basics, here's what to explore next:
-
Authentication & Security
- OAuth2 with JWT tokens
- API key authentication
- Rate limiting
-
Database Integration
- SQLAlchemy ORM
- Tortoise ORM (async)
- MongoDB with Motor
-
Advanced Features
- WebSocket support
- GraphQL with Strawberry
- File uploads and downloads
- Custom middleware
-
Deployment
- Docker containerization
- Deploy to AWS/GCP/Azure
- CI/CD pipelines
- Load balancing and scaling
-
Testing & Quality
- Unit testing with pytest
- Integration testing
- Load testing with Locust
- Code coverage
Conclusion
FastAPI is a game-changer for Python API development. It combines the best of modern Python features with excellent developer experience, automatic documentation, and impressive performance. Whether you're building a simple REST API or a complex microservices architecture, FastAPI has the tools you need.
The framework's use of type hints and Pydantic models means fewer bugs, better IDE support, and more maintainable code. Combined with automatic interactive documentation, it's no wonder FastAPI has become one of the fastest-growing Python frameworks.
Start building your first FastAPI application today - you'll be amazed at how quickly you can create production-ready APIs with clean, type-safe code.
Resources
- Official Documentation: https://fastapi.tiangolo.com/
- GitHub Repository: https://github.com/tiangolo/fastapi
- Tutorial: https://fastapi.tiangolo.com/tutorial/
- Awesome FastAPI: https://github.com/mjhea0/awesome-fastapi
- Community Discord: Join the FastAPI Discord for help and discussions
Happy coding! 🚀
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.