Back to blog

Every Django team solves the same DRF problems from scratch. They shouldn't have to.

If you have built more than one API with Django REST Framework, you know this feeling. A new project starts, and within the first week, you are writing things you have written before. The same response envelope logic. The same viewset structure. The same workaround for tracking who created or modified a record. The same bulk operation implementation, this time with transaction safety, because last time it didn't have it.

You are not building something new. You are reconstructing a foundation.

What DRF leaves unfinished

DRF is exceptional at what it does. This is not a criticism of DRF. It is an observation that building APIs at scale consistently surfaces the same gaps, and those gaps are not edge cases. They are the standard problems of any serious Django project.

Endpoints across the same codebase return data in structurally inconsistent forms. Some wrap in a data envelope. Some return arrays. Some return flat objects. Clients cannot rely on a predictable contract, and every consumer of the API learns this the hard way.

Every resource requires the same boilerplate. Permission classes, serializer resolution, queryset filtering, pagination integration, and error handling. In a system with fifty resources, this is thousands of lines of structural duplication that provide no value and create endless inconsistency.

There is no built-in mechanism for tracking who created or last modified a record. The common workarounds scatter responsibility across layers and break under pressure.

The standard pattern for storing the current user in thread local storage breaks in async environments. With ASGI becoming the deployment standard, this introduces subtle bugs that are genuinely difficult to diagnose.

Bulk operations are not first-class citizens. Rolling your own means reimplementing transaction safety, validation, and audit population for each resource, usually inconsistently.

Relational fields are written as ID, read as ID. Representing the same relation differently depending on context requires custom field classes every time, a disproportionate amount of code for something that comes up constantly.

None of these are exotic problems. Every team hits them. Every team solves them slightly differently. Every new project starts with reconstruction.

The decision to stop

After hitting every one of these across multiple systems, the pattern became impossible to ignore. The problem was not any individual implementation. It was that the implementation had to happen at all, every time, from scratch, with slightly different results each time.

The right answer was to solve them once, correctly, composably, and never again.

What drf-commons is

It is not a framework on top of DRF. It is a structural layer composed on top of DRF internals that follows one principle: progressive enhancement. You adopt what you need. It complements what you already have. It never breaks what DRF already provides.

Inconsistent responses become a single unified envelope. Repetitive viewset boilerplate becomes pre-composed base classes. The audit trail becomes a mixin that resolves the current user safely through context variables, with full async support. Bulk operations become first-class citizens with transaction safety built in. Relational fields become configurable with twenty plus field types covering every common pattern. Import and export become services, not custom implementations. Debug tooling becomes unified and coherent.

Each problem that used to be solved per project is now solved once.

If you build APIs with Django REST Framework, the full documentation is at drf-commons.readthedocs.io. The source is on GitHub. Future posts will go deep on each problem and how drf-commons approaches it specifically.

Author image

Victoire Habamungu

Software engineer specialising in data systems, distributed architecture and platform engineering.

Share post: