Skip to content

FastAPI vs Django: which one should you build your backend with?

My rule: FastAPI for APIs, Django for fullstack applications. The performance case for FastAPI, where Django rigidity costs you, and how to build APIs in Django with DRF, Django Ninja or Django Bolt.

  • python
  • fastapi
  • django
  • api

This is a personal take, not a balanced scorecard. I have build products with both, and the rule I actually apply is short: FastAPI for APIs, Django for fullstack applications.

That is not a compromise or a hedge. Django is one of the best frameworks Python has, and for a fullstack application with a database, users and an admin, it is very hard to beat. It is also, in my experience, the wrong tool for a service whose entire job is to accept requests and return JSON, and the most flexible one to shape around a business that does not fit anybody’s template.

FastAPI for APIs

FastAPI is built around a simple premise: you describe the shape of your data in Python type hints, and you get validation, serialisation and an OpenAPI schema for free. The type hints are not documentation you maintain separately. They are the single source of truth, and everything else is derived from them.

That derivation is the real value. When you add a field to a model, the validation, the docs and the client-facing schema all move together. There is no second place to forget to update, and no way for the docs to drift from the behaviour.

But the reason I default to it for services is performance, and it is worth being concrete about where that performance actually comes from.

The performance case

Async is the default, not a retrofit. This is the big one. An endpoint that calls a payment provider, a CRM and a notification service does not do it sequentially. await runs those calls concurrently, so the request costs one network round trip instead of three. Most backend latency is waiting on something else, and FastAPI removes that waiting by default.

Pydantic v2 validation is written in Rust. A framework that validates every request body, query parameter and response model is adding work to your hot path. Pydantic v2 moved its core to Rust and is fast enough that you stop thinking about it and start using it everywhere: request bodies, query parameters, headers, response models, settings, even the objects you pass between services.

No hidden layer between you and the response. FastAPI is Starlette plus the type system. There is no generic view, no content negotiation you did not ask for, no middleware stack you inherit by default. response_model is explicit, so you never accidentally serialise a full ORM object with a password hash on it because the framework decided the fields for you.

Dependencies are the injection system, and it is cheap. Depends replaces a large amount of the wiring you write by hand in a Django API: authentication, per-request database sessions, feature flags, tenant resolution, rate limiting. It is ordinary Python composition, and it costs almost nothing at runtime.

Flexibility: mapping the app to the actual business

The other reason I reach for FastAPI is that it does not have an opinion about how your application should be shaped.

Django has a structure, and that structure is the reason Django projects look alike. Models in one place, migrations generated, a specific URL layout, a specific way to do authentication, a specific way to do forms, a specific way to write a management command. Follow it and you get a maintainable codebase for free. That is not a flaw — it is the trade you are making.

The cost appears when the business does not fit the mould. A workflow that is event-driven rather than request-response. Data that is not naturally relational. An API that mirrors a third party’s contract rather than your database schema. An auth model with unusual rules. Multi-tenancy with a shape Django’s QuerySet does not naturally express. Then you are not fighting a bad framework, you are fighting a framework that is optimised for a different application shape, and you end up writing the awkward abstraction anyway, except now it is wrapped around someone else’s structure.

With FastAPI you map the application onto the exact business requirements. You choose the persistence layer, the session strategy, the project layout, the way requests are authenticated, the granularity of your endpoints. There is no project generator telling you where things go, and that is the point: nothing between the business requirement and the code. The flexibility is also the downside, and I will not pretend otherwise — the discipline of “the Django way” is a real reason Django projects stay maintainable with small teams. FastAPI gives you the freedom to build it exactly your way, including the freedom to build something badly structured.

Django for fullstack applications

When the product has a human being logging in to click things, I use Django.

The ORM is superb, and the migrations, the admin interface and the built-in auth are a decade of accumulated answers to questions you would otherwise spend two weeks asking. The admin alone justifies the framework for a large share of internal tools and back-office features, because a large share of those are, in essence, a database table plus a nice interface.

