Django Rest Framework
In one of my previous roles I had to use Django Rest Framework (drf) to make APIs and I enjoyed it. It fit my mental model for building robust APIs.
My mental model is as follows:
- A user sends a request
- Check auth
- Check request body (do we have the right fields/structure)
- Perform basic validation (types)
- Perform business logic validation
- Persist to data store
drf makes this super easy with its serializers. Views + permissions take care of auth in most cases and serializers do the rest.
A small amount noob traps do exist.
# Using ModelSerializer
This is the first trap most people fall into. ModelSerializers are convenient when prototyping but most of the time the input that you want from a user does not map nicely onto an existing model. This is true the larger/more complex your web API gets.
# Trying to use the same serializer for update and create
This is another noob trap that ends up resulting in some crazy complex and hard to follow code.
Most of the time it turns out the logic you want for create operations ends up being quite different from the logic you want when it comes to update operations.
This results in a lot of validation code that depends on the existence of
It's trivially easy to switch out which serializer gets used based on action
Using a serializer per action lets you have nicely segregated logic per verb per endpoint.
This makes it super clear what code gets run for
POST /something vs
PUT /something vs
This in turn leads pretty easy maintainability when needing to make a change as you can be sure a change to the behaviour of one action does not impact the others.
Which in turn leads to pretty easy testing, you just need to test each specific serializer plus its validation logic.
# Not using a separate serializer for retrieve vs list
For reference a retrieve is asking for a single record i.e.
GET /things/1 and list is asking for more than one record i.e.
Generally the data you want in a detail view (retrieve) is different from the data you want in a list view and your serializers should reflect this. Detail serializers might need to do a lot of extra look up to get related records to present a nice detailed record.
A mistake I've seen made is folks end up using this same detail serializer for their list operations. This results in an unholy amount of N+1 queries and abysmal performance.
# Not using a separate serializer for read vs write operations
This is not quite a noob trap but it does go a long way in the simplification of code. I find that more often than not you would like to receive data in a field as one type, but when returning the data it needs to be another type. e.g. Take in a foreign key but return a string from the related object. drf does not make it easy to re-use a field if its a different type on the way out. The maintainable/easy way out of this predicament is to just use a different serializer for incoming data vs outgoing.
The general takeaway from this is that you probably want to avoid using ModelSerializers and don't try to make a single serializer a one size fits all solution.