DEV Community

Moon Robert
Moon Robert

Posted on • Originally published at blog.rebalai.com

FastAPI vs Django vs Flask: What I Actually Use After Testing All Three in 2026

Three weeks ago I needed to pick a framework for a client project — a B2B analytics API with roughly 200 daily active users, a team of three backend engineers including me, and a hard requirement to expose several LLM inference endpoints. I've shipped production apps on all three of these frameworks at different points in my career, but I hadn't done a real side-by-side comparison in over two years. So I spent two weeks spinning up the same feature set in each one and taking notes.

Here's what I found.

FastAPI Has Quietly Become the Default for New Python APIs

When FastAPI first blew up, I was skeptical. Another micro-framework? Python doesn't exactly have a shortage of those. But FastAPI 0.115 (released late 2025) has matured in ways that make it genuinely hard to argue against for API-first projects.

The Pydantic v2 integration is the main thing. With Pydantic v2 as the validation layer, you're getting roughly 5-10x faster serialization compared to v1 — and FastAPI leans into this hard. Every request and response goes through Pydantic models, which means automatic validation, clear error messages, and OpenAPI docs generated from your actual code. Not from some YAML file you'll forget to update two sprints in.

Here's roughly what my inference endpoint looked like after about an hour of setup:

from fastapi import FastAPI, Depends, HTTPException, Header
from pydantic import BaseModel
from typing import Annotated
import httpx

app = FastAPI()

class InferenceRequest(BaseModel):
    prompt: str
    max_tokens: int = 512
    temperature: float = 0.7

class InferenceResponse(BaseModel):
    output: str
    tokens_used: int

# simple auth dependency — swap this for OAuth in prod
async def verify_key(x_api_key: str = Header(...)) -> str:
    if x_api_key != "my-secret":  # obviously use env vars
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key

@app.post("/infer", response_model=InferenceResponse)
async def run_inference(
    req: InferenceRequest,
    _key: Annotated[str, Depends(verify_key)],
):
    # async HTTP call to your actual model server
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "http://model-server/generate",
            json=req.model_dump(),
            timeout=30.0,
        )
    data = resp.json()
    return InferenceResponse(
        output=data["text"],
        tokens_used=data["usage"]["total_tokens"],
    )
Enter fullscreen mode Exit fullscreen mode

The dependency injection system is where FastAPI genuinely surprised me. I expected Django-style middleware overhead dressed up in new syntax, but it's cleaner than that — dependencies compose naturally, they can themselves have dependencies, and everything works with async just fine.

The gotcha that bit me on week one: FastAPI's DI system is powerful enough that you can accidentally build a dependency graph that's three layers deep before you realize you've made something unmaintainable. I started nesting auth → rate limiting → user context into one chain and debugging that at 11pm on a Thursday was genuinely miserable. Keep individual dependencies small and single-purpose. I didn't, initially, and paid for it.

One broader observation: the FastAPI community has standardized on certain patterns — repository pattern for database access, service layers, etc. There's no single blessed project structure from the FastAPI authors, which for a small team is actually fine. For a larger org onboarding junior engineers? You'll spend real time establishing conventions that Django would've given you for free.

Django 5.1 Is Still the Right Call for Full-Stack or Complex Domain Logic

Django gets grief for being "heavy," and sometimes that's fair. But people consistently underestimate how much complexity Django is silently absorbing.

I built a version of the analytics dashboard in Django 5.1, and the thing that saved me the most time wasn't the ORM — it was the admin panel. Within three hours I had a fully functional internal interface with filtering, search, CSV export, and per-row permission controls. If you've ever built that from scratch, you know that's easily a week of work. Django gives you 80% of it for free with ModelAdmin configuration.

The ORM is still genuinely excellent, especially now that async support has stabilized. Django 5.x async views actually work — no weird workarounds, no sync_to_async wrappers for every database call. That was a real pain point as recently as Django 4.2 and it's mostly resolved now.

For pure API work, though, Django's project structure fights you. You're hauling around the templating engine, the sessions framework, CSRF middleware — none of which you need. You can strip most of it out, but it's config friction: INSTALLED_APPS cleanup, middleware you have to manually disable. It works. It's just not the path of least resistance.

