<?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: Saleh Azimidokht</title>
    <description>The latest articles on DEV Community by Saleh Azimidokht (@cyberhuginn).</description>
    <link>https://dev.to/cyberhuginn</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3409751%2Fdcee882c-84e5-44eb-ad1d-a3037b029a45.png</url>
      <title>DEV Community: Saleh Azimidokht</title>
      <link>https://dev.to/cyberhuginn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cyberhuginn"/>
    <language>en</language>
    <item>
      <title>The API Worked. The Architecture Didn’t.</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Sat, 19 Sep 2026 15:04:32 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/the-api-worked-the-architecture-didnt-4m9i</link>
      <guid>https://dev.to/cyberhuginn/the-api-worked-the-architecture-didnt-4m9i</guid>
      <description>&lt;p&gt;The API worked.&lt;/p&gt;

&lt;p&gt;Requests returned &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The database had the right data.&lt;/p&gt;

&lt;p&gt;Tests were passing.&lt;/p&gt;

&lt;p&gt;The frontend was happy.&lt;/p&gt;

&lt;p&gt;And yet, the system was becoming harder to operate every week.&lt;/p&gt;

&lt;p&gt;This is one of the easiest traps in backend development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A working API does not necessarily mean a working architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An endpoint can be perfectly implemented while the system around it is slowly becoming fragile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Endpoint Looks Fine
&lt;/h2&gt;

&lt;p&gt;Imagine a simple endpoint:&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="err"&gt;POST /orders/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates an order and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&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;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="w"&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;The implementation might look completely reasonable:&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;def&lt;/span&gt; &lt;span class="nf"&gt;create_order&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;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;send_confirmation_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;update_inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;notify_warehouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing looks obviously wrong.&lt;/p&gt;

&lt;p&gt;The API works.&lt;/p&gt;

&lt;p&gt;The tests pass.&lt;/p&gt;

&lt;p&gt;The response is correct.&lt;/p&gt;

&lt;p&gt;But now imagine that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sending the email takes 800ms&lt;/li&gt;
&lt;li&gt;inventory is handled by another service&lt;/li&gt;
&lt;li&gt;warehouse notification sometimes takes 2 seconds&lt;/li&gt;
&lt;li&gt;one of those services occasionally times out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly, creating an order is no longer just creating an order.&lt;/p&gt;

&lt;p&gt;The problem is not the endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem is the architecture behind it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Correctness Is Not the Same as Architecture
&lt;/h2&gt;

&lt;p&gt;There are several different questions we should ask about a backend system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the API work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does it return the expected response?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the business logic correct?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does it enforce the right rules?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the system reliable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What happens when dependencies fail?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it scalable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What happens when traffic increases?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it maintainable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can another engineer safely change it six months from now?&lt;/p&gt;

&lt;p&gt;These are different properties.&lt;/p&gt;

&lt;p&gt;A system can be correct and still be difficult to scale.&lt;/p&gt;

&lt;p&gt;It can be fast and still be unreliable.&lt;/p&gt;

&lt;p&gt;It can pass every test and still have a bad architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Request Can Do Too Much
&lt;/h2&gt;

&lt;p&gt;A common pattern is gradually adding responsibilities to an endpoint.&lt;/p&gt;

&lt;p&gt;It starts simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Validate
   ↓
Save
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then requirements arrive.&lt;/p&gt;

&lt;p&gt;"Also send an SMS."&lt;/p&gt;

&lt;p&gt;"Also update analytics."&lt;/p&gt;

&lt;p&gt;"Also notify the admin."&lt;/p&gt;

&lt;p&gt;"Also generate the invoice."&lt;/p&gt;

&lt;p&gt;"Also sync with the external service."&lt;/p&gt;

&lt;p&gt;Eventually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌── Email
                    ├── SMS
                    ├── Analytics
Request → API → DB ─┼── Invoice
                    ├── External API
                    └── Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every individual operation might be valid.&lt;/p&gt;

&lt;p&gt;The architecture is the problem.&lt;/p&gt;

&lt;p&gt;The request lifecycle has become coupled to everything that happens after the database write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Makes It More Complicated
&lt;/h2&gt;

&lt;p&gt;Suppose the database transaction succeeds.&lt;/p&gt;

&lt;p&gt;Then the external API fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Order
     ↓
Database ✓
     ↓
Send Notification
     ↓
External API ✗
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should the order be rolled back?&lt;/p&gt;

&lt;p&gt;Usually not.&lt;/p&gt;

&lt;p&gt;But now the API needs to deal with partial failure.&lt;/p&gt;

&lt;p&gt;Maybe we retry.&lt;/p&gt;

&lt;p&gt;What if the retry succeeds but the API request times out?&lt;/p&gt;

&lt;p&gt;The client might retry the entire request.&lt;/p&gt;

&lt;p&gt;Now we could create the order twice.&lt;/p&gt;

&lt;p&gt;So we need idempotency.&lt;/p&gt;

&lt;p&gt;Then an idempotency key.&lt;/p&gt;

&lt;p&gt;Then somewhere to store that key.&lt;/p&gt;

&lt;p&gt;A seemingly simple endpoint has turned into a distributed systems problem.&lt;/p&gt;

&lt;p&gt;Not because someone wrote terrible code.&lt;/p&gt;

&lt;p&gt;Because multiple responsibilities were connected to one request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Synchronous Everything Problem
&lt;/h2&gt;

&lt;p&gt;One of the easiest architectural mistakes is doing everything synchronously.&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;def&lt;/span&gt; &lt;span class="nf"&gt;create_order&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;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_order&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="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;sync_with_partner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;generate_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API response now depends on all of these operations.&lt;/p&gt;