I have built my personal projects with it: a chatbot that answers questions about Romanian fiscal legislation, a social media scheduling app, an accounting app for Romanian freelancers. Different domains, same answer, because in each one there are models, users, forms, an admin and background jobs. Django is the shortest route from that list to a working product.

And it develops fast in a way that is easy to underestimate. Django has barely changed in shape over the last decade. The same patterns, the same docs, the same answers, the same idioms — and that stability is a genuine engineering asset when a lot of that code still has to work in three years.

It is also worth saying that coding assistants know Django. The framework has not moved, so the model has seen ten years of correct, idiomatic Django in its training data. Generated Django code is right far more often than generated framework code in general. When a project moves fast and half of it is written with an assistant, that is a real input to the decision and I would rather not pretend it is not.

FastAPI is also great for APIs, obviously. And the admin and the ORM that come with Django are great, for the projects that need them.

If you build APIs in Django anyway

You should not read the above as “Django cannot do APIs”. It can, and there are three real options.

Django REST Framework is the mature one, and the one with the longest production track record by a distance. Lots of years in production, which means every problem you will hit has been hit, documented and answered before you hit it. The cost is that it is verbose — a ModelViewSet, a serializer class, a router registration, a paginated response, a permissions class, and the same five blocks again for the next endpoint — and it is synchronous, built on Django’s WSGI request handling. The boilerplate is the price of the maturity.

Django Ninja is the newer, better default for new work. Pydantic-based, so schemas work the way they do in FastAPI, and the OpenAPI schema is a by-product rather than a project. It is what I would pick over DRF today.

Django Bolt is the newest of the three, a Rust-powered API layer mounted inside Django, in the FastAPI style. The benchmark numbers are interesting.

The honest caveat applies to the last two: neither Django Ninja nor Django Bolt has the years of production use that FastAPI has, or that DRF has. For a side API inside an application you already own, that risk is small and the payoff is real. For the critical public interface of a product with real traffic, I would take a chance and use Django Ninja. I have built a prospecting tool with Django and Django Ninja. The Django sync part took care of the admin crm and Django Ninja took care of the incoming data that filled the prospecting database. Quite a similar experience with FastAPI, not bad.

The comparison

FastAPI Django Django + Ninja/Bolt
Validation From type hints, automatic Forms and models, or explicit serialisers From Pydantic, automatic
API docs Generated OpenAPI Generated, with more effort Generated OpenAPI
Admin interface Not included Included, mature Included, mature
ORM and migrations Your choice Built-in, mature Built-in, mature
Concurrency Native async Sync by default Sync by default
Shape of the app Yours to define The Django way The Django way
Background jobs Celery, Taskiq, Dramatiq Celery, Taskiq, Dramatiq Celery, Taskiq, Dramatiq
Production track record Long Very long Younger
Best for APIs and services Fullstack applications Fullstack apps that expose an API

How I actually choose

When the frontend is separate — React, Vue, Svelte, anything that talks to the backend over HTTP — I use FastAPI. The API is the product, async fan-out matters, and nobody needs the admin. Svelte is my favourite of those three to build the client against, mostly because a typed OpenAPI schema means the frontend and backend can be developed against the same contract independently. In some cases I may prefer to use SvelteKit if Python specific libraries are not needed.

When I am going with a smaller team, moving fast, I use Django. Every question has a well-known answer, the assistants generate it correctly, and the admin means half the internal requests never become features. I would rather my small team spend its energy on the product than on assembling a stack. Not great DX, but it’s good enough.

The mistake I see most often is picking the framework before deciding what the system is for, then spending the first month building things the other one would have provided. Decide the constraints first.

If you are weighing this up for something you are actually shipping and want a second opinion, tell me what you are building and I will tell you which one I would reach for and why.

Found a process worth automating?

Tell us about it. One email, no forms, no sales sequence.

contact@softgata.com

Keep reading