Django needs a REST story

This is just not true for a whole variety of web applications. The reality of the modern web is that you do need REST if you’re using any amount of frontend framework, a mobile app, or have more than one service.

This is the best illustration of the necessity of good REST support. If people are using FastAPI over Django just for its REST/serialization layer, that indicates that Django is severely lacking in this department.

The Django community page already recommends both DRF and Django Ninja, and I believe that option 3 (have more on these in the Django docs) should be about both and why you would pick one over the other.

I personally recognize the age and challenges of DRF. I also think Django Ninja is too “FastAPI-like” in how it does its views. I’d personally prefer to use Pydantic based serializes with either DRF style views or Django Forms style class-based views, but that’s my personal preference.

Preferences vary, which is why I believe Django should build it’s own solution long-term, and that solution should be minimum viable infrastructure that is modular (allowing users to easily replace parts with community or homebrew alternatives). I think Django shipping API views with a modular serializer layer would be a great initial version of this feature, especially if these views were already compatible (or nearly compatible) with DRF and Django Ninja serializers.

3 Likes

+1 for Option #1.

I’m not a fan of DRF’s Rails inspired APIs and features. They’re awkward. I’d much prefer the Django project do its thing and create the kinds of Pythonic/Djangonic apis that they’ve been making for the past 20 years. This is a simple problem that needs a simple solution.

This is the best illustration of the necessity of good REST support. If people are using FastAPI over Django just for its REST/serialization layer, that indicates that Django is severely lacking in this department.

What I meant was that FastAPI has probably been a breath of fresh air for anybody forced to work in DRF, which has been detrimental to the continued uptake of Django for new projects that involve REST. Recommending DRF more strongly, or integrating its philosophy directly into Django, would be a bad idea (that’s the non sequitur I was referring to). I’ve worked with at least one person who, if Django Ninja existed one year earlier, they would have never switched to FastAPI.

I’d personally prefer to use Pydantic based serializes with either DRF style views or Django Forms style class-based views, but that’s my personal preference.

I definitely agree with the Pydantic part and the latter (CBV) part of this.

I also think Django Ninja is too “FastAPI-like” in how it does its views.

I also agree with this, to a certain degree.

With all that said, if I had to pick between the way DRF does things and physically mailing envelopes with printed JSON, to each API user, I’d pick the latter.


I think some of the primitives can be put into Django without being too opinionated (aside from maybe supporting Pydantic), but I think any attempt to do so should start with option 1, sub-task 1, by simply providing the validation and serialization, and perhaps some small foundational work for sub-task 2 (CBV).

3 Likes

i agree. it’s also very hard to compare the developer experience of the drf view layer to the fast api view layer. if we’re building an internal app that’s not going to care about content negotiation, permissions, throttling, etc, I could sympathize with the feel of bloat. however, it was pretty nice the one time i needed to support message pack along with json. or the few times i’ve needed to support form data as well as json. that is part of the “batteries” that i tend to appreciate over time.

The issuewith DRF is that the serializers arelast generation.

besides slow, what specifically does last generation mean? do you mostly mean the developer experiences that come from using runtime type hints?

I meant only that they’re slow.

Look at the benchmarks on the Django Ninja site. One is nonsense: it’s comparing async views to sync views with a blocking IO operation. The whole point of async IO is that async will process more requests in this kind of case. The other though is showing the contrast between older and more modern serialisation. There are significant speed ups to be had.

There’s a nice talk from PyCon Italia by Tin Tvrtkovic, the author of cattrs that explains what’s going on here:

The typing issue is totally separate. This gets into the weeds very quickly: I would hope not to derail this conversation by a tangent into typing.

Aborted musings…

For simple cases type hints are lovely:

from dataclasses import dataclass


@dataclass
class MyDTO:
     name: str
     count: int 

Wonderful.

When it gets more complex though, needing validators and all the rest of it, that we pass to a field, the examples aren’t any different in kind from Django’s forms.CharField(...) &co that we have. IRL these more complex cases are often the norm. As, then, is often the case, the actual complexity of the problem means that the great simplest-case demos don’t really continue to deliver. I look at many examples using Annotated and realise we live in different worlds.

There are DX gains to be had from any fresh API design — learning the lessons of history. Type hint support (for those simple cases) would be great. But it’s not the New Jerusalem.

As I said, this isn’t a conversation for this thread.

