<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wesley E. MONTCHO</title>
    <description>The latest articles on DEV Community by Wesley E. MONTCHO (@wesleyeliel).</description>
    <link>https://dev.to/wesleyeliel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F742917%2F493a3e0e-6922-4d06-b1de-2e512253570e.png</url>
      <title>DEV Community: Wesley E. MONTCHO</title>
      <link>https://dev.to/wesleyeliel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wesleyeliel"/>
    <language>en</language>
    <item>
      <title>The Django Digestive System: A request journey in a Django app instance.</title>
      <dc:creator>Wesley E. MONTCHO</dc:creator>
      <pubDate>Sat, 28 Mar 2026 00:35:13 +0000</pubDate>
      <link>https://dev.to/wesleyeliel/the-django-digestive-system-a-request-journey-in-a-django-app-instance-1coj</link>
      <guid>https://dev.to/wesleyeliel/the-django-digestive-system-a-request-journey-in-a-django-app-instance-1coj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🇫🇷 La version française est disponible sur Hashnode : &lt;a href="https://wesleymontcho.hashnode.dev/le-syst-me-digestif-de-django-anatomie-d-une-requ-te-http-vers-django" rel="noopener noreferrer"&gt;Le Système Digestif de Django : anatomie d'une requête HTTP vers Django&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Before we start, let review a few words we'll keep using
&lt;/h2&gt;

&lt;p&gt;&lt;a id="ref-1"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Socket: ([Ref 1])&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A socket is an endpoint for communication between two machines over a network. A king of pipe through which data flows. When a browser sends a request, it opens a socket connection to our server. When the server responds, it writes bytes back through that same socket.&lt;/p&gt;

&lt;p&gt;&lt;a id="ref-2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;TCP, HTTP: ([Ref 2])&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TCP (Transmission Control Protocol) is the transport layer. It defines standards for handling reliable delivery of data between machines. HTTP runs on top of TCP. HTTP is just text, formatted in a specific way. A browser sends an HTTP message through a TCP connection to our server.&lt;/p&gt;

&lt;p&gt;&lt;a id="ref-3"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;WSGI (Web Server Gateway Interface): ([Ref 3])&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WSGI is the standard interface between a Python web application and a web server. A WSGI server ( Gunicorn, uWSGI ect ) sits between the internet and Django in our case: it accepts (manage) the TCP connection, reads the raw HTTP bytes, and calls Django with a standardized Python dictionary ( called: &lt;a id="ref-4"&gt;&lt;/a&gt;&lt;code&gt;environ&lt;/code&gt; ( [Ref 4] ) and holding every thing related the to incoming request: method, path, headers, body stream ). Django never touches TCP or the raw socket directly. That boundary is WSGI's job.&lt;/p&gt;

&lt;p&gt;&lt;a id="ref-5"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Middleware: ([Ref 5])&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Middleware exists in every serious web framework. Express, NestJs, Laravel , Rails ect. In Django, middleware is a layer that sits between the WSGI server and the view ( who contains our code about how we want to treat the incoming request ). Middleware can read, modify, or stop a request entirely: stopping it and returning a response immediately, without passing the request any further, is called &lt;strong&gt;short-circuiting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id="ref-6"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ORM: ([Ref 6])&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ORM stands for Object-Relational Mapper. It's the layer that translates between Python objects and database tables. Rails calls it ActiveRecord, Laravel calls it Eloquent: in Django it's just "the ORM.".&lt;/p&gt;



&lt;p&gt;Still here? Good 👌. So now that we have a clear view about these words,  Let's trace a request. 🗺️&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Before Django, Raw Bytes comming from a client
&lt;/h2&gt;

&lt;p&gt;A browser decides to call our API. It opens a socket [Ref 1] connection to our server and sends something like this, which is a typical http text format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/users/42/&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer eyJhbG...&lt;/span&gt;
&lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Plain text. For now, no Python, no Django, no objects. A formatted string traveling through the socket [Ref 1], controlled by the TCP [Ref 2] standard.&lt;/p&gt;

