In my DjangoCon US talk, “All the Ways to Use Django”, I had several ideas for how Django could improve as a framework. My first and most actionable idea was for Django to have native support REST APIs. I have been in many discussions about this at DjangoCon US and want to describe the community feedback I have heard on this and give the set of possible solutions and the tradeoffs between them. (Minor note: in my talk, I spoke specifically about JSON APIs, but the discussions I’ve had have been around REST APIs in general rather than JSON APIs in particular, so I’ve done my best to keep this data-format agnostic.)
Why is this urgent? Django is a fantastic web framework but is currently losing in mindshare to other frameworks that focus more on easy creation of APIs (such as FastAPI) and easy support for JavaScript (such as Next.js). While Django can support REST APIs very well via third-party packages such as Django REST Framework (a.k.a. DRF), Django Ninja, there is a perception problem among new developers or developers unfamiliar with Django that Django itself doesn’t solve these problems. This is a challenging problem because it straddles the line of engineering and marketing.
Why REST API’s in particular? REST APIs support a huge variety of use cases, including: inter-service communication, JavaScript frameworks, mobile apps, and external APIs. According to PyPI Stats, Django REST Framework got about 52% the number of downloads as Django itself last month (DRF got 14.4 million monthly downloads vs Django’s 27.9 million). Given DRF has Django as a dependency, this means there are more installs of Django + DRF than of Django alone.
I see there are three high-level ways for us to move forwards on this:
- Add direct REST API support to Django
- Have Django absorb Django REST Framework
- Have the Django docs officially reference community solutions such as Django REST Framework
Here are the tradeoffs I’ve heard on these.
1. Add direct REST API support to Django
This practically is four components:
- Serializers/Validation
- Class-based API views
- Wrappers/decorators for function-based views
- Auto-generation of API documentation
Upsides:
- This would enable Django to create a modern solution that takes the best of DRF and Django Ninja
- This wouldn’t require Django to absorb all of the baggage of DRF or Django Ninja
- This may be the only way through this compromise.
Downsides:
- This requires a plan.
- Counterargument: we can make a plan.
- This requires engineering work.
- Counterargument: DRF and Django Ninja can serve as inspiration for this work, so none of these pieces need to be built from scratch.
- DRF and Django Ninja already do this.
- Counterargument: for the sake of new developers, Django should have a native way to do this. Django is inherently modular, and people can still use DRF or Django Ninja or other community solutions even if Django has native support. Django is inherently modular, and just because Django has native support for something doesn’t mean there isn’t room for community alternatives.
- This could leave people using DRF of Django Ninja in the lurch if those libraries lose support.
- Counterargument: we can build the new serializers/validators to be pluggable, so that users can continue using DRF or Django Ninja serialzers (potentially with some updates from those libraries). We could also create migration pathways from those libraries to Django’s official REST solution.
- This adds to the support burden of Django.
- Counterargument: if this helps bring new users to Django, this improves Django’s longevity as a framework.
- We could get the design wrong.
- Counterargument: Django should create mechanisms to experiment a little more. This is what Django contrib was originally intended to be, and there have been ideas to have official Django experiments that are not guaranteed to make it into core.
As to how to implement REST API support in Django, the main thing to decide is how we are going to do serializers and validation. I have heard five main proposals to this subproblem:
- 1A. Use Pydantic
- 1B. Extend the existing Django serializers
- 1C. Make new serializer classes
- 1D. Use Django Forms
- 1E. Use Django Models
- 1F. Handle this as part of the views
1A. Use Pydantic
Django could leverage Pydantic models as the core of its new serialization and validation.
Upsides:
- Pydantic is popular. Pydantic gets over 10x the number of monthly downloads as Django (421 million monthly downloads), and Django Ninja is an example that people want to use it with Django.
- Pydantic is supported by IDEs.
- Pydantic is fast.
- This creates an easy migration path for people coming from Django Ninja
- You can make Pydantic models off of Django models (ex. Django Ninja ModelSchema
The big downside I have heard is that this adds an external dependency to Django. This is particularly concerning because Pydantic had substantial breaking changes between versions 1 and 2. However, we can make this an optional dependency of Django, and Pydantic 2 is now used by enough major packages that it is unlikely they will do that again.
This approach would also be better if we added a tool for helping users of DRF migrate their existing serializers to Pydantic-based ones.
1B. Extend the existing Django serializers
Django has existing serializers but they are not as flexible in their parsing as Pydantic and are missing the validation necessary for full API support. We could extend these to make them more flexible and add validation, but this risks breaking areas where these are already used.
1C. Make new serializer classes
So long as we can sufficiently differentiate these from Django’s existing serializers to avoid confusion, creating a new set of serializers could give us all the functionality we need out of serializers without backwards compatibility concerns. Django Rest Framework has a decent stating point for a design if we want to pursue this plan.
1D. Use Django Forms
I originally proposed this as the path forwards in my talk, as Django Forms are very close to what serializers need to do and have built-in validation. There is a recent blog post showing a way to do this. However, Forms have a lot of baggage around rendering that isn’t relevant to serialization that would be confusing to new users. Additionally, bolting serialization on to Forms reduces the future flexibility of both components of the framework.
1E. Use Django Models
Another alternative is to add to Django Models native serialization functionality, such as from_json
and to_json
methods, and relying on Django Models existing validation. This could also support arbitrary APIs via creation of models with managed = False
. As much as this makes it easy for people to get started with generating an API from their models, I don’t like this approach because any complex API will need the layer of abstraction between the models and the API that is provided by a dedicated serializer class. This is especially important for publishing external APIs that need to be carefully curated and versioned. Django in general is set up to help its users future-proof (as opposed do something quick to get started that won’t hold up over time), and I believe its API support should do the same. This approach is also hard to make backwards compatible with DRF and Django Ninja.
1F. Handle this as part of the views
Neapolitan is an example of a library as an example of this. This lends itself well to class-based views, and might be hard to have equivalent for function-based views. This approach is also hard to make backwards compatible with DRF and Django Ninja.
2. Have Django absorb Django REST Framework
Django REST Framework is a fully-fledged, battle-tested solution to this problem that has incredibly wide-spread community adoption. If Django absorbed it into Django itself or included a modified version that was still backwards compatible with DRF, this could be a fairly minimal engineering lift while giving Django native REST support.
A scaled-down version of this proposal would be for DRF to continue as a standalone library but at least get moved to the Django GitHub org like Django Channels.
Upsides:
- DRF is stable. It has not gotten major changes in many years.
- DRF has widespread community adoption.
- This could enable DRF to get more than its current maintenance-mode support. This would also give users of DRF more confidence that it will stay in lockstep with Django and get the same base-level of support as Django.
The downsides to this approach are as follows:
- We ideally want permission from the DRF maintainers. Such a merger was proposed many years in the past and the DRF maintainers declined.
- Counterargument: DRF is not under active development anymore so this could look very differently now.
- DRF is not the only option. Django Ninja is already also an officially endorsed REST library, and Django absorbing DRF would be seen by some as Django “picking a side” in how to do REST API generation.
- Counterargument: Django is modular. Django having native REST support doesn’t preclude users of Django from doing it another way.
- DRF is not as performant as Django Ninja. Because Django Ninja uses Pydantic for serialization, which is built in Rust, it runs faster than DRF.
- Counterargument: DRF is sufficiently performant to have achieved widespread adoption (see the download stats from PyPI).
- Pydantic models are the future of Python. They are easier to make than serializes and better supported by IDEs.
- Counterargument: Django has been very hesitant to add third-party dependencies.
- This adds to the support burden of Django.
- Counterargument: DRF has substantial financial support, and including it in Django could be a net positive for Django in terms of support costs
- It’s too late. The time for this was years ago.
- Counterargument: Django needs a solution to this. Some solution is better than no solution.
3. Have the Django docs officially reference community solutions such as Django REST Framework
I see this as the bare minimum answer. We’re already part of the way there. The Django website now has a community section that includes references to recommended third party packages. These packages have already been approved by the Django Steering Council for endorsement, so we’ve already solved the hardest procedural problem here. There are two things missing. First, the content of this page is not included in Django Docs search; there is a PR up to address this. Additionally, Django should have more guidance to users looking to make a REST API on how to do so and which library to choose so that new users see this less as Django having abdicated responsibility to the community and more that Django is providing its users with multiple options.
Upsides:
- This is a minimal lift compared to the other solutions.
- This maintains Django’s neutrality with regards to which style of API we use
Downsides:
- This is more confusing to new users. This is just a bandage solution rather than native support.
- This is a departure from Django’s historic philosophies of “opinionated” and “batteries included”.
- These packages don’t get the same level of support as Django. DRF hasn’t gotten new features recently and Django Ninja is not yet at the level of polish as Django itself. Django is limiting the functionality it can capture and abdicating responsibility for this facet of the framework to the community.
The path forward
First and foremost, we must act. We cannot choose none.
I’m going go post my opinion on what we should do as a reply because I want to build consensus on the problem first and a solution second.