I'd reach for Django when there's meaningful business logic in the domain model, when an admin or CMS UI is needed, or when the team is mixed-seniority and benefits from conventions enforced by the framework. SaaS products with user accounts, billing, roles, permissions — Django's built-in auth system alone is worth the overhead there. Pure microservices or ML inference APIs? Probably not.

Flask 3.1: The Minimalist Case Is Getting Harder to Make

Flask 3.1 is a solid, mature framework — and I came out of this comparison with the uncomfortable feeling that it's lost its positioning. That's not easy to write; I've shipped a lot of Flask.

Flask's pitch has always been "start simple, add what you need." A Flask app can be 20 lines. The Blueprints system for organizing larger apps works fine. The extension ecosystem covers most needs. But FastAPI has made "simple" feel cheap by comparison. You get async, automatic validation, API docs, typed request and response models out of the box. Flask gives you none of that — you're bolting on marshmallow or Pydantic manually if you want validation.

I pushed a Flask API to production on a Friday afternoon — always a mistake, I know — and spent the weekend tracking down a bug where missing request fields were silently set to None rather than raising validation errors. Pydantic would have caught this at startup. Flask just trusted me, and I was wrong.

Where Flask still makes sense: scripting-heavy internal tools where you want an HTTP interface without ceremony, short-lived prototypes, or integrating with a legacy codebase that's already Flask-shaped. Deep team expertise and established internal conventions are also a real argument — there's genuine cost to switching frameworks mid-product.

But for something new in 2026, if the reasoning is "it's simpler than Django" — I'd push back. FastAPI is just as simple to start with and gives you significantly more infrastructure for free.

The AI/ML Angle Is Where FastAPI Really Pulls Away

Async-first matters a lot when you're calling LLM APIs or model servers. Calls to Anthropic or OpenAI endpoints are slow — sometimes 5-30 seconds for longer generations. In a synchronous framework, each in-flight request blocks a worker. At small scale it doesn't matter. At moderate concurrency — 50 simultaneous users — you can saturate your worker pool fast.

FastAPI's async model handles this cleanly. I ran a basic load test: 50 concurrent users, each hitting an endpoint that calls claude-sonnet-4-6 and waits for a full response. The async FastAPI version handled it with roughly 8x fewer workers than an equivalent synchronous Flask setup. I won't claim that number holds across all model latency profiles, but the directional result was consistent across several runs with different prompt lengths.

Django 5.1's async views are much better than they were, but you still hit friction mixing sync ORM calls with async views — sync_to_async is still necessary in some spots and adds cognitive overhead. FastAPI with SQLAlchemy's async ORM (or Tortoise ORM) gives you async all the way down with less mental juggling.

One thing that genuinely confused me at first: streaming responses. If you want to stream tokens back to the client as they arrive — the way most chat UIs work — FastAPI's StreamingResponse class and Server-Sent Events do handle it cleanly. But the docs kind of bury this use case. I found the right pattern after digging through a GitHub discussion thread and several Stack Overflow answers, not the official documentation. Worth figuring out early if you're building LLM-facing endpoints; don't assume it's obvious from the docs.

What I'd Actually Pick

FastAPI is my default for new projects in 2026. The type-driven development model, native async, Pydantic v2 performance — it's a better foundation for API services than Flask, and for pure API work it's lighter than Django without sacrificing much. For a team of 3-4 engineers building backend services, this is the path of least resistance with the most payoff.

Django I'd pick specifically when domain complexity justifies it: multi-role user systems, complex permission models, reporting dashboards that need an admin UI, or teams larger than five where the opinionated structure helps with onboarding. I worked on a Django 5.x SaaS project last year with a team of eight and the framework's conventions genuinely helped. That's real value — just not universal value.

Flask — honestly, probably not for a new project in 2026. I have genuine fondness for it. But FastAPI has taken its "simple API" niche and done it better. The main reason I'd still reach for Flask is inertia: existing codebase, institutional knowledge, existing integrations. Valid reasons. Just not a reason to start fresh with it.

What I'm actually running for the current client: FastAPI for the API layer, async SQLAlchemy for the database, and a thin Django-only app for internal admin tooling — both in the same mono-repo. It's less weird than it sounds, and it means we're not rebuilding Django admin from scratch in FastAPI. Trust me, you don't want to do that.

The harder problem is usually not the framework; it's the team and the domain. But the framework still matters, and in 2026, FastAPI earns the default slot.

Top comments (0)