&lt;p&gt;The WSGI server [Ref 3] receives this on the socket, parses the HTTP text, and builds the &lt;code&gt;environ&lt;/code&gt; dict [Ref 4]. Then it calls Django. That is where our story picks up.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Birth of &lt;code&gt;HttpRequest&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The entry point of every Django request is &lt;code&gt;WSGIHandler.__call__()&lt;/code&gt;, defined in &lt;code&gt;django/core/handlers/wsgi.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the exact line where the request stops being a dictionary and becomes a Python object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django/core/handlers/wsgi.py (simplified)
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WSGIHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ← born here
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;self.request_class&lt;/code&gt; is &lt;code&gt;HttpRequest&lt;/code&gt; class. Django takes the raw &lt;code&gt;environ&lt;/code&gt; dict and wraps it into a rich, usable Python object.&lt;/p&gt;

&lt;p&gt;Here is what that transformation looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmffi2c7kwyx53o3thvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmffi2c7kwyx53o3thvw.png" alt="WGSI tranformation before django" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice &lt;code&gt;request.META&lt;/code&gt; always contains the full &lt;code&gt;environ&lt;/code&gt; dict. Nothing is lost. It is just made accessible through a proper Python interface.&lt;/p&gt;

&lt;p&gt;Are we following? Good 👌. &lt;code&gt;HttpRequest&lt;/code&gt; is alive. Now it has to travel.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Middleware flow [Ref 5]
&lt;/h2&gt;

&lt;p&gt;This is where most people have a fuzzy mental model, and it's worth clearing up.&lt;/p&gt;

&lt;p&gt;Middlewares flow is &lt;strong&gt;not&lt;/strong&gt; a pipeline where each step ( middleware ) independently processes the request and passes it along. It's a stack of &lt;strong&gt;nested wrappers&lt;/strong&gt;. Each middleware wraps arounFork in thed everything that comes after it.&lt;/p&gt;

&lt;p&gt;Here is what a middleware actually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OurExampleMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;  &lt;span class="c1"&gt;# the next layer
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# → this runs before the view
&lt;/span&gt;        &lt;span class="nf"&gt;do_something_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# call the next layer. 
&lt;/span&gt;        &lt;span class="c1"&gt;# So if we have n other layer before the view, 
&lt;/span&gt;        &lt;span class="c1"&gt;# all them will be executed, the view too, 
&lt;/span&gt;        &lt;span class="c1"&gt;# before we continue to the next instruction of the current method
&lt;/span&gt;
        &lt;span class="c1"&gt;# ← The, this runs after the view, on the way back out
&lt;/span&gt;        &lt;span class="nf"&gt;do_something_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Middlewares are like, chained. So, &lt;code&gt;self.get_response&lt;/code&gt; is the next middleware in the chain () or the view itself if we are at the last middleware). Calling it sends the request deeper. &lt;strong&gt;Not&lt;/strong&gt; calling it and returning a response. If a middleware return a response that way, directly is a short-circuit and the view never runs.&lt;/p&gt;

&lt;p&gt;Django processes &lt;code&gt;MIDDLEWARE&lt;/code&gt; top to bottom on the way in, bottom to top on the way back. Order is not optional.&lt;/p&gt;

&lt;p&gt;Here is what the built-in middleware injects as the request travels through:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uy9ve1uprmdt75qeubt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uy9ve1uprmdt75qeubt.png" alt="Django middleware flow" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a sense, and compared to the human digestive system, This is the intestinal tract of the request. Each layer absorbs one thing: session here, user identity there etc. and passes the rest along. By the time the request exits, it's been fully enriched. Short-circuiting? That's your  body rejecting something before it reaches the bloodstream. &lt;br&gt;
By the time the request reaches our view ( officially the bloodstream 😅 ), &lt;code&gt;request.session&lt;/code&gt; and &lt;code&gt;request.user&lt;/code&gt; are already there, added by related middleware. Not magic just, middleware flow doing its job quietly.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. URL Routing: The Crossroads
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HttpRequest&lt;/code&gt; has cleared the middleware stack. Django now needs to figure out &lt;em&gt;which&lt;/em&gt; view should handle it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;URLResolver&lt;/code&gt; walks &lt;code&gt;urlpatterns&lt;/code&gt; (the list we define in &lt;code&gt;urls.py&lt;/code&gt; in the core folder containing settings), comparing the request path against each pattern. The first match wins. Captured groups become &lt;code&gt;kwargs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# urls.py
&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users/&amp;lt;int:pk&amp;gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserDetailView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;

