<?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: santiravila_</title>
    <description>The latest articles on DEV Community by santiravila_ (@santiravila).</description>
    <link>https://dev.to/santiravila</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%2F3941107%2F2f227de0-5cfc-4ce5-8d1f-236acf30f927.jpeg</url>
      <title>DEV Community: santiravila_</title>
      <link>https://dev.to/santiravila</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/santiravila"/>
    <language>en</language>
    <item>
      <title>3 Lessons from Scaling My First FastAPI Backend</title>
      <dc:creator>santiravila_</dc:creator>
      <pubDate>Thu, 11 Jun 2026 15:27:17 +0000</pubDate>
      <link>https://dev.to/santiravila/3-lessons-from-scaling-my-first-fastapi-backend-5h12</link>
      <guid>https://dev.to/santiravila/3-lessons-from-scaling-my-first-fastapi-backend-5h12</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;There is a moment early in every software engineering journey where you must stop thinking in scripts and start thinking in systems. You may be currently working on a non-trivial CLI project or product and realizing that &lt;strong&gt;complexity increases disproportionately&lt;/strong&gt; as you write more code. That your code is &lt;strong&gt;tightly coupled&lt;/strong&gt; so making changes in one aspect affects others when it shouldn't or simply that you'd like to &lt;strong&gt;serve your application to more people across the internet&lt;/strong&gt; so now you need to consider traffic, scaling and robustness.   &lt;/p&gt;

&lt;p&gt;I faced that situation myself 5 months ago, and that's why I'm here to share 3 lessons I learned transforming a local Python CLI app into a containerized, layered REST API using FastAPI and Docker.&lt;/p&gt;

&lt;p&gt;For learning purposes, I decided keeping my domain models small, prioritizing depth over breadth. My guiding philosophy was &lt;strong&gt;Pain-Driven Development (PDD)&lt;/strong&gt;: I only added architectural complexity when the absence of it physically hurt my development process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on CLI vs. APIs:&lt;/strong&gt;&lt;br&gt;
Backend APIs are not inherently superior to CLI apps; they just serve different purposes. CLI apps are fantastic for Proofs of Concept. But the moment a product requires scalability, concurrency, or cloud deployment, it must evolve into a headless Backend API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Context: The Setup (Why WSL2 for Serious Python Projects)
&lt;/h2&gt;

&lt;p&gt;More than a reactive pain, this is a proactive action that's good to embrace as it is a good practice. &lt;/p&gt;

&lt;p&gt;This is because while basic Python snippets work the same on basically any OS, things take a turn once we start talking about production-level systems. &lt;br&gt;
The main reasons to use WSL2 (Windows Subsystem for Linux) for production-level Python applications are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parity with Production:&lt;/strong&gt; AWS, GCP, and Azure run on Linux. Developing on a totally different OS introduces deployment uncertainty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python Web Servers:&lt;/strong&gt; ASGI servers like Uvicorn use Linux-kernel-specific features extensively for networking. Trying to run them on Windows adds unnecessary overhead. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerization (Docker):&lt;/strong&gt; Docker is Linux-native. It leverages Linux kernel features like &lt;em&gt;namespaces&lt;/em&gt; and &lt;em&gt;cgroups&lt;/em&gt; for process isolation. As I will explain later, modern cloud backends must be containerized.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Lesson 1: The Fat Routers (Layering and Decoupling)
&lt;/h2&gt;

&lt;p&gt;Many beginners cram all their &lt;em&gt;endpoints&lt;/em&gt; into the entrypoint of their application (&lt;code&gt;main.py&lt;/code&gt;). Even worse, they write all the execution code directly inside those endpoints. That means cramming serialization, database connections, business logic, and HTTP semantics into a single function! &lt;/p&gt;

&lt;p&gt;This approach is easier at the very beginning, but it creates massive technical debt. It promotes &lt;em&gt;spaghetti code&lt;/em&gt;, hinders modularity, and, most importantly, makes testing your endpoints unfeasible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 1: The Fat Router (No SRP)]&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="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/bank-account&lt;/span&gt;&lt;span class="sh"&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;create_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AccountCreate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AccountRead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="c1"&gt;# 1. Deserialize (HTTP Concern)
&lt;/span&gt;    &lt;span class="n"&gt;dict_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Business Logic (Domain Concern)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Database Connection &amp;amp; Persistence (Infrastructure Concern)
&lt;/span&gt;    &lt;span class="n"&gt;db_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&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;AccountRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;dict_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Solution: Layered Architecture&lt;/strong&gt;&lt;br&gt;
Think of your app like an onion. Each layer has a specific responsibility. Inner layers must be completely oblivious to outer layers, while outer layers depend on inner layers. Dependencies point inwards.&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%2Fi.imgur.com%2FENHmKVm.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%2Fi.imgur.com%2FENHmKVm.png" alt="Layered Architecture" width="798" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain Layer:&lt;/strong&gt; Contains your core classes and the &lt;em&gt;invariants&lt;/em&gt; they enforce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository Layer:&lt;/strong&gt; Contains all persistence logic (Database connections, SQL, or JSON file writing).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Layer:&lt;/strong&gt; Your business logic (Use Cases). It orchestrates the Domain and the Repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router Layer:&lt;/strong&gt; Handles the HTTP lifecycle. It catches HTTP requests, calls the Service layer, and returns HTTP responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemas Layer:&lt;/strong&gt; Implements DTOs (Data Transfer Objects) so your endpoints know the exact JSON structure to accept and return.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note for MVC developers:&lt;/em&gt; Do not write a "Controller" layer. In FastAPI, the Router endpoints &lt;em&gt;are&lt;/em&gt; the controllers. Adding a separate controller layer is redundant. I made that mistake myself.&lt;/p&gt;

