Django Weblog: Why, in 2025, do we still need a 3rd party app to write a REST API with Django?
The question was asked to the president of the DSF this year at FOSDEM, afterhis talk. And it is clearly a legitimate one!
But… is itTrue? Do we actually need a 3rd party app to write an API with Django?
In a lot of cases, when you require a complex and full-featured API, I would recommend youdouse one.Django REST FrameworkandDjango Ninjabeing very sound choices with a bunch of nifty things you might need in a bigger project.
But…what if what you need is a simple REST API that does CRUD? Do you really need a 3rd party app to do that?
Let's try not to!
Let's first ask what is a REST API in the context of this article. Let's limit ourselves to building this:
- a URL that answers to
GETrequests with a list of records of a single model type POST-ing to that same URL should create a new record- a second URL with the primary key of a record tagged to the end. When
GET-ing that URL, one should receive only that single record, in a similar format as in the list PUT-ing data to that URL should update the record and return that record with updated valuesDELETE-ing to that same URL should delete the record- everything should be achieved using JSON
Listing records from a model
Chances are you have heard of generic class-based views (CBVs) in Django, the one that comes to mind when it comes to listing records isthe built-indjango.views.generic.ListView.
ListViewextends 2 other classes,BaseListViewandMultipleObjectTemplateResponseMixin. Since we want to build an API, we clearly don't need to extend anything template-related. Looking at whatBaseListViewprovides, one can notice the only thing really missing there is arender_to_responsemethod. And this is going to be the case for most of the other base classes.
This sets our starting point!
The type of response we want to render is ajsonresponse and Django already provides one. So let's build aJsonViewMixinthat looks like this for now:
The next thing we have to tackle is that thecontextreturned byBaseListView'sget_context_datareturns much more data than what westrictlyneed for this article. Also the list of records returned is not serialized tojson.
Serializers for mutliple formats already exist in Django core (seedjango.core.serializers) but I will go a different route here. There is another way to serialize data in Django that you are likely familiar with but is not labelled as clearly: forms.
Forms are used in regular views to serialize models to simple types, understandable by HTML forms (mostly text) and vice-versa. This is very close to what we need, sincejsonis also mostly text-based.
To start with, using forms as serializers requires creating a new form instance for each record in the list we want to return.
Let's add that to the mixin!
Why use forms?
ModelForms are a built-in and robust Django tool that are built around the idea of handling the transition betweenModelfields and simple (and also JSON-serializable) types (mostly text and numbers). Which is exactly what we want from (de-)serializers in a lot of cases.
If you need to (de-)serialize a custom field type, Django documentscreating a custom form fieldand this covered invarious places like StackOverflow.
Moving on to our firstView
Now that we have a tool to serialize the records list returned byBaseListViewlet's write the first version ofJsonListView. As I alluded to earlier, we need to strip down what is returned fromget_context_data.
This won't work yet becauseget_form_classthat I used in theJsonViewMixinis only provided by classes that descend fromFormMixin. Since we want this view to handle both listing and creating records, let's go and fix that in the next section!
1 down, 3 to go: Adding records
First thing first, let's rebrandJsonListViewand make it inherit fromBaseCreateView.
Form creation and validation will be handled automatically by Django!
Almost…
The first concern will be with populating the form withPOSTdata. While Django does this for you when dealing with URL encoded or multipart form data, it does not (yet) handle json-encodedPOSTcontent.
But this can be handled by taking advantage of the modularity of Django's generic class-based-views and overwrittingget_form_kwargs.
Let's address this (in a naïve way) within the mixin as it will be applicable to any JSON view:
An issue that could arise here is that aJSONDecoderErrorcould be triggered.get_form_kwargsdoes not return a response so I don't think it is the right place to handle the exception.
Thepostmethod does return a response, let's wrap the original one with atry/except(still in the mixin):
Speaking of returning responses, theBaseCreateViewclass is built around HTML principles and itsform_validandgetmethods are both designed to render a form (viaget_context_data).
In the case of our REST API, the "create" part of things should not be involved withGETrequests.
Furthermore the reply to an invalid form submission should only comprise of an error (status + message) and should not require anything provided byget_context_data.
Still, in regards to form validation, a valid form should not result in a redirect (behaviour ofBaseCreateView) but rather in a201response optionally containing the representation of the created record.
The form handling part is generic enough to put it in the mixin itself.
The behaviour ofGETis specific to the list/create view though.
Let's write the code accordingly:
Halfway there!
That was everything needed to handle thecreateandlistportions of our CRUD REST application. Now we can move on to theread,update,deletepart. We'll do that in a secondViewclass as it requires a slightly different URL, one that contains thepkof the resource.
Both read and update functionalities are provided by DjangoBaseUpdateViewbut, as with the create/list view, the major difference in this case will be that we need a much simpler context.
That's it!!!
Well, almost…
BaseUpdateViewis wired to answer toPOSTrequests for updating a record while REST good practices want us to usePUTinstead. The fix for this is to raise an error in reply toPOSTcalls while directingPUTs to the parent'spostimplementation.
One more fix…
Our mixin implementation returns a201onform_valid. In case of any view which is not creating a record, this should be200. Here are the necessary changes:
WhyPUTand notPATCH?
BaseUpdateViewbuilds a form that expects all fields to be filled. Non-present fields would be reset to empty on the existing record for partial updates.
I'll leave it as an exercise to the reader to override that behaviour in case of aPATCHrequest in order to "pre-fill the form" with existing values, maybe by using the form'sinitialproperty… 😉
Finally…
The last bit of logic we have to implement is for deleting objects. Most of the code from Django'sBaseDeleteViewis related to creating and validating a form for confirming the user's intend on deleting the resource. This is usually not the expected behaviour for a REST API, this part being handled by whatever is calling the API.
Furthermore, it doesn't implement a delete method. In the HTML world of Django'sBaseDeleteView, everything is done usingGETandPOST. So we are (mostly) on our own for this last part.
We can still leverage theget_objectimplementation provided byBaseUpdateViewthough.
Here is what implementing the delete operation for our read/update/delete view looks like:
Conclusion
This implementation is basic and clearly naïve. But it gets the job done!
And this can all be done by leveraging Django-provided tools and mechanisms, mainly using Django's generic CBVs.
Generic class-based views have been built in such a modular fashion that implementing one's own mini REST framework can be done in less than 100 lines of code.
A non-negligible advantage of such an approach is that most libraries written to work with Django's generic CBVs are also likely to work with this implementation.
This rather simple approach can certainly be improved (handling exceptions indelete… anyone?) and is clearly not going to cover everybody's use cases. And it most likely misses handling a bunch of edge cases!
And if you are building a large REST API, I would say you are probably still better off using a 3rd party library but… to me, the answer to the question“Why do you need a 3rd party application to write a simple REST application with Django?”is: "You don’t"
If you enjoyed this article, read more from Emma onEmma has a blog, which is where this piece was from. Or watch the FOSDEM talk that Emma reacts to:
Thibaud Colas - Shifting DX expectations: keeping Django relevant 😬 | FOSDEM 2025
https://www.djangoproject.com/weblog/2025/may/22/why-need-3rd-party-app-rest-api-with-django/