&lt;span class="c1"&gt;# request to /users/42/ → kwargs = {"pk": 42}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nothing matches, Django returns a 404 before our view code ever runs. At this step, no view, no serializer, no database. Just a 404 out.&lt;/p&gt;

&lt;p&gt;Still with us? We're almost at the part where our code actually runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Our Code Finally Runs 💻
&lt;/h2&gt;

&lt;p&gt;The view receives two things: the &lt;code&gt;HttpRequest&lt;/code&gt; object and the resolved &lt;code&gt;kwargs&lt;/code&gt;. Everything that happened before this (socket, WSGI, environ, middleware, routing ) was Django's machinery. This part is ours.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDetailView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RetrieveAPIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# request.user is already set ( thank AuthenticationMiddleware, during the middleware flow )
&lt;/span&gt;        &lt;span class="c1"&gt;# pk came from the URL thank URLResolver
&lt;/span&gt;        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;s&lt;br&gt;
&lt;code&gt;User.objects.get(pk=pk)&lt;/code&gt;: this is where the ORM [Ref 6] steps in. Django translates this Python call into a SQL query, hits the database, and returns a Python object. One line from us; a lot happening underneath. [I tried to read this code in the django module in the past, and it was not a simple logics. Thanks to django contributors.]&lt;/p&gt;

&lt;p&gt;For those of us using Django REST Framework: &lt;code&gt;APIView.dispatch()&lt;/code&gt; ( The base of viewsets ect. ) adds one more execution layer before our &lt;code&gt;.get()&lt;/code&gt; or &lt;code&gt;.post()&lt;/code&gt;, or &lt;code&gt;.create()&lt;/code&gt; or &lt;code&gt;custom action view&lt;/code&gt; runs: authentication classes, permission classes, throttle classes. We'll go deep on this in a dedicated post. For now, just know it's there.&lt;/p&gt;


&lt;h2&gt;
  
  
  6. The Return Journey
&lt;/h2&gt;

&lt;p&gt;Our view returns an &lt;code&gt;HttpResponse&lt;/code&gt;. This is where the middleware stack &lt;strong&gt;unwinds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same chain runs in reverse: bottom to top. Each middleware's post-view logic executes as &lt;code&gt;get_response(request)&lt;/code&gt; returns back up the call stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tpogugwpo3ffs4ju6a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tpogugwpo3ffs4ju6a3.png" alt="Django Middleware Return Flow" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Headers like &lt;code&gt;Content-Security-Policy&lt;/code&gt;, &lt;code&gt;Strict-Transport-Security&lt;/code&gt;, and &lt;code&gt;Set-Cookie&lt;/code&gt; are added on this return trip. The middleware that sits at the &lt;strong&gt;top&lt;/strong&gt; of &lt;code&gt;MIDDLEWARE&lt;/code&gt; is the &lt;strong&gt;last&lt;/strong&gt; to touch the response. That's the nesting in action.&lt;/p&gt;


&lt;h2&gt;
  
  
  7. Out the Other End
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WSGIHandler&lt;/code&gt; takes the &lt;code&gt;HttpResponse&lt;/code&gt; object returned and then converts it back to bytes, according to the HTTP protocol&lt;/p&gt;

&lt;p&gt;The client receives HTTP text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;87&lt;/span&gt;
&lt;span class="na"&gt;Set-Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sessionid=abc123; HttpOnly; Path=/&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full circle. Bytes in, bytes out. Everything in between was Django. Digestion complete.&lt;/p&gt;




&lt;h2&gt;
  
  
  That's the full trip
&lt;/h2&gt;

&lt;p&gt;Most of us live in section 5. The view, the serializer, the business logic. That's where the product lives: that's normal.&lt;/p&gt;

&lt;p&gt;But the next time &lt;code&gt;request.user&lt;/code&gt; is an &lt;code&gt;AnonymousUser&lt;/code&gt; we didn't expect, or a response header is missing, or a 404 shows up before our code even has a chance to run, we'll know exactly which layer to look at.&lt;/p&gt;