&lt;p&gt;If the slowest operation takes three seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API latency ≈ database + email + SMS + partner API + report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if creating the order itself takes only 20ms.&lt;/p&gt;

&lt;p&gt;A better architecture might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Create Order
   ↓
Commit
   ↓
Response
   │
   └── Background Work
          ├── Email
          ├── SMS
          ├── Partner Sync
          └── Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not simply "use a queue."&lt;/p&gt;

&lt;p&gt;The important part is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate operations that must happen before the response from operations that can happen after it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes that means a background worker.&lt;/p&gt;

&lt;p&gt;Sometimes an event.&lt;/p&gt;

&lt;p&gt;Sometimes an outbox.&lt;/p&gt;

&lt;p&gt;Sometimes simply moving non-critical work outside the request path.&lt;/p&gt;

&lt;p&gt;The architecture should follow the actual business requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Can Hide Problems Too
&lt;/h2&gt;

&lt;p&gt;Consider a perfectly valid ORM query:&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;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&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;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;For 20 orders, it might be fine.&lt;/p&gt;

&lt;p&gt;For 20,000 orders, you may have an N+1 query problem.&lt;/p&gt;

&lt;p&gt;The API contract didn't change.&lt;/p&gt;

&lt;p&gt;The endpoint still works.&lt;/p&gt;

&lt;p&gt;But the system doesn't behave well under realistic load.&lt;/p&gt;

&lt;p&gt;The same thing happens with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing indexes&lt;/li&gt;
&lt;li&gt;unbounded queries&lt;/li&gt;
&lt;li&gt;large payloads&lt;/li&gt;
&lt;li&gt;expensive serialization&lt;/li&gt;
&lt;li&gt;unnecessary joins&lt;/li&gt;
&lt;li&gt;repeated external calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A working endpoint is not proof that the data access pattern is healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  "We'll Add Caching Later"
&lt;/h2&gt;

&lt;p&gt;Caching is another common architectural band-aid.&lt;/p&gt;

&lt;p&gt;A query becomes slow.&lt;/p&gt;

&lt;p&gt;Instead of asking why, we add Redis.&lt;/p&gt;

&lt;p&gt;Sometimes that's exactly the right solution.&lt;/p&gt;

&lt;p&gt;But sometimes the real problem is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Missing index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N+1 queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetching data we don't need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doing an expensive aggregation on every request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a cache can make the endpoint faster while hiding the actual bottleneck.&lt;/p&gt;

&lt;p&gt;Now we have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
 ↓
Redis
 ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;plus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cache invalidation&lt;/li&gt;
&lt;li&gt;TTL decisions&lt;/li&gt;
&lt;li&gt;stale data&lt;/li&gt;
&lt;li&gt;cache misses&lt;/li&gt;
&lt;li&gt;cache failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system is faster.&lt;/p&gt;

&lt;p&gt;But also more complicated.&lt;/p&gt;

&lt;p&gt;Performance improvements should start with measurement and understanding the bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microservices Don't Automatically Fix Architecture
&lt;/h2&gt;

&lt;p&gt;Sometimes the response to a growing monolith is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let's split it into microservices."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But moving bad boundaries into separate services doesn't create good architecture.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     ↓
Payment Service
     ↓
Inventory Service
     ↓
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every request needs all four services synchronously, you've created a distributed monolith.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And network calls introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;partial failures&lt;/li&gt;
&lt;li&gt;service discovery&lt;/li&gt;
&lt;li&gt;observability requirements&lt;/li&gt;
&lt;li&gt;deployment coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microservices can solve real organizational and scaling problems.&lt;/p&gt;

&lt;p&gt;But service boundaries should exist because there is a meaningful boundary in the system.&lt;/p&gt;

&lt;p&gt;Not simply because the application has become large.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Is About Boundaries
&lt;/h2&gt;

&lt;p&gt;One of the most useful questions in backend development is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What should be allowed to depend on what?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Layer
    ↓
Application Logic
    ↓
Domain Logic
    ↓
Persistence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact architecture doesn't have to look like this.&lt;/p&gt;

&lt;p&gt;The important idea is boundaries.&lt;/p&gt;

&lt;p&gt;Your HTTP handler shouldn't need to know how an email provider works.&lt;/p&gt;

&lt;p&gt;Your domain logic shouldn't need to know about HTTP status codes.&lt;/p&gt;

&lt;p&gt;Your database model shouldn't become the entire business layer.&lt;/p&gt;

&lt;p&gt;Your notification system shouldn't determine whether an order is valid.&lt;/p&gt;

&lt;p&gt;Good boundaries make change cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask What Happens When Things Fail
&lt;/h2&gt;

&lt;p&gt;A happy-path architecture is easy to design.&lt;/p&gt;

&lt;p&gt;The real architecture appears when something fails.&lt;/p&gt;

&lt;p&gt;Ask questions like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the database is slow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does the entire application become slow?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if Redis is unavailable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can the application continue?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if an external API times out?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do we retry? How many times?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the client retries the request?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can we safely process it twice?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if a worker crashes halfway through a job?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can the job be retried safely?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the same event is processed twice?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Is the operation idempotent?&lt;/p&gt;

&lt;p&gt;These questions often reveal more about an architecture than another diagram of the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API Is Only the Surface
&lt;/h2&gt;

&lt;p&gt;When we review an API, we often focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST conventions&lt;/li&gt;
&lt;li&gt;response structure&lt;/li&gt;
&lt;li&gt;status codes&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;serializers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those things matter.&lt;/p&gt;

&lt;p&gt;But they don't tell the whole story.&lt;/p&gt;