Not in favour of option two. DRF is massively complex and over engineered with its too many layers. It is only suitable if your API works exactly like the model does and if it doesn’t, it becomes increasingly difficult to implement. I find DRF to have a very flat learning curve. If something gets put into core (which it should), it shouldn’t be DRF in my opinion.

2 Likes

Django has everything you need for REST. I talk about this in my talks “µ-Django“ or in my workshops “Build REST API with raw Django“

The problem is, that all of this is poorly documented. For example, I see that many people still misuse the terms “serializer” and “validator.”

Django has an awesome internal part for serializers. It works much more flexibly than Pydantic because Pydantic mixes data serialization and validation tasks.
Django has an awesome internal part for validation (forms). It works faster than Pydantic v1. And forms are still faster than Pydantic v2 if we use complex business validators. I explain how this is possible in my “Django-FTL” talks/workshops.

Django has an awesome GCBV, which works as a REST-API view together with JsonResponse. I use TemplateView + “response_class = JsonResponse”. The problem is that TemplateResponse breaks the standard order of arguments during initialization and expects a “template” on the first place, not data, like other xxResponse in Django. I solve this problem by overriding __init__ in the jsonresponse class. Yes, this can be improved.

Django has another problem with the latest Error out-middleware. It also needs to be updated: if we see content-type json in the request, the response should not send back html, as debug.technical_404_response does. I also show how this works in my workshops.

API documentation is handled with two lines of code, using the documentation-first paradigm (schema.yaml). I don’t need to change Django for this. However, I change the method OPTIONS from django.views.generic.base.View, which in Django does not allow a body at all, and I send Yaml/Json in the body according to the headers with documentation for the current URL. Something similar can be found in DRF, and I like that approach.

By the way, i generate Django models from shema.yaml automatically with codegen, although with the latest AI-agents, this is not important at all.

In this case, to add REST API support to Django, you only need to create documentation without adding any additional elements to Django.

Or @zags do you want to add additional elements in Django that will duplicate existing parts in Django?

6 Likes

@danilovmy thanks for the perspective. Is there an example project we can review with these concepts? I watched your talk on uDjango, and while it is great, it doesn’t really touch on most of the concepts you present in your response here around how to build an API-first application using Django. It is more about breaking up an existing Django application, where things like migrations, models, and logic are stored already, into new, small, isolated endpoints. I would enjoy reviewing an example application that might apply some of the concepts you discuss here in a concrete fashion.

I’m curious how you use the Django serializers without coupling them to your model layer. I definitely haven’t used this layer much. My team often finds that the shape of our APIs and our data models end up diverging in a domain-rich application, so efficiently and explicitly defining our API layer is often important separate from our models when building APIs. Are there ways to do this with the built-in serializers?

I generally don’t build my APIs as schema-first, especially for APIs built for the frontend engineers within my own company, because I communicate with my frontend team in other ways. This is usually through typescript interfaces, which we find easier to write and more expressive in iterative design phases. However, having the documentation generated for us after the fact (either with DRF or Ninja) is a quietly awesome feature once we reach maintenance mode and want to review what features are supported by our system - all of those forgotten fields we built a year ago that we might revisit. My past experiences with managing YAML schema files manually was painful. It has admittedly been a few years since I tried, so maybe the tooling is better now. Within a larger application, one with hundreds of routes, even if aggregated across multiple modular services, do you have techniques for managing these schemas manually?

I’m also interested in your patterns around JsonResponse and the template views. I have used basic JsonResponses in the past, especially when absolute performance was important. At times like this, my team has used either plain dict or TypedDict and passed the data straight to a JSON encoder to get the information back out to the requester as quickly as possible. I’m curious if your approach is similar.

Side note: I agree with many of the statements here about putting too much validation into an API layer. I think it is best to keep it simple. Validate shapes and type of data. Maybe toss in some regular expressions. As soon as our team started digging into relationship validation, we found we were really hitting domain logic, and we prefer to pull that out of our view layer entirely.

2 Likes

@zags I super super appreciate you making this post! As @carltongibson notes we’ve been discussing this for probably around a decade, and you’ve done an excellent job summarizing those past discussions and laying out the options clearly.

I share your feeling that Django needs a “story” – we can’t keep having the same circular conversations for another decade! My hope is that this conversation you’ve kicked off can eventually turn into a DEP (several DEPS?), and then a concrete decision and plan going forward. I doubt that we’ll find consensus here, so this feels very much like a situation where “disagree, then commit” should be seen as the optimal outcome.

I’m happy to help author or edit a DEP or two here. I don’t want to be the primary author but I am happy to support, including doing the heavy lifting on the writing parts since I know that’s intimidating. Feel free to hit me up.