&lt;p&gt;The machinery is not magic. It's layers, each doing one job, in a specific order. Knowing this also helps us customize some behavior, if required by our business logic.&lt;/p&gt;

&lt;p&gt;Which layer have we had to dig into when debugging? Drop it in the comments.&lt;/p&gt;

</description>
      <category>django</category>
      <category>middleware</category>
      <category>requestjourney</category>
      <category>tcphttpsocket</category>
    </item>
    <item>
      <title>Structuring a Django REST Framework API Project: How Do I Go About It?</title>
      <dc:creator>Wesley E. MONTCHO</dc:creator>
      <pubDate>Mon, 23 Mar 2026 00:33:55 +0000</pubDate>
      <link>https://dev.to/wesleyeliel/structuring-a-django-rest-framework-api-project-how-do-i-go-about-it-5g9e</link>
      <guid>https://dev.to/wesleyeliel/structuring-a-django-rest-framework-api-project-how-do-i-go-about-it-5g9e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is my first technical blog post. More will follow on Django, architecture, and things I learned accross years. Discussions and comments are very welcome.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;A few weeks ago, a senior Django developer (more experienced than me 😅) joined a project I had been building solo. After reviewing the codebase, he sent me this message (translated and paraphrased from French):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"There are quite a few things that are not well finished. Some deficiencies. But there are also some very beautiful things that allowed me to move fast."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence is why I told myself: why not start writing, both to preserve what worked and to connect with others? So here I am. 👋&lt;/p&gt;

&lt;p&gt;Properly structuring a project, especially a large one, is crucial. It's about making decisions early that let you, and others, navigate confidently, add features safely, and hand the project off without needing to be called back every five minutes.&lt;/p&gt;

&lt;p&gt;Let's get into it. 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Django's Default Layout
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;django-admin startproject backend&lt;/code&gt;, you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
├── backend/
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── manage.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you add your first app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py startapp myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And suddenly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
├── myapp/
│   ├── models.py
│   ├── views.py
│   ├── admin.py
│   └── tests.py
├── backend/
│   └── settings.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works perfectly. For a tutorial. 😬&lt;/p&gt;

&lt;p&gt;In a real production context, &lt;code&gt;models.py&lt;/code&gt; quickly hits 1,000+ lines. &lt;code&gt;views.py&lt;/code&gt; become too long for reading. &lt;code&gt;settings.py&lt;/code&gt; mixes database config, email config, AWS config, etc.&lt;/p&gt;

&lt;p&gt;So, to ensure proper scaling as the app grow, it becom natural to: &lt;strong&gt;design our app from day one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's jump on that.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Top-Level Split: &lt;code&gt;apps/&lt;/code&gt; and &lt;code&gt;backend/&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first and most impactful decision: &lt;strong&gt;separate your Django apps from your project configuration&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── backend/           ← Django project config (settings, urls, celery…)
├── apps/              ← All your Django apps live here
├── docs/              ← Documentation
├── scripts/           ← Utility scripts
├── tests/             ← Test suite
├── manage.py
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any developer opening this repo for the first time immediately understands the map:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;backend/&lt;/code&gt; → framework config, don't touch without a reason&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apps/&lt;/code&gt; → this is where the features and business logics are implemented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is simple, obvious with no ambiguity. That's the goal.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The &lt;code&gt;backend/&lt;/code&gt; Folder: Config That Breathes
&lt;/h2&gt;

&lt;p&gt;Still with me? Good. 👌&lt;/p&gt;

