Async Python in 2026: when FastAPI + uv is the right stack and when it isn’t

Elena Vasquez

Elena Vasquez

August 28, 2026

Async Python in 2026: when FastAPI + uv is the right stack and when it isn’t

I did not arrive at FastAPI because I love async. I arrived at it because I was tired of two things at once: Flask apps that grew a pile of untyped dicts, and Poetry lockfiles that made CI feel like a weather event. In 2026 the pairing I actually reach for is FastAPI plus uv. That is a real default for a certain shape of service. It is also a fashionable default for shapes that will punish you.

This is the decision I make on Monday, not the victory lap after a rewrite. When is FastAPI plus uv the right stack, and when do I walk away and pick Django, a sync worker, or a different language.

What I mean by the stack

FastAPI for the HTTP surface: Pydantic models, OpenAPI that is not a side quest, async routes when the work is waiting. Uvicorn or a process manager in front. A worker process that is allowed to exist, often still Python, sometimes not. uv for the toolchain: install, lock, pin Python, run. I do not mean “async everything.” I do not mean replacing a working Django admin because a talk said ASGI.

uv is the part people still underestimate. It is not a personality. It is the first Python packaging tool in years that I trust to make a clean CI runner boring. If your pain is “the API framework,” uv will not save you. If your pain is “every new laptop is a different pip story,” uv is the reason I will start a Python service in 2026 instead of apologizing for Python.

When FastAPI + uv is the right stack

The service is mostly I/O. JSON in, a database, another HTTP API, a queue, JSON out. That is waiting. Async Python is good at waiting if you do not accidentally block the loop. FastAPI is a clean place to put that waiting, with types at the boundary so the OpenAPI file is not fan fiction.

The consumers of the API are other engineers or a generated client. FastAPI’s generated docs are not a toy when a partner needs to see the shape of a webhook. I have sent a /docs URL to a contractor and watched them stop asking me for a Postman dump. Flask can do this with enough plugins. FastAPI does it if you write the models. That is the trade I want.

The team already writes Python. Data people will read the handler. An ML service already lives next door. A notebook will become a route, whether I like it or not. In that shop I do not start in Node to feel web-native. I start in FastAPI so the same people can follow the types from the training script to the request body. If the team is TypeScript and the product is a checkout, I still start that backend in Node.js. Language gravity beats framework fashion.

You want a lockfile that installs in seconds, not a poetry.lock that negotiates with the universe. uv is why I stopped treating Python packaging as a reason to pick Go. I still pick Go for pipes. I no longer pick Go because pip install hurt my feelings.

You need auth, validation, and a few background jobs, not an admin CMS. FastAPI is a good API framework. It is a mediocre content-management stand-in. If the product is “staff need a UI to edit rows tomorrow,” I look at Django first.

Dark desk with a terminal installing Python packages from a lockfile

The uv part, separately

I will use uv even when I will not use FastAPI. A Django app, a CLI, a worker — uv is the installer and the lock. That is an important split. “FastAPI + uv” is a blog pairing. In the repo they are two decisions. I have put uv under a Flask service I was not allowed to rewrite. The deploys got less stupid. The framework stayed Flask.

What uv is right for: deterministic installs, a single tool instead of pip-plus-venv-plus-pip-tools-plus-a-prayer, pinning the Python version next to the deps. What it is not: a reason to rewrite a working tox matrix this sprint. If the company has a blessed internal index and a working pip-tools pipeline, I do not burn a month for a faster resolve. I introduce uv on the next greenfield, or on the service whose CI is already a joke.

The failure mode I have seen: someone adds uv, leaves a half-dead Poetry config, and now onboarding has two stories. Pick one toolchain per repo. Document the one command. Delete the other file.

When I would not start FastAPI

The shop already runs Django well. Admin, ORM, auth, a decade of apps. I do not start the next HTTP surface in FastAPI to “be modern.” I add a small FastAPI app only if it is a separate process the Django team will not own — a model server, a webhook sidecar. Otherwise I stay in Django, maybe on ASGI if they are already there. Two Python web stacks is a hiring and on-call tax. I have paid it. It was not worth a prettier /docs.

The work is CPU. Pandas on the request path. Image work. PDF rendering. A “quick” sklearn call. Async will not save you. It will hide the stall until the p99 becomes a war story. I put that work in a worker, and I am honest about whether the worker should stay Python. Sometimes yes, because the library is Python. Sometimes the API is FastAPI and the worker is not. FastAPI as a costume on a data job is how you get an event loop that is a queue with extra steps.