3 Likes

Long-time lurker and Django enthusiast here. As “batteries included” is a core Python and Django philosophy, I would encourage this story too as AI/LLM training data will continue to carry this confusion - a lack of coherence in how to associate a cogent REST approach for Django - forward. Try fighting any/most AI/LLM coding assistants if you want strict python -m unittest (for better Django alignment) and watch how much the tool can’t help but suggest pytest (nothing against it). Is there any way for “the rest of us” to help in this regard?

I kinda second what @danilovmy wrote - RESTful APIs are possible with vanilla django, all the needed building blocks are already there (used it that way since ~15ys and it is really fast).

So while I see a need for more swagger/openAPI-friendly automagic machinery (protocol/datatype contracts are a good thing), I also think that a very slim/fast layer can be built by re-using the already existing batteries. (I am slightly negative about Pydantic here, as it adds again another layer. But I am not opposed to that, if the whole ecosystem already settled on it - for the sake of interface compat contraints…)

Ok, I have a pitch on what framing to use when putting together tools for an API. We should think in terms of templates, not serializers, is the TL;DR.

The problem with serializers to dump our models into API responses is that they quickly break down. What starts as a straightforward serialize command quickly has overrides to not include specific fields, or translate fields to a different format or structure, or to merge in fields from another table. It gets harder and harder to maintain as time goes on, if you’re trying to maintain a consistent API.

This can work fine for internal APIs when you can change the API and the code that uses it too, but even when I’ve been working in small teams on internal APIs, I prefer APIs to be more intentional and maintainable. Thus, the simple template approach.

def employee_history_template(employee):
    return {
        'data': {
            'employee': employee_template(employee)
            'events': [event_template(event) for event in employee.events()]
        }
    }

def event_template(event):
    if event.event_type() == BONUS_EVENT: return bonus_event_template(event)
    if event.event_type() == GRANT_EVENT: return grant_event_template(event)

def bonus_event_template(event):
    return {
        'amount': event.amount,
        'amountpct': event.percent_amount(),
        'currency': api_display(event.currency)
    }

def grant_event_template(event):
    return {
        'grant_id': event.grant_id(),
        'shares': event.number_shares(),
        'canceled': event.number_canceled() or 0,
        'type': event.display_grant_type(),
        'vested': event.percent_vested
    }

This is a slight re-imagining (and deleting a lot more events and other data types) of real maintainable testable code from a previous project, where employee events were models inheriting from an abstract base EmployeeEvent model, and grant events linked to a RSUGrant or OptionGrant… yet the complexity of the model is appropriately hidden in these templates.

I’ve now done this across almost 10 different projects mostly in django, because it’s no more work to get started and has so many advantages over time…

  • It’s very clear what’s in the API and what isn’t
  • Nobody accidentally adds something to the API (that might be sensitive!) by adding fields to models.
  • It keeps names of things in the API consistent even if names change in the data models.
  • Code reviews are easier. It’s easier to see what the developer intends to do or change in the API.
  • It keeps display/formatting (e.g. displaying date time or currency for the API differently than the GUI) where it belongs, in the view files, not in the model files.
  • The API is explicitly designed, not incidentally spit out. This is even more important for external APIs which should have a little advance thought go into what things are called and what format fields use!

Doing the templates as simple methods that return dicts is only one possibility. Another logical possibility is to define a template file format for JSON responses the same way django already defines a template file format for HTML responses.

I wrote blog posts about this in 2011, including one on testing with a similar focus on readable and maintainable API tests from a time I was doing Rails, but some blog posts have been inexplicably unpublished in more recent years, argh.

How does this impact the proposal on explictly doing REST in django? I think that it’s a good idea to offer more to developers, especially the way some are envisioning it as a combination of documentation, existing core tools, new core tools, and recommended libraries. These templates don’t require any new tools but that’s because my teams keep implementing templates within django as it stands.

I also have a pitch to make about using decorators on API views to make obvious, up-front statements that the view does things like:

  • use @requires_api_key on every view method that requires an API key
  • use @json_response on every API view that returns JSON
  • use @api_error_handling to invoke a common set of API-friendly (developer friendly) response formats in case of exceptions raised

But this post is too long already.

1 Like

I was talking about this offline with another programmer, and he suggested it would be hard to convince people to do templates manually, when they could plug in a library that can do serializing of models automatically. I think there are possible mitigations for this.