&lt;p&gt;The most common mistake inside &lt;code&gt;backend/&lt;/code&gt; is keeping a single monolithic &lt;code&gt;settings.py&lt;/code&gt;. The &lt;code&gt;settings.py&lt;/code&gt; is where django settings, third parti apps settings live. So JWT, email, AWS, rate limiting: all in one file becomes a lot of config with no real structure as the app grow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split settings by domain:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
├── settings/
│   ├── __init__.py      ← Loads sub-modules
│   ├── base.py          ← Core Django config (installed apps, middleware, DB…)
│   ├── smtp.py          ← Email config only
│   ├── ...
│   └── configs/
│       └── storages.py  ← S3/media storage config
├── urls.py              ← Root URL dispatcher
├── celery.py            ← Celery app init (if using async tasks)
├── asgi.py
└── wsgi.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# backend/settings/smtp.py: email concerns only
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.core.mail.backends.smtp.EmailBackend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL_PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_USE_TLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# backend/settings/configs/storages.py: storage concerns only
&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_FILE_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storages.backends.s3boto3.S3Boto3Storage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;AWS_STORAGE_BUCKET_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_BUCKET_NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a DevOps engineer needs to update email config, they go directly to &lt;code&gt;smtp.py&lt;/code&gt;. They don't have to read 400 lines of unrelated settings. This is respect for the next developer's time. 🙏&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The &lt;code&gt;apps/&lt;/code&gt; Folder: One Domain, One App
&lt;/h2&gt;

&lt;p&gt;Every feature domain gets its own Django app. No monolithic &lt;code&gt;core/&lt;/code&gt; app that holds everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
├── authentication/    ← Registration, login, JWT…
├── transactions/      ← Transactions, refunds…
├── users/             ← User model, profiles…
├── notifications/     ← Email, SMS, Push, WhatsApp…
├── marketing/         ← Referrals, contact messages…
├── settings/          ← System and per-user settings
└── utils/             ← Shared code (base model, enums, errors, middleware)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each app is a &lt;strong&gt;bounded context&lt;/strong&gt;: it owns its models, its views, its serializers, its business logic. If tomorrow you extract &lt;code&gt;notifications/&lt;/code&gt; into a separate microservice, the limits are already drawn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule I apply:&lt;/strong&gt; if you can describe it in one domain noun, it's an app. &lt;code&gt;Authentication&lt;/code&gt;, &lt;code&gt;Transactions&lt;/code&gt;, &lt;code&gt;Notifications&lt;/code&gt;. Not &lt;code&gt;Stuff&lt;/code&gt;, not &lt;code&gt;Core&lt;/code&gt;, not &lt;code&gt;Api&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Inside an App: Every File Has a Job
&lt;/h2&gt;

&lt;p&gt;Still following? 👀 This is where most Django projects diverge from best practices.&lt;/p&gt;

&lt;p&gt;The generated &lt;code&gt;models.py&lt;/code&gt; / &lt;code&gt;views.py&lt;/code&gt; monolith pattern appear to not ease scaling. Here's what a well-structured app looks like instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/transactions/
├── models/
│   ├── __init__.py          ← Star-imports all model files
│   ├── transactions.py
│   ├── refunds.py
│   └── …
├── serializers/
│   ├── transactions.py
│   ├── refunds.py
│   └── …
├── views/
│   ├── transactions.py
│   ├── refunds.py
│   └── …
├── services/
│   ├── transaction_processor.py
│   └── refund_service.py
├── admin/
│   ├── __init__.py
│   └── transactions.py
├── router.py
├── urls.py
├── tasks.py     ← Celery tasks (can be split too)
├── apps.py
└── tests.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Split models into sub-files
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;models/__init__.py&lt;/code&gt; re-exports everything via star imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/transactions/models/__init__.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.transactions.models.transactions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.transactions.models.refunds&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From Django's perspective, nothing changes: &lt;code&gt;apps.transactions.Transaction&lt;/code&gt; still works. From a developer's perspective, everything changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each model file stays focused and readable (&amp;lt; 300 lines)&lt;/li&gt;
&lt;li&gt;Git merge conflicts are rare and simple to resolve when they happen&lt;/li&gt;
&lt;li&gt;Searching "transactions" in your file list returns models, services, serializers, all at once&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;services/&lt;/code&gt; layer
&lt;/h3&gt;

