Life of Arjav
@lifeofarjav
A detailed, beginner-friendly explanation of the 10 FastAPI concepts covered in the carousel, with practical examples and a complete mini project.
Who this is for
Beginners, Python developers, founders, AI builders, and anyone who wants to understand how FastAPI works without getting buried in jargon.
Honest framing: FastAPI removes a lot of repetitive work, but it does not remove the need to understand HTTP, validation, databases, security, testing, and deployment.
Contents
- FastAPI, Starlette, and Pydantic
- Async for I/O work
- Data validation with Pydantic models
- Dependency injection
- Automatic interactive documentation
- Middleware and error handling
- Authentication through dependencies
- Lifespan events
- Background tasks
- CORS and browser security
- Complete mini API
- Testing and production checklist
What is FastAPI?
FastAPI is a Python framework for building APIs. An API is a set of URLs that software can call to send data, receive data, trigger an action, or read information.
Install and run
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
from fastapi import FastAPI
app = FastAPI(title="Life of Arjav Demo API")
@app.get("/")
def home():
return {"message": "FastAPI is working"}
fastapi dev main.py
Open:
- API:
http://127.0.0.1:8000/ - Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
1. FastAPI = Starlette + Pydantic
This phrase is useful, but simplified.
- Starlette handles the web layer: routing, requests, responses, middleware, async behavior, WebSockets, and more.
- Pydantic handles typed data: parsing, validation, schemas, and serialization.
- FastAPI combines them with a developer-friendly dependency system and automatic OpenAPI documentation.
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class ProductCreate(BaseModel):
name: str = Field(min_length=2, max_length=100)
price: float = Field(gt=0)
in_stock: bool = True
@app.post("/products")
def create_product(product: ProductCreate):
return {"message": "Product accepted", "product": product}
If the caller sends a negative price or omits the name, FastAPI returns a structured 422 validation error before your function runs.
2. Async for I/O work
Async helps when your application is waiting for something:
- Database query
- External API
- Object storage
- Network response
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/exchange-rate")
async def exchange_rate():
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get("https://api.example.com/rate")
response.raise_for_status()
return response.json()
Simple rule
- Waiting work: async is useful.
- Heavy calculating work: async does not make it faster.
For video processing, large image work, or heavy local AI inference, use a worker, process pool, job queue, or separate compute service.
3. Data validation with Pydantic
Pydantic models are your gatekeeper.
from typing import Literal
from pydantic import BaseModel, EmailStr, Field
class LeadCreate(BaseModel):
full_name: str = Field(min_length=2, max_length=120)
email: EmailStr
company: str = Field(min_length=2)
employee_count: int = Field(ge=1, le=1_000_000)
source: Literal["cold_email", "linkedin", "referral"]
Benefits:
- Predictable endpoint inputs
- Clear error responses
- Editor autocomplete
- Automatic documentation
- Reduced accidental bugs
Use separate input and output models so private fields, such as passwords or internal notes, do not leak.
4. Dependency injection
Dependencies are reusable functions that FastAPI runs for an endpoint.
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
def require_api_key(x_api_key: str = Header()):
if x_api_key != "demo-secret":
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
@app.get("/private-report")
def private_report(api_key: str = Depends(require_api_key)):
return {"report": "Sensitive data", "authenticated": True}
Common uses:
- Database sessions
- Authentication
- Current user
- Shared query parameters
- Configuration
- Rate-limit checks
5. Automatic documentation
FastAPI generates an OpenAPI schema from your routes, type hints, request models, response models, and security rules.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(
title="RetailReach API",
description="API for lead qualification and campaign operations.",
version="1.0.0",
)
class HealthResponse(BaseModel):
status: str
@app.get(
"/health",
response_model=HealthResponse,
summary="Check service health",
tags=["System"],
)
def health():
return {"status": "ok"}
The business value is simple: frontend developers, QA testers, and partners can understand and test the API without reading the full codebase.
6. Middleware and errors
Middleware wraps every request and response.
from time import perf_counter
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time(request: Request, call_next):
started = perf_counter()
response = await call_next(request)
elapsed = perf_counter() - started
response.headers["X-Process-Time"] = f"{elapsed:.4f}"
return response
Use HTTPException for clear API errors:
from fastapi import HTTPException
@app.get("/products/{product_id}")
def get_product(product_id: int):
product = fake_db.get(product_id)
if product is None:
raise HTTPException(status_code=404, detail="Product not found")
return product
Use middleware for logging, timing, tracing, request IDs, CORS, and security headers. Do not dump normal business logic into middleware.
7. Authentication through dependencies
Authentication asks who the caller is. Authorization asks what they are allowed to do.
from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
app = FastAPI()
security = HTTPBearer()
def current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
):
token = credentials.credentials
if token != "demo-token":
raise HTTPException(status_code=401, detail="Invalid token")
return {"id": 1, "role": "admin"}
@app.get("/admin")
def admin_dashboard(user: dict = Depends(current_user)):
if user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin access required")
return {"message": "Welcome, admin"}
This is a teaching example only. Production auth needs secure password hashing, proper token verification, HTTPS, secret rotation, permissions, and auditing.
8. Lifespan events
Lifespan logic runs once at startup and once at shutdown.
from contextlib import asynccontextmanager
from fastapi import FastAPI
resources: dict[str, object] = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
resources["model"] = "loaded-model"
resources["db_pool"] = "open-pool"
yield
resources.clear()
app = FastAPI(lifespan=lifespan)
Use it for database pools, shared HTTP clients, loaded ML models, cache warming, and clean shutdown.
9. Background tasks
Background tasks run after the response is sent.
from fastapi import BackgroundTasks, FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class Signup(BaseModel):
email: EmailStr
def send_welcome_email(email: str):
print(f"Sending welcome email to{email}")
@app.post("/signup")
def signup(data: Signup, background_tasks: BackgroundTasks):
background_tasks.add_task(send_welcome_email, data.email)
return {"message": "Account created"}
Good fits:
- Small email
- Lightweight audit record
- Webhook
- Non-critical cache update
Bad fits:
- Long-running video processing
- Critical payment work
- Jobs requiring retries
- Large pipelines
Use a durable job queue when reliability matters.
10. CORS and browser security
CORS controls whether JavaScript running on one origin can call another origin.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.example.com",
"http://localhost:3000",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
A request can work in curl or Postman and fail in a browser. That usually means the backend is reachable, but the browser rejected the call because of CORS.
Complete mini API
Project structure
fastapi-demo/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ ├── dependencies.py
│ └── services.py
└── tests/
└── test_main.py
app/models.py
from datetime import datetime
from pydantic import BaseModel, EmailStr, Field
class LeadCreate(BaseModel):
full_name: str = Field(min_length=2, max_length=120)
email: EmailStr
company: str = Field(min_length=2, max_length=120)
class LeadPublic(BaseModel):
id: int
full_name: str
email: EmailStr
company: str
created_at: datetime
app/dependencies.py
from fastapi import Header, HTTPException
def require_api_key(x_api_key: str = Header()):
if x_api_key != "demo-secret":
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
app/services.py
def notify_sales_team(lead_email: str) -> None:
print(f"New lead received:{lead_email}")
app/main.py
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from .dependencies import require_api_key
from .models import LeadCreate, LeadPublic
from .services import notify_sales_team
fake_db: dict[int, LeadPublic] = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
print("Application starting")
yield
print("Application stopping")
app = FastAPI(
title="Life of Arjav Lead API",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health", tags=["System"])
def health():
return {"status": "ok"}
@app.post(
"/leads",
response_model=LeadPublic,
status_code=201,
tags=["Leads"],
)
def create_lead(
payload: LeadCreate,
background_tasks: BackgroundTasks,
_: str = Depends(require_api_key),
):
lead_id = len(fake_db) + 1
lead = LeadPublic(
id=lead_id,
full_name=payload.full_name,
email=payload.email,
company=payload.company,
created_at=datetime.now(timezone.utc),
)
fake_db[lead_id] = lead
background_tasks.add_task(notify_sales_team, str(lead.email))
return lead
@app.get(
"/leads/{lead_id}",
response_model=LeadPublic,
tags=["Leads"],
)
def get_lead(
lead_id: int,
_: str = Depends(require_api_key),
):
lead = fake_db.get(lead_id)
if lead is None:
raise HTTPException(status_code=404, detail="Lead not found")
return lead
Run it:
fastapi dev app/main.py
Create a lead:
curl -X POST "http://127.0.0.1:8000/leads" -H "Content-Type: application/json" -H "X-API-Key: demo-secret" -d '{
"full_name": "Aarav Mehta",
"email": "[email protected]",
"company": "Example Labs"
}'
Testing
from fastapi.testclient import TestClient
from app.main import app, fake_db
client = TestClient(app)
def setup_function():
fake_db.clear()
def test_health():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_create_lead():
response = client.post(
"/leads",
headers={"X-API-Key": "demo-secret"},
json={
"full_name": "Aarav Mehta",
"email": "[email protected]",
"company": "Example Labs",
},
)
assert response.status_code == 201
def test_invalid_email():
response = client.post(
"/leads",
headers={"X-API-Key": "demo-secret"},
json={
"full_name": "Aarav Mehta",
"email": "not-an-email",
"company": "Example Labs",
},
)
assert response.status_code == 422
pip install pytest
pytest -q
Production checklist
- Use environment variables or a secret manager.
- Put the API behind HTTPS.
- Use a real database and migrations.
- Use connection pooling.
- Set explicit CORS origins.
- Define response models.
- Add structured logs and request IDs.
- Add health and readiness checks.
- Add automated tests.
- Use a durable queue for important background jobs.
- Set timeouts for external calls.
- Add monitoring for latency, errors, and resource usage.
- Add rate limiting where abuse is possible.
The endpoint code is usually the easy part. Production quality comes from security, observability, failure handling, testing, deployment, and maintenance.
Epistemic status
- Confidence: 9/10 for the core explanations and patterns.
- What would change my mind: Major FastAPI, Pydantic, Starlette, or Python releases.
- Assumptions: Python 3.10+, current FastAPI, and Pydantic v2.
- Not covered deeply: SQLAlchemy architecture, full OAuth2, container orchestration, distributed tracing, and advanced performance tuning.
Official references
- FastAPI tutorial: https://fastapi.tiangolo.com/tutorial/
- FastAPI async: https://fastapi.tiangolo.com/async/
- FastAPI dependencies: https://fastapi.tiangolo.com/tutorial/dependencies/
- FastAPI background tasks: https://fastapi.tiangolo.com/tutorial/background-tasks/
- FastAPI lifespan: https://fastapi.tiangolo.com/advanced/events/
- FastAPI CORS: https://fastapi.tiangolo.com/tutorial/cors/
- FastAPI middleware: https://fastapi.tiangolo.com/tutorial/middleware/
- FastAPI errors: https://fastapi.tiangolo.com/tutorial/handling-errors/
- Pydantic models: https://docs.pydantic.dev/latest/concepts/models/
- Starlette: https://www.starlette.io/