One idea would be to have a “makeapis” command that could generate some API templates from a list of models. Then the code that would be auto-generated and added to the project can be reviewed for what the structure is, what fields are included, what values look like.

It depends on project style what one would do next…

Vertical integration style: I’ve worked with folks who update their API every time they update their data models, and update their client code every time they update their API. E.g. when renaming or adding fields (like rename “start_date” to “latest_start_date” and add “original_start_date”) this keeps clarity and consistency through the stack. For vertical integration style, the API could be regenerated every time the team wanted to, replacing the previous generated API or replacing just one changed model. Diffs are still reviewable if nobody mucked with the templates too much, and the fact that the API changed is visible to front-end folks (compared to serializer approaches) and documented in the code history.

Stable API style: The API shouldn’t change without review - possibly external folks rely on it, possibly there are security and compliance requirements. A team with a stable API might still use ‘makeapis’, once or only rarely. Perhaps when a new model is added ‘makeapis newmodel’ would be invoked. New fields would have to be added explicitly to the templates, whereas internal field renames would not usually pass through to the API. But if it helps to get started from the standard REST template approach then this can still be useful - like ‘startapp’ is great early on or for greenfield model work inside an established project.

Probably there are better ideas - I’ve never built a system for auto-generating APIs because I so strongly prefer the stable API style, even in small teams with internal APIs! I’ve previously talked about how APIs are for people too, and I would have used the term “DX” if I’d known it back then - you should craft an API for the DX and end-user cases you want to have, not for the data model you currently have*. I’d probably not even use a magic ‘makeapis’ command at all if an external API is needed. Crafting the API by hand is fast and allows me to add container elements, extra info elements, decide which related models to include, or copy values from related models (some of these simple things also avoid significant numbers of roundtrips, some are futureproofing), etc.

*Unless you’re FB and exposing large numbers of models to vast numbers of developers with unknown use cases and then I guess GraphQL makes sense. Even then, Meta didn’t expose their internal data models with GraphQL!

1 Like

I’m very curious to understand what a pluggable, Django-sympathetic validation and serialization layer looks like. I’m particularly interested in its use not only in REST APIs, but also across internal, cross-app APIs as well. But it touches so many different parts of the stack that I have difficulty grokking what the shape of it would be. It feels more like the Models part of the ORM than the database backends part of the ORM, if that makes any sense.

I get that Django is a project that’s been around a long time, and that there is reluctance to place a bet on a winner that might not pan out. But I would like to offer a different perspective: Django’s pick here doesn’t need to be “the winner” in order to secure Django’s place as a compelling web framework. It just has to be good.

I work on a large Django project that started in 2011. I didn’t join until 2012, so the framework choice had already been made at that point. I had come from previous jobs that used Pylons and had really strict requirements about querying the database. At the time, every particular thing in Django leapt out at me as being an inferior option. The templating engine was slow and limited compared to Jinja or Mako. The ORM was primitive and confining compared to SQLAlchemy. Tornado had a better story around serving high levels of traffic. About the only things that stood out to me as first-class about Django was its admin interface and its docs. I also had a criminal under-appreciation of the concept of apps. There is a decent chance that if I had been the one to choose at the time, I would have picked a different framework.

I would have been wrong. Choosing Django was probably one of the best decisions that our project made in those early days, but not because Django’s features became best-in-class across the board. Django represents a coherent, documented, supported, integrated, and easily upgradable set of features that are good enough on their own, and extremely compelling when packaged together. Did I want to use some feature in SQLAlchemy? Sure. Was I willing to give up the Django Admin for it? Not a chance. Do I want to play with django-ninja? Yup. Am I going to advocate that we adopt it without more evidence that it will be strongly maintained in five years? Not really.

FastAPI came out six years ago. Using Pydantic as a way to validate, serialize, and generate API docs now qualifies as boring tech (in a good way). Even if I prefer the approach that cattrs takes in the abstract, it’s a lot less interesting to me if I can’t easily generate a JSON schema from it or take advantage of whatever else is in the greater ecosystem around it.

As it stands today, the Pydantic-based approach of FastAPI and Django Ninja is a huge step up over DRF in terms of developer experience. There’s always going to be something better down the line, but will it be so much better that it’s going to be a deciding factor for developers?


There are folks on this thread that know much more about Django than I do, and have been thinking about this problem longer than I have. I’m giving my two cents of feedback here, but I realize that I’m only seeing a small part of the picture.