The libraries you must call are sync and you will not wrap them carefully. I have seen requests in an async route, a blocking SDK, a database driver used wrong. FastAPI will let you write that. Uvicorn will serve it. Then one slow call holds a worker and everyone calls it “async performance.” If the team does not yet have the reflex to hunt blocking calls, I start sync — Flask or Django or Starlette sync — and add async when we can name the wait. Teaching async and shipping a checkout in the same week is how you get both wrong.

You need a lot of HTML, forms, and a session cookie for a staff app. FastAPI can do templates. I do not want it to. Django or a small HTMX app on a boring framework will ship the admin faster. I have rebuilt a “simple internal tool” in FastAPI and then reinvented CSRF, flash messages, and an auth story Django already had.

Engineer watching a CPU meter spike on a second monitor while an API waits

When I would not start uv, either

Rare, but real: a regulated build that already signs a specific pip toolchain, and changing the installer is a paperwork event. I do not pick a fight with compliance for a faster resolve. I pick the fight when the current installer is already failing.

Also rare: a monorepo that has standardized on one internal wrapper. I wrap uv inside the wrapper or I wait. Drive-by toolchain changes in a 200-package repo are how you get a week of “works on my machine” from people who did not ask for a new verb.

Async Python is still easy to get wrong

The 2026 story is not “async is ready.” Async has been ready. Teams are still not. The bugs I still see: blocking calls in async routes, shared state across tasks, connection pools sized for threads, tests that pass because they never hit concurrency, asyncio.run nested inside a running loop because someone copied a snippet.

I start FastAPI async when the first real wait is a network call we own. I start FastAPI with sync routes when the first real wait is a library I do not trust. FastAPI allows both. That is a feature. Using async as a default adjective on every def is not.

Cancellation and timeouts are part of the stack decision. If you cannot say what happens when the client hangs up, you are not ready for a fleet of async handlers. I set timeouts at the HTTP client and at the gateway. I do not assume the framework will save a runaway downstream. That is true in Node too. Python’s version just looks like a stuck event loop and a rising backlog.

The alternatives I actually consider

Django, when the product is a system of apps and an admin, not a single API. Django Ninja or Django REST if we are already in that house and I only need a typed API. I do not extract FastAPI to feel cleaner.

Flask, when the surface is tiny and the team already has the muscle memory. I do not start new Flask for a public API I will generate clients from. I will keep Flask for a webhook that is 80 lines and a cron.

Go, when the service is a pipe or the on-call does not want Python in the fleet. Python stays where the libraries are. Go stays where the binary is the product.

A notebook and a job, when there is no HTTP yet. I have watched people wrap a batch in FastAPI so they could say they had an API. They had a batch with extra failure modes. If the trigger is a schedule, start with a job. Add HTTP when a human or another service needs to call it.

A checklist I use on day one

  • Is the first year of this service mostly waiting on I/O?
  • Is Python already the language the owners can debug at 3 a.m.?
  • Do we need a generated OpenAPI surface more than we need an admin?
  • Can we keep CPU work off the request path from day one?
  • Will the team treat async def as a contract, not a style?
  • Is packaging currently a tax — and is this repo allowed to change the tax?

Four yeses and I start FastAPI plus uv. Two yeses and I do not. The last question is how uv gets in without FastAPI. They are allowed to travel separately.

What I tell a team that already picked it

If you already have FastAPI and the loop is healthy, do not rewrite it because someone likes Django’s admin. Add an admin another way, or add a small Django next to it if staff UI is the product now. If you already have FastAPI and the loop is sick, do not add more async. Find the blocking call. Move the CPU. Then talk about frameworks.

If you have FastAPI and pip and CI is slow, add uv. That is the cheap win. If you have Poetry and it works, put uv on the next service. I am not a purist about one installer across a company. I am a purist about one installer per repo.

FastAPI plus uv is the right stack when you want a typed HTTP API, Python people on the page, and a toolchain that does not make onboarding a folklore project. It is the wrong stack when you are hiding a data job behind ASGI, splitting a healthy Django house, or asking a team that still uses requests in async routes to grow up in public. I will keep choosing it for the first kind of service. I will keep refusing it for the second. The framework is fine. The costume is not.

More articles for you