&lt;p&gt;Views should not contain business logic. This isn't a rule I invented. It's a principle that saves you every time you need to reuse logic in a task, a management command, or a test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Business logic in the view — classic antipattern
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionViewSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelViewSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dest_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;dest_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User not available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...50 more lines of business logic...
&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ The view delegates to a service
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionViewSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelViewSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CreateTransactionSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raise_exception&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TransactionProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validated_data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransactionSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TransactionProcessor.create_transaction()&lt;/code&gt; can now be called from a view, a task, a test, or a management command, without changing a single line. 💪&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Base Model: Your Foundation for Every Table
&lt;/h2&gt;

&lt;p&gt;Every model in this project inherits from one class. Just one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/utils/models/base_model.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django_softdelete.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SoftDeleteModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractCommonBaseModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SoftDeleteModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every model then uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.utils.models.base_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AbstractCommonBaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractCommonBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# uuid, active, timestamp, updated → already there for free
&lt;/span&gt;    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pure DRY. You get for free, on every table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uuid&lt;/code&gt; (PK)&lt;/td&gt;
&lt;td&gt;No sequential IDs leaking record counts; safe in URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;active&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logical on/off switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creation date, always available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Last modification date, always available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soft delete&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.delete()&lt;/code&gt; marks the row deleted, doesn't destroy it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Soft delete, specifically, has become a habit over the years. You never permanently remove a sensitive record. Even if a user "deletes" something from their view, the row stays for audits, disputes, and reconciliation. 🔍&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining this in the base model means you can never forget it.&lt;/strong&gt; The pattern is enforced at the class hierarchy level.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The &lt;code&gt;utils/&lt;/code&gt; App: Your Project's Internal Library
&lt;/h2&gt;

&lt;p&gt;Every project accumulates shared code. The question is whether it lives scattered everywhere or in one dedicated place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/utils/
├── models/
│   └── base_model.py           ← AbstractCommonBaseModel
├── xlib/                       ← "extended library" (your project's vocabulary)
│   ├── enums/
│   │   ├── base.py             ← BaseEnum
│   │   ├── errors.py           ← All error codes
│   │   ├── transactions.py     ← TransactionStatusEnum, etc.
│   │   └── users.py            ← UserStatusEnum, etc.
│   ├── error_util.py           ← Centralized error resolution
│   ├── exceptions.py           ← Custom domain exceptions
│   └── validators.py           ← Custom validators
├── middleware/
│   └── …
├── commons/
│   └── mixins.py               ← Reusable ViewSet mixins
└── views/
    └── errors.py               ← Public error map endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One pattern worth highlighting: the &lt;strong&gt;Enum system&lt;/strong&gt;. Instead of scattering verbose choice tuples across model files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ The old way, repeated everywhere
&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PENDING&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Failed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...])&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ One enum, reused everywhere
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.utils.xlib.enums&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TransactionStatusEnum&lt;/span&gt;

&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TransactionStatusEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TransactionStatusEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One source of truth. No copy-paste drift, no Model.BLABLABLA. 🎯&lt;/p&gt;




&lt;h2&gt;
  
  
  7. URL Routing: Three Layers, Clear Contracts
&lt;/h2&gt;

&lt;p&gt;Every app follows the same three-layer URL pattern. No exceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 (&lt;code&gt;router.py&lt;/code&gt;): Register ViewSets&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/transactions/router.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.routers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultRouter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.transactions.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TransactionViewSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RefundViewSet&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RefundViewSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransactionViewSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;transactions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;urls_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Layer 2 (&lt;code&gt;urls.py&lt;/code&gt;): App URL contract&lt;/strong&gt; (router URLs + any manual &lt;code&gt;path()&lt;/code&gt; entries)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/transactions/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apps.transactions.router&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urls_patterns&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urls_patterns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Layer 3 (&lt;code&gt;backend/urls.py&lt;/code&gt;): Root dispatcher, versioned&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1/auth/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps.authentication.urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1/transactions/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps.transactions.urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1/users/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps.users.urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="c1"&gt;# …
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new developer looking for "where does the transaction list endpoint come from" follows this path in under 60 seconds. No magic. No hunting. 🗺️&lt;/p&gt;

&lt;p&gt;When you need &lt;code&gt;/v2/&lt;/code&gt;, you add a second block without touching the first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: &lt;code&gt;scripts/&lt;/code&gt; and &lt;code&gt;docs/&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Small things that signal a professional codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/
├── rmpycaches.py        ← Clear all __pycache__ folders
└── rmmigrationfiles.py  ← Remove migration files (for dev resets)

