June 30, 2026 · Backend · Performance
Migrating a Python API from Flask to FastAPI: what actually changes
FastAPI isn't a drop-in replacement for Flask — it changes how request handling, validation, and concurrency work. Here's what mattered in practice, migrating a live public-sector API.
04 From building PSEB Chatbot"Migrate to FastAPI for better performance" undersells what actually changes. Flask is a WSGI framework — synchronous by default, one request occupies one worker thread until it returns. FastAPI is built on ASGI, so a request that's waiting on a slow downstream call (an LLM API, in this case) doesn't have to block a whole worker while it waits.
On the PSEB chatbot, that distinction was the actual performance problem: under concurrent load, a Flask deployment with a fixed worker count queues incoming requests the moment every worker is blocked waiting on an upstream response. Moving to FastAPI's async request handling let the server keep accepting and progressing other requests while any one of them waits.
What the migration actually touches
- Route handlers become `async def`, and any blocking I/O inside them (a synchronous HTTP client, a blocking DB driver) has to become async too, or you've just moved the blocking problem rather than fixed it
- Request and response validation moves to Pydantic models, which replaces a lot of manual `request.json.get(...)` defensive code with declared types the framework validates before your handler even runs
- Automatic OpenAPI docs come for free from those same type-annotated models — useful, but not the reason to migrate
- Middleware and error handling patterns are different enough that a line-by-line port doesn't work; the request lifecycle itself is a different shape
Where the win actually came from
The measurable improvement wasn't from FastAPI being faster to execute a single request — it was from the server no longer stalling under concurrent load the same way. A synchronous framework degrades hard once every worker is occupied; an async one degrades more gracefully because requests waiting on I/O aren't holding a worker hostage.
This is also why the migration isn't automatically worth doing: if your endpoints are CPU-bound rather than I/O-bound — heavy computation rather than waiting on other services — async buys you much less, and the migration cost may not be worth paying.