Honestly, I’m just looking for a blessed, supported upgrade path towards something with a developer experience that is comparable to FastAPI in terms of validation/serialization/doc-generation. If that gets rolled into Django proper, that’s great. If the Django leadership decides that Django wants to be the Debian of web frameworks and django-ninja will be the DSF-blessed REST API-centric distro, I’d shrug my shoulders, roll with it, and ask what the LTS release cycle for django-ninja will be.

8 Likes

It absolutely does. And, yes, it’s not just about REST APIs. (For me, the one you didn’t mention is logic bearing Display Objects to pass into templates, but let’s not go off there… :sweat_smile:)

I’m working on a proof of concept here now. I’ve been pottering on it for a couple of years but the discussion here and @FarhanAliRaza’s recent benchmarking work showing Django with comparable performance to FastAPI if we but only used a modern serialisation option (msgpec in his case) gave me a boost. I finally worked out the API I want on paper yesterday. (So I’ll have something to show in the coming period. I’d have it already but the work project is almost 100% HTML driven, so it’s not been pressing personally.)

——

Boring tech…

Yes. Absolutely. But I think we’re at a point where another set of advances are now visible. I think if we just merged Ninja, just as if we just merged DRF, we’d be skating backwards instead of forwards. We’d do it and be back here moaning that we’re lagging behind immediately.

Absolutely we should be documenting the options folks have. But we need to let the current very exciting developments play out before we pull the trigger on an in core option.

There’s no reason why Django can’t both be as fast as other (Python) frameworks out there and have an ORM to wire story that makes folks jealous once again.

3 Likes

Absolutely we should be documenting the options folks have. But we need to let the current very exciting developments play out before we pull the trigger on an in core option.

There’s no reason why Django can’t both be as fast as other (Python) frameworks out there and have an ORM to wire story that makes folks jealous once again.

We also should not let perfect be the enemy of the good. It’s time that something happens on this front.

Absolutely. I’ve been supporting the promotion of the ecosystem solutions here for a good while now. The new ecosystem page is a direct outcome of that work, together with the other steering council members. We need to be promoting that now, which is a whole other thread. TBH I think if the website were clearer on what Django, and the ecosystem, already has to offer, most of the concerns expressed in this thread would evaporate. (It’s not that Django doesn’t have a REST story; it’s that if you turn up at the website you’d have a darn hard job trying to work out what it was)

I’ve also been pushing the proposal to add Content Negotiation to Django’s Request object for a good few years now. This is the missing bit at the request layer that we’d need for feature equivalence to DRF there. This hasn’t been successful yet, but I know @emma wants to drive it forward, directly because of this API story topic, so I’m hopeful that will make ground in the next cycle. This should just be part of core, in my opinion. (It’s foundational in a way that other layers aren’t. DRF’s wrapping of the Request object to add it was always a source of pain.)

The serialisation story is the other leg, so to speak. It’s this bit that I think is too in-flux (i.e. exciting, new, unknown, …) to be straight to Django and we should let the ecosystem continue to explore.

It’s similar to template components: everyone wants them, but it’s a fertile ground of new ideas, and we need to see how that unfolds.

In both these cases, I’m absolutely behind promoting what’s out there, and have been trying to do that. We should be doing that more IMO

The point about perfect vs good is that there are already demonstrations that we can have top level performance by adopting modern approaches. This isn’t something that’s theoretical.

4 Likes

I’ve been reading this thread with interest. My 2 cents of where Django could go.

  1. Having the API guide pages that layout common options. This is probably the most impactful quick win to my mind. I would possibly go so far as to say a first draft could be done quickly with an AI of choice and then refined by humans.

  2. I have been pondering what the Python API for APIs in core could look like and I am slowly coming around to the idea that it lives in the view layer with some decorators for FBV’s and additions (or perhaps a new start?) for CBVs, along with work on the URLs layer of routers in the mix. There is a huge amount of choice at the serialization layer so having a common interface to choose your own adventure there would seem key to me and allow third party package to continue to provide optionality and innovation.
    The general point for me is getting the Python API right that would allow someone to migrate from DRF to cattrs to pydantic etc without a huge amount of effort.

Just quickly (I’m trying not to over post) It seems ≈ everyone is agreed that the immediate step is promote the existing options. I’m going to take that to the next Steering Council meeting, and see what we can do to advance that (which might be advance promoting the ecosystem page).

The Options 1 and 2 are more in the air — but I’d recommend to everyone: if you’ve got an idea, put up a proof of concept. The more the better here.

3 Likes

I can’t even begin to express how happy I have been while watching this thread over the past few weeks.

2 Likes