docs/
├── modelings/           ← (For example ) Use cases, class diagrams, sequence diagrams
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Real Impact
&lt;/h2&gt;

&lt;p&gt;We're almost done, and this last part is the reason any of this matters. 🏁&lt;/p&gt;

&lt;p&gt;The senior developer who reviewed my codebase moved fast. He could navigate without asking me where things were. That's the metric that matters: not "is the code clever" but "can the next person read it?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;During development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You never wonder "where should I put this?". The structure answers.&lt;/li&gt;
&lt;li&gt;Features are isolated: adding &lt;code&gt;marketing/&lt;/code&gt; doesn't touch &lt;code&gt;transactions/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The base model prevents forgotten patterns (timestamps, soft delete)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;During code review:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRs stay small because changes live in one app's files&lt;/li&gt;
&lt;li&gt;Reviewers know exactly where to look&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;During onboarding:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new developer maps the codebase in 20 minutes by reading folder names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;backend/urls.py&lt;/code&gt; is the full table of contents for the API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;During incidents:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug in transaction creation? Check &lt;code&gt;services/transaction_processor.py&lt;/code&gt;, not a 1,000-line &lt;code&gt;views.py&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;During refactoring:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extracting &lt;code&gt;notifications/&lt;/code&gt; into a microservice means moving one folder, not grepping scattered code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are so many other small tips (😅) i used to use when it come to prepare my api for scaling, i will try to talk about them in my next posts.&lt;/p&gt;




&lt;h2&gt;
  
  
  So, now, to resume: The takeaway Checklist
&lt;/h2&gt;

&lt;p&gt;For your next Django project, or to refactor your current one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Name your config folder &lt;code&gt;backend/&lt;/code&gt; (or &lt;code&gt;config/&lt;/code&gt;), not after your project&lt;/li&gt;
&lt;li&gt;[ ] Put all apps under &lt;code&gt;apps/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Split &lt;code&gt;settings.py&lt;/code&gt; by domain: &lt;code&gt;smtp.py&lt;/code&gt;, &lt;code&gt;storages.py&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;[ ] Create &lt;code&gt;AbstractCommonBaseModel&lt;/code&gt; with UUID PK, timestamps, and soft delete&lt;/li&gt;
&lt;li&gt;[ ] Split &lt;code&gt;models.py&lt;/code&gt; into &lt;code&gt;models/&amp;lt;domain&amp;gt;.py&lt;/code&gt; files, re-exported via &lt;code&gt;__init__.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add a &lt;code&gt;services/&lt;/code&gt; layer: views call services, not raw ORM&lt;/li&gt;
&lt;li&gt;[ ] Create &lt;code&gt;apps/utils/&lt;/code&gt; as your project's internal library&lt;/li&gt;
&lt;li&gt;[ ] Use enums for model choices (one source of truth)&lt;/li&gt;
&lt;li&gt;[ ] Follow the three-layer URL pattern: &lt;code&gt;router.py&lt;/code&gt; → &lt;code&gt;urls.py&lt;/code&gt; → &lt;code&gt;backend/urls.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add a &lt;code&gt;scripts/&lt;/code&gt; folder for repetitive dev commands&lt;/li&gt;
&lt;li&gt;[ ] Write at least a minimal &lt;code&gt;docs/&lt;/code&gt;. Your future self will thank you.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next post: &lt;strong&gt;Standardized error handling in Django REST Framework, with i18n support and a React integration example&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every API returns errors. The question is whether they're consistent, translatable, and useful to the frontend consuming them. I'll show you the &lt;code&gt;ErrorEnum&lt;/code&gt; + &lt;code&gt;ERROR_MAP&lt;/code&gt; pattern, how &lt;code&gt;drf-standardized-errors&lt;/code&gt; shapes every response, and how to wire it up so a React app displays the right message in the right language, without any if/else chains.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you remark something I got wrong, or if you do it differently and it works better, comment below. I'm here to learn as much as to share.&lt;/em&gt; 🤝&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