&lt;p&gt;A backend system has many layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    API Contract
                         │
                         ↓
                  Application Logic
                         │
                         ↓
                    Data Access
                         │
                         ↓
                Infrastructure
                         │
                         ↓
                External Systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is the surface.&lt;/p&gt;

&lt;p&gt;The architecture is everything underneath it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Definition of "It Works"
&lt;/h2&gt;

&lt;p&gt;Instead of asking only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the API work?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Does the system behave correctly when the happy path disappears?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A production backend should have reasonable answers for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependency failures&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;duplicate requests&lt;/li&gt;
&lt;li&gt;concurrent operations&lt;/li&gt;
&lt;li&gt;large datasets&lt;/li&gt;
&lt;li&gt;increasing traffic&lt;/li&gt;
&lt;li&gt;partial failures&lt;/li&gt;
&lt;li&gt;background job failures&lt;/li&gt;
&lt;li&gt;database contention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every application needs an elaborate solution for all of these.&lt;/p&gt;

&lt;p&gt;That's important.&lt;/p&gt;

&lt;p&gt;Architecture is not about preparing a small application for billions of users.&lt;/p&gt;

&lt;p&gt;It's about preparing the system for the problems it is actually expected to face.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Architecture Boring
&lt;/h2&gt;

&lt;p&gt;Good backend architecture is often surprisingly boring.&lt;/p&gt;

&lt;p&gt;You might end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
 ↓
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's fine.&lt;/p&gt;

&lt;p&gt;Maybe you later need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
 ↓
PostgreSQL
 ↓
Background Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's fine too.&lt;/p&gt;

&lt;p&gt;Then perhaps traffic grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                ┌── API
Load Balancer ──┼── API
                └── API
                     │
                     ↓
                 PostgreSQL
                     │
                     ↓
                  Workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Architecture should evolve because requirements evolve.&lt;/p&gt;

&lt;p&gt;Not because technology is available.&lt;/p&gt;

&lt;p&gt;You don't get architectural maturity by adding more boxes to a diagram.&lt;/p&gt;

&lt;p&gt;You get it by understanding &lt;strong&gt;why each box exists&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;A good architecture should make the next change easier.&lt;/p&gt;

&lt;p&gt;If adding a new notification provider requires changing the order creation flow, you have coupling.&lt;/p&gt;

&lt;p&gt;If changing the database requires rewriting business logic, you have coupling.&lt;/p&gt;

&lt;p&gt;If one external service being unavailable prevents unrelated functionality from working, you have coupling.&lt;/p&gt;

&lt;p&gt;If scaling one feature requires scaling the entire application, you may have a boundary problem.&lt;/p&gt;

&lt;p&gt;The goal isn't to eliminate coupling.&lt;/p&gt;

&lt;p&gt;That's impossible.&lt;/p&gt;

&lt;p&gt;The goal is to make coupling &lt;strong&gt;intentional and understandable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The API can work perfectly.&lt;/p&gt;

&lt;p&gt;The tests can be green.&lt;/p&gt;

&lt;p&gt;The deployment can be successful.&lt;/p&gt;

&lt;p&gt;And the architecture can still be wrong for the system you're building.&lt;/p&gt;

&lt;p&gt;That's why backend engineering is more than writing endpoints.&lt;/p&gt;

&lt;p&gt;It's about understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependencies&lt;/li&gt;
&lt;li&gt;boundaries&lt;/li&gt;
&lt;li&gt;failure modes&lt;/li&gt;
&lt;li&gt;data ownership&lt;/li&gt;
&lt;li&gt;consistency&lt;/li&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;scaling&lt;/li&gt;
&lt;li&gt;operational cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API is what the client sees.&lt;/p&gt;

&lt;p&gt;The architecture is what determines whether the system remains healthy as the system grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A working API is a feature.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A sustainable architecture is a system.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Read the full version
&lt;/h3&gt;

&lt;p&gt;This article is part of my backend engineering notes.&lt;/p&gt;

&lt;p&gt;I publish the full version, along with other practical backend architecture articles, on my personal site:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cyberhuginn.com/notes/the-api-worked-the-architecture-didnt" rel="noopener noreferrer"&gt;CyberHuginn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're interested in backend engineering, system design, APIs, databases, and the trade-offs behind real-world systems, you may find the other notes useful too.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>That doesn’t mean you should use Redis for everything!</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Sat, 19 Sep 2026 12:00:14 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/that-doesnt-mean-you-should-use-redis-for-everything-56kl</link>
      <guid>https://dev.to/cyberhuginn/that-doesnt-mean-you-should-use-redis-for-everything-56kl</guid>
      <description>&lt;p&gt;Redis is fast.&lt;/p&gt;

&lt;p&gt;That doesn’t mean you should use Redis for everything.&lt;/p&gt;

&lt;p&gt;It’s easy to reach for Redis whenever you need caching, OTPs, rate limiting, locks, queues, sessions, or temporary data.&lt;/p&gt;

&lt;p&gt;But every new infrastructure component comes with a cost.&lt;/p&gt;

&lt;p&gt;More services to operate.&lt;br&gt;
More failure modes.&lt;br&gt;
More things to monitor.&lt;br&gt;
More distributed-state problems to reason about.&lt;/p&gt;

&lt;p&gt;Sometimes Redis is exactly the right tool.&lt;/p&gt;

&lt;p&gt;Sometimes PostgreSQL is enough.&lt;/p&gt;

&lt;p&gt;And sometimes the simplest solution is not adding another component at all.&lt;/p&gt;

&lt;p&gt;I wrote about how I think about these trade-offs, and when Redis can add more complexity than value.&lt;/p&gt;

