I've been writing Flask for years. I've shipped Flask apps for document management systems, internal admin tools, and more than a few "API by Friday" startup emergencies. One time, "by Friday" meant "we demo in two hours," and yes, the CEO was already screen-sharing.
FastAPI, on the other hand, keeps showing up in job postings like it pays rent. And job postings are weather reports: ignore them if you want, but you'll still end up outside holding a laptop in the rain.
A client recently asked for an API that other teams would consume, with typed models, clean docs, and fewer "why is this endpoint returning a string sometimes?" surprises. I stopped pretending FastAPI was a fad and learned it. Turns out it's not a fad. It's just better at yelling at you early, which is the only kind of yelling I've learned to appreciate.
Here's what changes in your head-and how to port a tiny CRUD app without setting your week on fire.
The shift that matters (what changes in your brain)
If you're a Flask developer, the good news is: routes still feel like routes. Decorators still exist. URLs still map to functions.
What changes is who does the boring work.
-
Automatic request validation: FastAPI uses Pydantic models to validate input. Bad input gets a clean 422 response automatically. I can't not fix the flaw here: once you've seen this, going back to hand-rolled
if "name" not in datachecks feels like choosing to mop the floor with a toothbrush.
Micro-anecdote: I once shipped a Flask endpoint that accepted"true",true,"1", and1for the same flag. Support called it "flexible." I called it "a future incident." -
Dependency Injection (DI) that's spelled out: In Flask, it's easy to lean on
gandcurrent_appand quietly pass state around. That's fine until you're debugging a request that depends on a global that depends on a config that depends on a monkey patch. FastAPI makes dependencies obvious and in your face.
Micro-anecdote: I've had a bug where auth worked in staging and failed in prod because somebody setg.userin one blueprint and assumed it existed everywhere. I spent ten minutes staring at the terminal like it owed me money. -
Async is first-class: You can write
async defendpoints and run an async server without duct tape. Flask can do async-ish now, but FastAPI was born in it. (I was born in the early 80s; not the same vibe.)
Micro-anecdote: the first time I mixed a sync DB call into an async endpoint, my "fast" API moved like an LA freeway in Die Hard 3. Technically traffic was flowing. Spiritually, it was not.
Flask CRUD (before)
Here's a minimal Flask-style "create item" endpoint:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post("/items")
def create_item():
data = request.get_json()
if "name" not in data:
return jsonify({"error": "name required"}), 400
return jsonify({"id": 1, "name": data["name"]}), 201
This works. It also quietly encourages you to copy the same parsing + validation + error shape into every endpoint, forever. Flask is polite like that. It lets you do anything. Including re-inventing input validation badly.
Micro-anecdote: I once found three different error formats in the same Flask API. One endpoint returned {"error": "x"}, another returned {"message": "x"}, and a third returned plain text. The frontend team learned new words that day.
FastAPI CRUD (after)
Now the FastAPI version:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemIn(BaseModel):
name: str
@app.post("/items", status_code=201)
def create_item(item: ItemIn):
return {"id": 1, "name": item.name}
What got easier:
- Validation: missing fields and wrong types get handled without you writing a pile of checks.
- Errors: responses are consistent, which reduces "creative interpretation" by clients.
- Docs: OpenAPI docs show up automatically, which means fewer homegrown Postman collections that rot the second you look away.
Docs are there by default at /docs and /redoc. That's it. That's the whole pitch.
Micro-anecdote: I've watched a PM "test" an API by clicking around Swagger UI and declare it ready for launch. I didn't correct them. I just shipped it faster.
"Where did my app context go?" (and why you might not miss it)
In Flask, it's common to stash things in global-ish places: DB connections, auth state, feature flags, per-request state.
In FastAPI, you push that through dependencies via Depends. It's a bit more typing up front. Then it pays you back in tests, clarity, and fewer ghost bugs.
Micro-anecdote: I once inherited a Flask app where a feature flag lived in current_app.config, got overridden in a request hook, and then leaked between requests. It was like debugging a haunted thermostat.
What's different (and will trip you once)
A few things will get you. One will get you while you're sure you're doing everything right.
- There's no
flask run.
I typed flask run out of muscle memory and waited. Nothing happened. I stared at the terminal for ten minutes like it owed me money. You'll usually do this instead:
bash
uvicorn main:app --reload
-
Pydantic models everywhere: requests, responses, configs. You'll write more schemas than you expect. The upside: you stop guessing what the API accepts and returns. The downside: schema sprawl. You start with
ItemInandItemOutand end up withItemMaybeOut,ItemOutButWithOwner, andItemOutButLegalSaysNoAddress.
Micro-anecdote: I once added a "tiny" field and touched six models. I learned a lot about my own optimism. -
Async works great, but your stack has to match: DB driver, HTTP clients, everything. If half your stack is sync, you don't get async performance. You get a traffic jam with nicer lane markings.
Micro-anecdote: I've seen a team celebrate "we're async now" while their biggest call was still a blocking request to an ancient internal service. The graphs didn't care about the celebration.
Ecosystem reality check (Flask still has perks)
Flask's ecosystem is older. There's a Flask extension for everything.
Need to integrate with some obscure payment processor from 2014 that only one dentist in Florida still uses? There's probably a Flask-Obscure-Processor package for it. FastAPI is growing fast, but you'll still hit gaps and wire things yourself sometimes.
Micro-anecdote: I once spent an afternoon reading Starlette middleware code because the "official" integration didn't exist yet. It was fine. I just aged a little.
My actual recommendation (based on shipping and supporting real stuff)
I still reach for Flask when I'm building a small internal tool, staying synchronous, or leaning on a weird old extension that's been battle-tested for a decade.
But for APIs that other teams will consume, FastAPI wins. The validation and automatic docs prevent nonsense by default. And when you're the person who gets the "your API is broken" email at 7 a.m., preventing nonsense becomes a lifestyle.
By the way, if your API is going to call LLMs or other services, this guide on calling LLMs from Python is a handy rescue rope-especially once you start caring about async and timeouts.
-Sethers