7 Things I Wish I Knew About Python FastAPI

Clean code, better structure, and fewer bugs — that’s what these 7 lessons gave me.

7 Things I Wish I Knew About Python FastAPI
Photo by Coley Christine on Unsplash

FastAPI looked easy at first — until I hit production. Here’s what I wish I had known sooner.

7 Things I Wish I Knew About Python FastAPI

When I first discovered FastAPI, I was blown away.

A modern, lightning-fast Python web framework with built-in support for async, automatic docs, and type hints? Count me in.

But, like most powerful tools, FastAPI has its nuances. Some things I learned the hard way. Others I wish I’d known earlier.

So, whether you’re just getting started or looking to deepen your FastAPI knowledge, here are 7 things I wish I knew about FastAPI — that could save you hours of frustration and supercharge your backend development.


1. Pydantic Is the Heart of FastAPI

I initially thought of FastAPI as “just another Flask-like framework” — but it’s much more. At the core of FastAPI is Pydantic, the library that powers FastAPI’s request validation, serialization, and documentation.

What I learned:
Understanding Pydantic deeply (especially how it handles type coercion, custom validators, and nested models) makes you 10x more productive with FastAPI.

from pydantic import BaseModel 
 
class User(BaseModel): 
    name: str 
    age: int
  • Incoming JSON is automatically validated
  • You get a free OpenAPI schema
  • And yes, type hints actually matter here!

2. You Can Mix Sync and Async (But Know When Not To)

FastAPI supports both synchronous and asynchronous route handlers. That sounds cool — until you mix them without understanding the consequences.

What I learned:

  • Use async def only when you're calling non-blocking I/O (e.g., databases, HTTP requests).
  • Using async with blocking code (like traditional ORMs or file operations) can wreck performance.
# Don't do this with sync DB call 
@router.get("/users") 
async def get_users(): 
    return db.fetch_all_users()  # blocking!

Use libraries like SQLModel, encode/databases, or asyncpg when working with async.

3. The Auto Docs Are Incredible — and Customizable

FastAPI gives you OpenAPI docs at /docs and /redoc out of the box. What I didn’t know? You can customize nearly everything:

  • Add descriptions, examples, and metadata
  • Group endpoints with tags
  • Control schema names and versions
@app.get("/items/", tags=["Inventory"], summary="Get all items") 
def get_items(): 
    return [{"id": 1, "name": "Widget"}]

This isn’t just a bonus — it’s a developer onboarding superpower. Your API becomes self-explanatory.

4. Dependency Injection Is Clean, Powerful, and Scalable

FastAPI’s dependency injection system lets you share logic across endpoints (e.g., authentication, database sessions, configuration).

What I learned:
You can create reusable, testable components for almost anything.

from fastapi import Depends 
 
def get_current_user(token: str = Depends(oauth2_scheme)): 
    # Decode and return user from token 
    return decode_token(token) 
 
@app.get("/me") 
def read_me(current_user = Depends(get_current_user)): 
    return current_user

This structure scales well and reduces boilerplate.

5. Middleware Is Your Secret Weapon

If you’ve used Express.js or Flask middlewares, FastAPI’s @app.middleware might feel familiar—but more Pythonic.

Middleware can:

  • Log requests
  • Handle CORS
  • Track execution time
  • Modify request/response
@app.middleware("http") 
async def log_requests(request, call_next): 
    response = await call_next(request) 
    print(f"{request.method} {request.url} -> {response.status_code}") 
    return response

Add these early in your app structure to keep concerns cleanly separated.

6. Background Tasks = Easy Async Work Offloading

Sometimes you want to respond fast but also do work in the background (like sending emails or writing logs). FastAPI gives you a BackgroundTasks object—no Celery setup required.

from fastapi import BackgroundTasks 
 
def send_email(email: str): 
    # imagine sending email here 
    ... 
 
@app.post("/register") 
def register_user(email: str, background_tasks: BackgroundTasks): 
    background_tasks.add_task(send_email, email) 
    return {"message": "User registered!"}

Perfect for small async jobs — without adding complexity.

7. Testing FastAPI Is Surprisingly Enjoyable

Most frameworks make testing a bit of a chore. FastAPI is different.

With the built-in TestClient (powered by requests), you can write clean and fast tests for routes, dependencies, and integrations.

from fastapi.testclient import TestClient 
 
client = TestClient(app) 
 
def test_read_main(): 
    response = client.get("/") 
    assert response.status_code == 200

Bonus: Dependency injection makes mocking simple!


Final Thoughts

FastAPI is easily one of the most exciting Python frameworks in recent years. It’s fast (really fast), intuitive, and perfect for building robust APIs quickly.

But the magic only truly reveals itself when you go beyond the basics.

So if you’re new to FastAPI or revisiting it after a while, I hope these lessons help you write cleaner, faster, and more scalable APIs — without the late-night debugging sessions.


Enjoyed this?
Follow me for more Python backend tips and stories from the trenches of modern API development.

Let me know in the comments: What’s your favorite FastAPI trick?

Photo by Toa Heftiba on Unsplash