&lt;p&gt;Read the full article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cyberhuginn.com/notes/when-redis-is-not-the-solution" rel="noopener noreferrer"&gt;https://cyberhuginn.com/notes/when-redis-is-not-the-solution&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>infrastructure</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>The Hidden Cost of Django REST Framework Serializers</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Mon, 07 Sep 2026 08:21:04 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/the-hidden-cost-of-django-rest-framework-serializers-1d0c</link>
      <guid>https://dev.to/cyberhuginn/the-hidden-cost-of-django-rest-framework-serializers-1d0c</guid>
      <description>&lt;p&gt;Your Django REST Framework API can have fast database queries and still be slow.&lt;/p&gt;

&lt;p&gt;That's one of the most confusing performance problems in Django applications.&lt;/p&gt;

&lt;p&gt;You check PostgreSQL.&lt;/p&gt;

&lt;p&gt;The queries look fast.&lt;/p&gt;

&lt;p&gt;You optimize your views.&lt;/p&gt;

&lt;p&gt;Everything seems fine.&lt;/p&gt;

&lt;p&gt;But the endpoint is still taking hundreds of milliseconds—or even seconds.&lt;/p&gt;

&lt;p&gt;One place developers often forget to investigate is the &lt;strong&gt;Django REST Framework serializer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A serializer is not always just converting Python objects into JSON.&lt;/p&gt;

&lt;p&gt;It can trigger database queries, access relationships, execute Python code, calculate fields, and recursively serialize nested objects.&lt;/p&gt;

&lt;p&gt;And that's where things can get expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The N+1 Query Problem Hiding Inside a Serializer
&lt;/h2&gt;

&lt;p&gt;Consider a simple serializer:&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;VehicleSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;customer_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SerializerMethodField&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;plate_number&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;customer_name&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_customer_name&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;obj&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;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks perfectly reasonable.&lt;/p&gt;

&lt;p&gt;But where does &lt;code&gt;obj.customer&lt;/code&gt; come from?&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;customer&lt;/code&gt; relationship hasn't been loaded, Django may execute another SQL query.&lt;/p&gt;

&lt;p&gt;With 100 vehicles, you could end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 query   → fetch vehicles
100 queries → fetch customers

Total: 101 queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the classic &lt;strong&gt;N+1 query problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the dangerous part is that the serializer itself doesn't look like a database operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  SerializerMethodField Is Not Free
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SerializerMethodField&lt;/code&gt; is one of my favorite DRF features.&lt;/p&gt;

&lt;p&gt;It's also one of the easiest ways to accidentally create expensive APIs.&lt;/p&gt;

&lt;p&gt;Consider:&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;ServiceSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SerializerMethodField&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_last_service&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;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Service&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-created_at&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="nf"&gt;first&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;This performs a database query every time &lt;code&gt;get_last_service()&lt;/code&gt; is called.&lt;/p&gt;

&lt;p&gt;Serialize 500 services?&lt;/p&gt;

&lt;p&gt;Potentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 main query
+
500 additional queries
=
501 queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is readable.&lt;/p&gt;

&lt;p&gt;The API is correct.&lt;/p&gt;

&lt;p&gt;And yet the performance can be terrible.&lt;/p&gt;

&lt;p&gt;This is why &lt;strong&gt;correct code isn't necessarily efficient code&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix Usually Isn't Inside the Serializer
&lt;/h2&gt;

&lt;p&gt;One of the most important lessons I've learned when optimizing Django APIs is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The serializer should not be responsible for discovering data that the database could have prepared.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of querying for every object, move the work into the QuerySet.&lt;/p&gt;

&lt;p&gt;For example, you can use &lt;code&gt;Subquery&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Subquery&lt;/span&gt;

&lt;span class="n"&gt;last_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ownership&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="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&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;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;last_service_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;last_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&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;Now the database prepares the information as part of the main query.&lt;/p&gt;

&lt;p&gt;Your serializer becomes simpler:&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;VehicleSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last_service_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;plate_number&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;last_service_id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
    ↓
Optimized QuerySet
    ↓
Annotations
    ↓
Serializer
    ↓
JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
    ↓
Serializer
    ↓
"Let's query something"
    ↓
Another query
    ↓
Another query
    ↓
Another query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  select_related() vs prefetch_related()
&lt;/h2&gt;

&lt;p&gt;When working with Django REST Framework serializers, you should always pay attention to the relationships being accessed.&lt;/p&gt;

&lt;p&gt;For single-valued relationships:&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;ForeignKey&lt;/span&gt;
&lt;span class="n"&gt;OneToOneField&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use:&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="nf"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&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;vehicles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&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;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&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;Now Django can retrieve the vehicle and customer using a SQL JOIN.&lt;/p&gt;

&lt;p&gt;For collections such as:&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;ManyToManyField&lt;/span&gt;
&lt;span class="n"&gt;Reverse&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use:&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="nf"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&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;services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Service&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;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple rule to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ForeignKey / OneToOne
        ↓
select_related()

ManyToMany / Reverse FK
        ↓