&lt;p&gt;Here is how &lt;strong&gt;[Code Block 1]&lt;/strong&gt; looks after applying Layered Architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 2: Layered Architecture (Hardcoded Dependencies)]&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="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/bank-account&lt;/span&gt;&lt;span class="sh"&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;create_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AccountCreate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AccountRead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# The Router only handles HTTP and pure Python translation!
&lt;/span&gt;    &lt;span class="n"&gt;account_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Instantiate the Service &amp;amp; Repo locally
&lt;/span&gt;    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccountRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccountService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Call the application logic
&lt;/span&gt;    &lt;span class="n"&gt;created_account&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="nf"&gt;create_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_domain&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;AccountRead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lesson 2: Testing &amp;amp; Dependency Injection (Hitting 80%+ Coverage)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 2]&lt;/strong&gt; is beautifully decoupled logically, but it has a fatal flaw. Look at the hardcoded lines: &lt;code&gt;repo = BankAccountRepository()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If we try to test this endpoint using FastAPI's &lt;code&gt;TestClient&lt;/code&gt;, look what happens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 3: The Leaky Test]&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.testclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; 

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&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;test_create_account_success&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;name&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;Test Account&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;balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/bank-account&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this and go check your production database. &lt;strong&gt;Congratulations: your testing code just leaked into production infrastructure.&lt;/strong&gt; Because the repository was hardcoded inside the endpoint, &lt;strong&gt;the test wrote fake data to your real database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter Dependency Injection (DI)&lt;/strong&gt;&lt;br&gt;
(DI from now on) is a structural design pattern. Instead of the endpoint creating its own repository, we &lt;em&gt;inject&lt;/em&gt; the required service into the endpoint from the outside. FastAPI natively supports this via the &lt;code&gt;Depends()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;You can intuitively think of the interplay between endpoints and DI as a contractor(endpoint) his materials supplier(repository). A contractor could manufacture his own lumber and steel, but then he's locked into whatever he built. By delegating that to a specialist supplier(Dependency provider), the contractor stays flexible — you can swap suppliers without touching the contractor at all.&lt;/p&gt;

&lt;p&gt;Let's refactor the endpoint one last time to use DI:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 4: The Injected Endpoint]&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;# Create the dependency provider
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_bank_account_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BankAccountService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccountRepository&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;BankAccountService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/bank-account&lt;/span&gt;&lt;span class="sh"&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;create_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AccountCreate&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;BankAccountService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_bank_account_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Provide the dependency
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AccountRead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;account_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;created_account&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="nf"&gt;create_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_domain&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;AccountRead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Testing via Dependency Overrides (DO)&lt;/strong&gt;&lt;br&gt;
Because our router now accepts the service from the outside, we can easily intercept it during testing. We use FastAPI's &lt;code&gt;dependency_overrides&lt;/code&gt; to swap the real database for a fake, test-scoped temporary file (&lt;code&gt;tmp_path&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Code Block 5: Safe Integration Testing]&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="nd"&gt;@pytest.fixture&lt;/span&gt; 
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;test_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_repo.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 

    &lt;span class="c1"&gt;# Create a fake service pointing to the temporary file
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;override_service&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccountRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_file&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;BankAccountService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Override the dependency globally for the test
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependency_overrides&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_bank_account_service&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;override_service&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

    &lt;span class="c1"&gt;# Clean up after the test
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependency_overrides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By isolating our infrastructure into a Repository layer, orchestrating it via a Service layer, and injecting it via DI, we can perform integration testing without ever touching our production data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: The "It Works on My Machine" Pain (Containerization)
&lt;/h2&gt;

&lt;p&gt;For early-stage projects, &lt;code&gt;git&lt;/code&gt; and a &lt;code&gt;virtualenv&lt;/code&gt; are usually enough for managing dependencies. However, the moment you want to prepare an app for the cloud, you must understand that modern cloud servers are stateless and rely entirely on containers.&lt;/p&gt;

&lt;p&gt;When traffic spikes, AWS won't pull your Git repo and run &lt;code&gt;pip install&lt;/code&gt;—that is far too slow and error-prone. Instead, it asks for a &lt;strong&gt;Docker Image&lt;/strong&gt; of your project so it can spin up identical containers in milliseconds.&lt;/p&gt;

&lt;p&gt;I wrote a &lt;code&gt;Dockerfile&lt;/code&gt; to package my OS, Python version, and dependencies. But when I ran the container, added a workout routine, and restarted the container, I realized something initially baffling: &lt;strong&gt;My data was gone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because I was using JSON-based persistence (local state), and because Docker containers are strictly &lt;em&gt;ephemeral&lt;/em&gt; (destroyed on restart), all my data vanished. &lt;/p&gt;

&lt;p&gt;That was the ultimate signal: to make an application truly stateless and scalable, local files must be migrated to a dedicated Database.&lt;/p&gt;

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

&lt;p&gt;By adopting a Layered Architecture, injecting dependencies, and containerizing the environment, your API can go from a fragile local script to a robust, nearly cloud-ready system with good test coverage. This is the way I've bridged the "CLI script" to a truly headless API.&lt;/p&gt;

&lt;p&gt;However, as Lesson 4 proved, relying on JSON for storage means a system is not truly stateless. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 of this project begins now:&lt;/strong&gt; For my own project, I am ripping out the JSON repository and swapping it for a standalone PostgreSQL database, entirely without touching my HTTP routers or core domain logic.&lt;/p&gt;

&lt;p&gt;Check out the source code for this architecture on my &lt;a href="https://github.com/santiravila" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or connect with me on &lt;a href="https://www.linkedin.com/in/santiago-rojas-avila/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to follow along as I build out Phase 2!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>fastapi</category>
      <category>architecture</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