prefetch_related()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two methods are some of the most important tools for preventing N+1 queries in Django APIs. (&lt;a href="https://dev.to/highcenburg/how-i-caught-and-fixed-an-n1-query-in-my-django-rest-api-36p5?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested Serializers Make Things Worse
&lt;/h2&gt;

&lt;p&gt;Nested serializers are convenient.&lt;/p&gt;

&lt;p&gt;But convenience can hide expensive database access.&lt;/p&gt;

&lt;p&gt;Imagine:&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;ProductSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&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;ServiceSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;many&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;products&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine your API returns 1,000 services.&lt;/p&gt;

&lt;p&gt;Every service needs its products.&lt;/p&gt;

&lt;p&gt;Without prefetching, your API can start generating queries like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service query
Product query
Product query
Product query
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And nested relationships can go even deeper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service
 ├── Customer
 │    └── Address
 └── Products
      └── Category
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A response that looks innocent in JSON can represent a surprisingly expensive data-access pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Build One Giant Serializer
&lt;/h2&gt;

&lt;p&gt;Another common mistake is trying to use one serializer for everything.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VehicleSerializer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gets used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List endpoints&lt;/li&gt;
&lt;li&gt;Detail endpoints&lt;/li&gt;
&lt;li&gt;Create endpoints&lt;/li&gt;
&lt;li&gt;Update endpoints&lt;/li&gt;
&lt;li&gt;Admin endpoints&lt;/li&gt;
&lt;li&gt;Search endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, the serializer becomes huge.&lt;/p&gt;

&lt;p&gt;A list endpoint might only need:&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;VehicleListSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;plate_number&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;color&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While a detail endpoint can provide more information:&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;VehicleDetailSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CustomerSerializer&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;
        &lt;span class="n"&gt;fields&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;id&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;plate_number&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;color&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;customer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't unnecessary duplication.&lt;/p&gt;

&lt;p&gt;It's API design.&lt;/p&gt;

&lt;p&gt;A list endpoint shouldn't return an entire object graph just because the detail endpoint needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller Responses Are Usually Better
&lt;/h2&gt;

&lt;p&gt;More data isn't always better.&lt;/p&gt;

&lt;p&gt;Consider an API response containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
 ├── Vehicles
 ├── Services
 ├── Invoices
 ├── Payments
 ├── Addresses
 └── Orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might look impressive.&lt;/p&gt;

&lt;p&gt;But every additional relationship can increase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database work&lt;/li&gt;
&lt;li&gt;Serialization time&lt;/li&gt;
&lt;li&gt;Response size&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Network transfer&lt;/li&gt;
&lt;li&gt;Frontend processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API should return the data the client needs—not everything the database knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Before Optimizing
&lt;/h2&gt;

&lt;p&gt;This is probably the most important rule.&lt;/p&gt;

&lt;p&gt;Don't look at a serializer and say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This probably causes performance problems."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Measure it.&lt;/p&gt;

&lt;p&gt;For Django APIs, useful things to measure include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL query count&lt;/li&gt;
&lt;li&gt;SQL query duration&lt;/li&gt;
&lt;li&gt;Serializer execution time&lt;/li&gt;
&lt;li&gt;Total request duration&lt;/li&gt;
&lt;li&gt;Response size&lt;/li&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools such as Django Debug Toolbar, Django Silk, and APM platforms can help you find where the time is actually going.&lt;/p&gt;

&lt;p&gt;Because sometimes the database isn't the bottleneck.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database:       40 ms
View logic:     30 ms
Serialization: 650 ms
JSON rendering: 80 ms

Total:          800 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optimizing a 40 ms database query isn't going to solve your 800 ms API response.&lt;/p&gt;

&lt;p&gt;Profile the entire request.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple DRF Performance Checklist
&lt;/h2&gt;

&lt;p&gt;When a Django REST Framework endpoint becomes slow, I usually start here:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Count the queries
&lt;/h3&gt;

&lt;p&gt;How many SQL queries does the endpoint execute?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect SerializerMethodField
&lt;/h3&gt;

&lt;p&gt;Look for database access inside:&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;get_&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Inspect relationship access
&lt;/h3&gt;

&lt;p&gt;Look for:&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;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Check select_related()
&lt;/h3&gt;

&lt;p&gt;Are your &lt;code&gt;ForeignKey&lt;/code&gt; and &lt;code&gt;OneToOneField&lt;/code&gt; relationships loaded efficiently?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Check prefetch_related()
&lt;/h3&gt;

&lt;p&gt;Are your &lt;code&gt;ManyToMany&lt;/code&gt; and reverse relationships prefetched?&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Look at annotations
&lt;/h3&gt;

&lt;p&gt;Can a calculation be performed by the database instead of repeatedly inside Python?&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Reduce nested data
&lt;/h3&gt;

&lt;p&gt;Does the frontend actually need all those nested objects?&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Separate serializers
&lt;/h3&gt;

&lt;p&gt;Does the list endpoint really need the same serializer as the detail endpoint?&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Measure again
&lt;/h3&gt;

&lt;p&gt;Never assume the optimization worked.&lt;/p&gt;

&lt;p&gt;Measure before and after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of a Serializer
&lt;/h2&gt;

&lt;p&gt;Django REST Framework serializers aren't inherently slow.&lt;/p&gt;

&lt;p&gt;The problem is what we ask them to do.&lt;/p&gt;

&lt;p&gt;A serializer becomes expensive when it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executes database queries repeatedly&lt;/li&gt;
&lt;li&gt;Accesses unloaded relationships&lt;/li&gt;
&lt;li&gt;Performs expensive Python calculations&lt;/li&gt;
&lt;li&gt;Uses deeply nested serializers&lt;/li&gt;
&lt;li&gt;Returns unnecessary data&lt;/li&gt;
&lt;li&gt;Hides database access inside &lt;code&gt;SerializerMethodField&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best optimization is often not changing the serializer.&lt;/p&gt;

&lt;p&gt;It's changing the &lt;strong&gt;QuerySet behind it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about your API like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
View
   ↓
Optimized QuerySet
   ↓
Database
   ↓
Serializer
   ↓
Minimal JSON Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
View
   ↓
Huge QuerySet
   ↓
Serializer
   ↓
Query
   ↓
Query
   ↓
Query
   ↓
N+1
   ↓
Slow API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The next time you see a slow Django REST Framework endpoint, don't only inspect the SQL.&lt;/p&gt;

&lt;p&gt;Inspect what happens &lt;strong&gt;after the QuerySet is evaluated&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your database query might be fast.&lt;/p&gt;

&lt;p&gt;Your serializer might be the expensive part.&lt;/p&gt;

&lt;p&gt;And that hidden cost becomes much more visible as your dataset grows.&lt;/p&gt;

&lt;p&gt;I wrote a deeper technical breakdown with more examples, optimization strategies, &lt;code&gt;select_related()&lt;/code&gt;, &lt;code&gt;prefetch_related()&lt;/code&gt;, annotations, nested serializers, and practical debugging techniques:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://cyberhuginn.com/notes/the-hidden-cost-of-django-rest-framework-serializers" rel="noopener noreferrer"&gt;The Hidden Cost of Django REST Framework Serializers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building production Django APIs, understanding the relationship between &lt;strong&gt;QuerySets, serializers, and database queries&lt;/strong&gt; can make a huge difference.&lt;/p&gt;




&lt;h1&gt;
  
  
  django #python #djangorestframework #drf #backend #api #webdevelopment #performance #database #programming
&lt;/h1&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>drf</category>
      <category>performance</category>
    </item>
    <item>
      <title>I Built a Signed Webhook Receiver for Cross-Server Communication</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Tue, 11 Aug 2026 12:05:48 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/building-a-secure-webhook-receiver-for-server-to-server-communication-23d7</link>
      <guid>https://dev.to/cyberhuginn/building-a-secure-webhook-receiver-for-server-to-server-communication-23d7</guid>
      <description>&lt;p&gt;Sometimes your application can reach an external service from one server, but not from another.&lt;/p&gt;

&lt;p&gt;I ran into this problem while working on one of my projects. I needed my server in Iran to communicate with Telegram, but the connection wasn't reliable from inside Iran.&lt;/p&gt;

&lt;p&gt;Instead of moving the whole application, I built a small intermediate service:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signed Webhook Receiver&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is a lightweight &lt;strong&gt;FastAPI&lt;/strong&gt; service that receives requests signed with an RSA private key and verifies them using the corresponding public key before processing them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Server
     |
     | RSA Signed Request
     v
Webhook Receiver
     |
     | HTTP Request
     v
External Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The receiver can be useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure server-to-server communication&lt;/li&gt;
&lt;li&gt;Webhooks and internal APIs&lt;/li&gt;
&lt;li&gt;Acting as a controlled proxy/gateway&lt;/li&gt;
&lt;li&gt;Connecting servers across different network environments&lt;/li&gt;
&lt;li&gt;Payment integrations where a provider requires requests from an Iranian IP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if your main application is hosted outside Iran but a payment gateway only accepts requests from Iranian IP addresses, an Iranian server can act as the intermediate gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Foreign Server
      |
      | Signed Request
      v
Iranian Gateway Server
      |
      v
Payment Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that this isn't an open proxy. Requests can be authenticated and the gateway can be restricted to specific operations and destinations.&lt;/p&gt;

&lt;p&gt;The project is built with &lt;strong&gt;Python, FastAPI, Cryptography, Docker, and Traefik&lt;/strong&gt; and is open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cyberhuginn/signed-webhook-receiver" rel="noopener noreferrer"&gt;View the project on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also wrote more technical notes and development articles on my website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cyberhuginn.com/notes/building-secure-webhook-receiver-server-to-server-communication" rel="noopener noreferrer"&gt;Building a Secure Webhook Receiver for Server-to-Server Communication | CyberHuginn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>webhooks</category>
      <category>security</category>
    </item>
    <item>
      <title>Designing a Gold Jewelry E-Commerce Database with Django</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Tue, 04 Aug 2026 05:04:51 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/designing-a-gold-jewelry-e-commerce-database-with-django-16lc</link>
      <guid>https://dev.to/cyberhuginn/designing-a-gold-jewelry-e-commerce-database-with-django-16lc</guid>
      <description>&lt;p&gt;When I accepted a project to build an online gold jewelry store, I thought it would be just another e-commerce platform.&lt;/p&gt;

&lt;p&gt;Products. Orders. Payments.&lt;/p&gt;

&lt;p&gt;Nothing unusual.&lt;/p&gt;

&lt;p&gt;I couldn't have been more wrong.&lt;/p&gt;

&lt;p&gt;A few hours into the database design, I realized the real challenge wasn't Django—it was understanding the business.&lt;/p&gt;

&lt;p&gt;Unlike a regular online store, customers aren't buying a generic product. They're buying a very specific variation of that product.&lt;/p&gt;

&lt;p&gt;A single ring, for example, can exist in multiple versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different weights&lt;/li&gt;
&lt;li&gt;Different karats&lt;/li&gt;
&lt;li&gt;Different sizes&lt;/li&gt;
&lt;li&gt;Different gemstones&lt;/li&gt;
&lt;li&gt;Different making fees&lt;/li&gt;
&lt;li&gt;Different prices&lt;/li&gt;
&lt;li&gt;Different inventory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That simple observation completely changes how your database should be designed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Mistake
&lt;/h2&gt;

&lt;p&gt;Many developers model their orders like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order
 └── Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works perfectly for books, digital products, or simple inventory.&lt;/p&gt;

&lt;p&gt;It completely falls apart for jewelry.&lt;/p&gt;

&lt;p&gt;Because the customer isn't purchasing the &lt;strong&gt;Product&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They're purchasing one specific &lt;strong&gt;variation&lt;/strong&gt; of that product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Model
&lt;/h2&gt;

&lt;p&gt;Instead of attaching orders directly to &lt;code&gt;Product&lt;/code&gt;, I introduced another layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product
    └── ProductVariable
            ├── Price
            ├── Stock
            ├── Making Fee
            ├── Weight
            ├── Karat
            └── Attributes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every purchasable item has its own inventory, pricing, and specifications while still belonging to the same parent product.&lt;/p&gt;

&lt;p&gt;This approach also makes searching and filtering much easier because attributes become reusable across multiple products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start From Business Scenarios
&lt;/h2&gt;

&lt;p&gt;Before writing models, I always walk through real business scenarios.&lt;/p&gt;

&lt;p&gt;Questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly is the customer buying?&lt;/li&gt;
&lt;li&gt;Can inventory differ between variations?&lt;/li&gt;
&lt;li&gt;Can price change based on weight?&lt;/li&gt;
&lt;li&gt;What happens when an item is out of stock?&lt;/li&gt;
&lt;li&gt;Can customers order custom-made jewelry?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions usually define your entities long before you write your first Django model.&lt;/p&gt;




&lt;p&gt;This article only scratches the surface.&lt;/p&gt;

&lt;p&gt;In the full version, I explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to identify entities using Use Cases&lt;/li&gt;
&lt;li&gt;Building an ER Diagram&lt;/li&gt;
&lt;li&gt;Designing Django models&lt;/li&gt;
&lt;li&gt;Modeling &lt;code&gt;ProductVariable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Categories vs Tags&lt;/li&gt;
&lt;li&gt;Product Attributes&lt;/li&gt;
&lt;li&gt;Inventory management&lt;/li&gt;
&lt;li&gt;Ordering and payment architecture&lt;/li&gt;
&lt;li&gt;Real-world jewelry business scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Read the complete article here:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cyberhuginn.com/notes/designing-a-gold-jewelry-ecommerce-database-with-django" rel="noopener noreferrer"&gt;https://cyberhuginn.com/notes/designing-a-gold-jewelry-ecommerce-database-with-django&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building an e-commerce platform with Django, especially one that sells configurable products, this design pattern can save you from painful database refactoring later.&lt;/p&gt;

</description>
      <category>django</category>
      <category>database</category>
      <category>ecommerce</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Every Small SaaS Needs Uptime Monitoring</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:34:22 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/why-every-small-saas-needs-uptime-monitoring-387j</link>
      <guid>https://dev.to/cyberhuginn/why-every-small-saas-needs-uptime-monitoring-387j</guid>
      <description>&lt;p&gt;Building software has never been easier.&lt;/p&gt;

&lt;p&gt;Keeping it online is the hard part.&lt;/p&gt;

&lt;p&gt;Whether you're running a personal project, an internal API, a startup MVP, or a growing SaaS business, downtime can quickly become expensive. Users lose trust, support requests increase, and revenue disappears while you're unaware that something has gone wrong.&lt;/p&gt;

&lt;p&gt;The worst part?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most outages aren't discovered by monitoring systems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're discovered by users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That realization is exactly what led me to build &lt;strong&gt;Bidar&lt;/strong&gt;—a lightweight uptime monitoring platform focused on one simple goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Notify developers as soon as their services become unavailable.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Hidden Cost of Downtime
&lt;/h2&gt;

&lt;p&gt;It's easy to think of downtime as "just a server issue."&lt;/p&gt;

&lt;p&gt;In reality, the impact is much broader.&lt;/p&gt;

&lt;p&gt;Even a short outage can affect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User trust&lt;/li&gt;
&lt;li&gt;Customer satisfaction&lt;/li&gt;
&lt;li&gt;Conversion rates&lt;/li&gt;
&lt;li&gt;Search rankings&lt;/li&gt;
&lt;li&gt;Team productivity&lt;/li&gt;
&lt;li&gt;Brand reputation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your users don't care whether the issue was caused by a failed deployment, DNS problem, database outage, or cloud provider incident.&lt;/p&gt;

&lt;p&gt;They only know one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your service wasn't available when they needed it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Manual Monitoring Doesn't Scale
&lt;/h2&gt;

&lt;p&gt;In the beginning, many developers rely on manual checks.&lt;/p&gt;

&lt;p&gt;They refresh the website.&lt;/p&gt;

&lt;p&gt;They test an API endpoint.&lt;/p&gt;

&lt;p&gt;They occasionally check server metrics.&lt;/p&gt;

&lt;p&gt;That works for hobby projects.&lt;/p&gt;

&lt;p&gt;It stops working when real users depend on your application.&lt;/p&gt;

&lt;p&gt;You can't monitor your services 24 hours a day.&lt;/p&gt;

&lt;p&gt;You can't wake up every hour to verify your API is still responding.&lt;/p&gt;

&lt;p&gt;Automation eventually becomes a necessity.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Should You Monitor?
&lt;/h2&gt;

&lt;p&gt;A good monitoring solution should cover more than just your homepage.&lt;/p&gt;

&lt;p&gt;Some of the most valuable things to monitor include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website availability&lt;/li&gt;
&lt;li&gt;REST API endpoints&lt;/li&gt;
&lt;li&gt;SSL certificate expiration&lt;/li&gt;
&lt;li&gt;Response time&lt;/li&gt;
&lt;li&gt;HTTP status codes&lt;/li&gt;
&lt;li&gt;Critical customer-facing pages&lt;/li&gt;
&lt;li&gt;Internal services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monitoring the right endpoints allows you to detect problems before your customers notice them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes a Good Monitoring Tool?
&lt;/h2&gt;

&lt;p&gt;After working on several backend systems, I've found that a monitoring solution doesn't need hundreds of features.&lt;/p&gt;

&lt;p&gt;It needs to do a few things really well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fast Detection
&lt;/h3&gt;

&lt;p&gt;The sooner you're notified, the sooner you can fix the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reliable Alerts
&lt;/h3&gt;

&lt;p&gt;Notifications should arrive immediately through the channels you actually use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy Setup
&lt;/h3&gt;

&lt;p&gt;Monitoring should take minutes—not hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear Results
&lt;/h3&gt;

&lt;p&gt;The goal isn't generating pretty dashboards.&lt;/p&gt;

&lt;p&gt;The goal is answering one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Is my service healthy?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why I Built Bidar
&lt;/h2&gt;

&lt;p&gt;While working on Django projects and APIs, I wanted a monitoring service that focused on uptime instead of trying to be a complete observability platform.&lt;/p&gt;

&lt;p&gt;Many existing tools are incredibly powerful.&lt;/p&gt;

&lt;p&gt;They also come with dozens of dashboards, metrics, traces, and features that smaller teams often don't need.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Bidar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Its philosophy is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add your website or API.&lt;/li&gt;
&lt;li&gt;Configure monitoring.&lt;/li&gt;
&lt;li&gt;Get notified when something goes wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Just reliable uptime monitoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Is It For?
&lt;/h2&gt;

&lt;p&gt;Bidar is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indie Hackers&lt;/li&gt;
&lt;li&gt;SaaS founders&lt;/li&gt;
&lt;li&gt;Freelancers managing client websites&lt;/li&gt;
&lt;li&gt;Small development teams&lt;/li&gt;
&lt;li&gt;Backend developers&lt;/li&gt;
&lt;li&gt;Anyone running production APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If uptime matters to your users, monitoring should matter to you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Reliable software isn't just about writing good code.&lt;/p&gt;

&lt;p&gt;It's also about knowing when things stop working.&lt;/p&gt;

&lt;p&gt;Monitoring helps you detect issues faster, reduce downtime, and respond before your users start sending emails asking if your service is down.&lt;/p&gt;

&lt;p&gt;If you're looking for a lightweight uptime monitoring platform, I'd love for you to check out &lt;strong&gt;Bidar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://bidarhq.ir" rel="noopener noreferrer"&gt;https://bidarhq.ir&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm always looking for feedback from developers.&lt;/p&gt;

&lt;p&gt;If there's a feature you'd like to see, let me know!&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;I'm a backend developer focused on Django, distributed systems, developer tools, and open-source software.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://cyberhuginn.com" rel="noopener noreferrer"&gt;https://cyberhuginn.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/cyberhuginn" rel="noopener noreferrer"&gt;https://github.com/cyberhuginn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>saas</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Built a Lightweight Health Check Package for Django</title>
      <dc:creator>Saleh Azimidokht</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:30:06 +0000</pubDate>
      <link>https://dev.to/cyberhuginn/i-built-a-lightweight-health-check-package-for-django-1nj7</link>
      <guid>https://dev.to/cyberhuginn/i-built-a-lightweight-health-check-package-for-django-1nj7</guid>
      <description>&lt;p&gt;While working on several Django projects, I often needed a simple endpoint to verify that the application was actually healthy—not just running.&lt;/p&gt;

&lt;p&gt;Most existing solutions were either too heavy, required extra configuration, or didn’t provide the flexibility I wanted.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;django-healthkit&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The package lets you expose a health endpoint that can verify different parts of your application.&lt;/p&gt;

&lt;p&gt;Current checks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connectivity&lt;/li&gt;
&lt;li&gt;Cache availability&lt;/li&gt;
&lt;li&gt;Disk usage&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration is intentionally simple:&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;HEALTH_CHECKS&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;database&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;cache&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;disk&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;memory&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;cpu&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then expose the endpoint and you're ready to use it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker health checks&lt;/li&gt;
&lt;li&gt;Kubernetes probes&lt;/li&gt;
&lt;li&gt;Traefik&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Monitoring systems&lt;/li&gt;
&lt;li&gt;Uptime monitoring services&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;My goal wasn't to create another monitoring platform.&lt;/p&gt;

&lt;p&gt;I wanted a small package that answers one simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Is my Django application actually healthy?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No dashboards.&lt;/p&gt;

&lt;p&gt;No unnecessary dependencies.&lt;/p&gt;

&lt;p&gt;Just a clean JSON response that can be consumed by automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;This is only the first release.&lt;/p&gt;

&lt;p&gt;Some ideas planned for future versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom health checks&lt;/li&gt;
&lt;li&gt;Async support&lt;/li&gt;
&lt;li&gt;Response customization&lt;/li&gt;
&lt;li&gt;More built-in system checks&lt;/li&gt;
&lt;li&gt;Better integrations with monitoring tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  I'd love your feedback
&lt;/h2&gt;

&lt;p&gt;If you're using Django, I'd really appreciate your thoughts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What health checks are missing?&lt;/li&gt;
&lt;li&gt;What features would make this useful in production?&lt;/li&gt;
&lt;li&gt;Any ideas for improving the API?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/cyberhuginn/django-healthkit" rel="noopener noreferrer"&gt;https://github.com/cyberhuginn/django-healthkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/django-healthkit/" rel="noopener noreferrer"&gt;https://pypi.org/project/django-healthkit/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback and contributions are always welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;I'm a backend developer focused on Django, distributed systems, developer tools, and open-source software.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://cyberhuginn.com" rel="noopener noreferrer"&gt;https://cyberhuginn.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/cyberhuginn" rel="noopener noreferrer"&gt;https://github.com/cyberhuginn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
