<?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: Digvijay Singh </title>
    <description>The latest articles on DEV Community by Digvijay Singh  (@digvijay_singhrajput).</description>
    <link>https://dev.to/digvijay_singhrajput</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%2F1734261%2Ff8cbd54d-40b7-4624-ad5c-14082ec26462.png</url>
      <title>DEV Community: Digvijay Singh </title>
      <link>https://dev.to/digvijay_singhrajput</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/digvijay_singhrajput"/>
    <language>en</language>
    <item>
      <title>.gitignore Done Right — What to Ignore, Why, and the Pattern Every Production Codebase Uses</title>
      <dc:creator>Digvijay Singh </dc:creator>
      <pubDate>Tue, 21 Apr 2026 23:30:54 +0000</pubDate>
      <link>https://dev.to/digvijay_singhrajput/gitignore-done-right-what-to-ignore-why-and-the-pattern-every-production-codebase-uses-23o</link>
      <guid>https://dev.to/digvijay_singhrajput/gitignore-done-right-what-to-ignore-why-and-the-pattern-every-production-codebase-uses-23o</guid>
      <description>&lt;h6&gt;
  
  
  A deep dive into .gitignore for Python projects — the secrets pattern, the template exception, what belongs in version control and what doesn't, and how one missing line can cost you real money.
&lt;/h6&gt;

&lt;h2&gt;
  
  
  .gitignore Done Right
&lt;/h2&gt;

&lt;p&gt;A developer at a startup pushed their &lt;code&gt;.env&lt;/code&gt; file to a public GitHub repository by mistake.&lt;/p&gt;

&lt;p&gt;Within 4 minutes — automated bots had scraped it.&lt;br&gt;
Within 6 minutes — they were making API calls on his account.&lt;br&gt;
His bill at the end of the month: $340.&lt;/p&gt;

&lt;p&gt;One missing line in &lt;code&gt;.gitignore&lt;/code&gt; caused this.&lt;/p&gt;

&lt;p&gt;This article covers everything about &lt;code&gt;.gitignore&lt;/code&gt; for Python projects — what to ignore, why each category exists, and the pattern every production codebase uses.&lt;/p&gt;


&lt;h2&gt;
  
  
  What &lt;code&gt;.gitignore&lt;/code&gt; Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; tells Git: "these files and folders exist on my machine — never track them, never commit them, never include them in diffs or pull requests."&lt;/p&gt;

&lt;p&gt;Two reasons you need it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 1 — Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; file contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database passwords&lt;/li&gt;
&lt;li&gt;API keys (OpenAI, Groq, Anthropic)&lt;/li&gt;
&lt;li&gt;JWT signing secrets&lt;/li&gt;
&lt;li&gt;AES encryption keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these reach GitHub — automated bots scan public repositories continuously. They find keys, abuse them, and you receive a bill. This is not theoretical. It happens every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 2 — Repository hygiene&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some files are auto-generated on your machine and serve no purpose in the repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;__pycache__/&lt;/code&gt; — Python bytecode, machine-specific, regenerates automatically&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.venv/&lt;/code&gt; — your virtual environment, thousands of files each developer creates themselves&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.DS_Store&lt;/code&gt; — macOS filesystem metadata, meaningless to other developers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.idea/&lt;/code&gt; — IDE configuration, personal and machine-specific&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files change constantly, cause merge conflicts, and bloat the repository for zero benefit.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Most Important Pattern — Secrets
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ============================================================
# SECRETS — NEVER commit these
# ============================================================
.env
.env.*
!.env.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's break down each line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt; — Your real secrets file. Contains actual API keys, passwords, database URLs. Lives only on your machine. Never committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env.*&lt;/code&gt;&lt;/strong&gt; — Covers all environment variants: &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.production&lt;/code&gt;, &lt;code&gt;.env.staging&lt;/code&gt;. One pattern ignores all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;!.env.example&lt;/code&gt;&lt;/strong&gt; — The &lt;code&gt;!&lt;/code&gt; prefix means &lt;strong&gt;exception&lt;/strong&gt;. Despite the previous rules, this file IS tracked by git.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why commit &lt;code&gt;.env.example&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.env.example&lt;/code&gt; is the template. It has the same variable names as &lt;code&gt;.env&lt;/code&gt; but with placeholder values — no real secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.example — committed to git&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql+asyncpg://docqa:docqa_password@localhost:5432/docqa
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-64-char-hex-generate-with-openssl-rand-hex-32
&lt;span class="nv"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-groq-api-key-here
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="c"&gt;# optional — leave empty if not using&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env — never committed — has real values&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql+asyncpg://docqa:myRe4lP@ss@prod.db.internal:5432/docqa
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a3f8c2e1d4b7f9a2c5e8d1b4f7a2c5e8d3f6b9a2c5e8d1b4f7a2c5e8d3f6b9a
&lt;span class="nv"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone clones the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# fill in real values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero guessing about what variables are needed. Zero Slack messages asking "what env vars do I need?" Zero setup friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A professional &lt;code&gt;.env.example&lt;/code&gt; includes comments:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# JWT signing key — if stolen, anyone can forge login tokens for any user&lt;/span&gt;
&lt;span class="c"&gt;# Generate with: openssl rand -hex 32&lt;/span&gt;
&lt;span class="c"&gt;# Must be at least 32 characters — app refuses to start otherwise&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-64-char-hex-string

&lt;span class="c"&gt;# AES-256-GCM encryption key for LLM API keys stored in DB&lt;/span&gt;
&lt;span class="c"&gt;# MUST be different from SECRET_KEY&lt;/span&gt;
&lt;span class="c"&gt;# Generate with: openssl rand -hex 32&lt;/span&gt;
&lt;span class="nv"&gt;ENCRYPTION_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-another-64-char-hex-string

&lt;span class="c"&gt;# Default LLM provider — completely free, no credit card needed&lt;/span&gt;
&lt;span class="c"&gt;# Get your key at: https://console.groq.com&lt;/span&gt;
&lt;span class="nv"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-groq-api-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each comment tells: what the variable does, why it matters, how to generate it, where to get it. Future developers (including future you) will thank present you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python-Specific Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Bytecode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;__pycache__/
*.py[cod]
*.pyo
.Python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Python runs your &lt;code&gt;.py&lt;/code&gt; file, it compiles it to bytecode and stores it in &lt;code&gt;__pycache__/&lt;/code&gt;. This happens automatically every time you run your code.&lt;/p&gt;

&lt;p&gt;Why ignore it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Machine-specific — your compiled bytecode won't work on someone else's machine&lt;/li&gt;
&lt;li&gt;Regenerates automatically — no value in tracking it&lt;/li&gt;
&lt;li&gt;Changes constantly — causes meaningless merge conflicts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;*.py[cod]&lt;/code&gt; pattern covers &lt;code&gt;.pyc&lt;/code&gt; (compiled), &lt;code&gt;.pyo&lt;/code&gt; (optimized), and &lt;code&gt;.pyd&lt;/code&gt; (Windows DLL) in one rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;venv/
.venv/
env/
ENV/
.env/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your virtual environment contains thousands of files — every package you've installed. Each developer creates their own virtual environment. The &lt;code&gt;requirements.txt&lt;/code&gt; file is what gets committed — that's the contract. The actual installed packages stay local.&lt;/p&gt;

&lt;p&gt;Multiple folder names covered because different tools create differently named environments (&lt;code&gt;python -m venv&lt;/code&gt; vs &lt;code&gt;virtualenv&lt;/code&gt; vs &lt;code&gt;poetry&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing and Coverage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.pytest_cache/
.coverage
coverage.xml
htmlcov/
.tox/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test runners generate cache and coverage files. &lt;code&gt;.pytest_cache/&lt;/code&gt; speeds up test runs locally but is meaningless in the repository. Coverage reports are generated artifacts — they should be generated fresh rather than committed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type Checking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.mypy_cache/
.ruff_cache/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type checkers cache their analysis for performance. Like bytecode, these are machine-specific and regenerate automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  IDE and Editor Files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IDE configuration is personal. Your VS Code settings for font size, color theme, and key bindings are not relevant to other developers. Your IntelliJ project structure is machine-specific.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exception&lt;/strong&gt;: Some teams commit a &lt;code&gt;.vscode/&lt;/code&gt; folder with shared extension recommendations. If you do this deliberately, use &lt;code&gt;!.vscode/extensions.json&lt;/code&gt; to re-include just that file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*.swp&lt;/code&gt; and &lt;code&gt;*.swo&lt;/code&gt; are temporary files created by Vim.&lt;br&gt;
&lt;code&gt;.DS_Store&lt;/code&gt; is macOS metadata about folder display preferences.&lt;br&gt;
&lt;code&gt;Thumbs.db&lt;/code&gt; is Windows thumbnail cache.&lt;/p&gt;

&lt;p&gt;None of these belong in a repository.&lt;/p&gt;


&lt;h2&gt;
  
  
  Uploads and User Data
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uploads/
*.pdf
*.docx
*.pptx
*.csv
*.xlsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;User-uploaded documents are not source code. In production, they go to cloud object storage (Cloudflare R2, AWS S3, Google Cloud Storage) — not the server disk and definitely not git.&lt;/p&gt;

&lt;p&gt;Including these patterns prevents accidentally committing a test PDF during development. It also protects against committing user data that might contain sensitive information.&lt;/p&gt;


&lt;h2&gt;
  
  
  Logs
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.log
logs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Logs are runtime output. They change constantly, are often large, and contain information (user IDs, IP addresses, request data) that shouldn't be in version control.&lt;/p&gt;


&lt;h2&gt;
  
  
  Build Artifacts
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/
build/
*.egg-info/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are generated when you package your Python application for distribution. Like bytecode, they're build outputs — not source code.&lt;/p&gt;


&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;


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

&lt;/div&gt;


&lt;p&gt;Some Docker tools create a &lt;code&gt;.docker/&lt;/code&gt; directory with local state. This stays local.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Complete &lt;code&gt;.gitignore&lt;/code&gt; for Python AI Projects
&lt;/h2&gt;

&lt;p&gt;Here's the full file with comments explaining each section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ============================================================
# SECRETS — NEVER commit these
# ============================================================
.env
.env.*
!.env.example

# ============================================================
# Python bytecode
# ============================================================
__pycache__/
*.py[cod]
*.pyo
.Python

# ============================================================
# Virtual environment
# ============================================================
venv/
.venv/
env/
ENV/
.env/

# ============================================================
# Testing and coverage
# ============================================================
.pytest_cache/
.coverage
coverage.xml
htmlcov/
.tox/

# ============================================================
# Type checking and linting cache
# ============================================================
.mypy_cache/
.ruff_cache/

# ============================================================
# IDE and editor files
# ============================================================
.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db

# ============================================================
# Docker
# ============================================================
.docker/

# ============================================================
# Uploads and user data
# Never commit user files — they go to cloud storage
# ============================================================
uploads/
*.pdf
*.docx
*.pptx
*.csv
*.xlsx

# ============================================================
# Logs — runtime output, not source code
# ============================================================
*.log
logs/

# ============================================================
# Build artifacts
# ============================================================
dist/
build/
*.egg-info/

# ============================================================
# Node (for future React frontend)
# ============================================================
node_modules/
.next/
frontend/dist/
frontend/build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Happens If You Commit a Secret Accidentally
&lt;/h2&gt;

&lt;p&gt;If you commit a &lt;code&gt;.env&lt;/code&gt; file or any file containing secrets to a public repository — even briefly — assume the secret is compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Remove from git tracking:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove the file from git tracking (keeps it on disk)&lt;/span&gt;
git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; .env

&lt;span class="c"&gt;# Add to .gitignore&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore

&lt;span class="c"&gt;# Commit the removal&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"remove .env from git tracking"&lt;/span&gt;

&lt;span class="c"&gt;# Push&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2 — Rotate every secret immediately.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do not wait. Do not hope "nobody saw it". Bots scan constantly.&lt;/p&gt;

&lt;p&gt;For API keys — go to the provider dashboard and regenerate.&lt;br&gt;
For database passwords — change the password.&lt;br&gt;
For JWT secrets — rotate the key (this invalidates all existing tokens — users must log in again).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Clean git history (if the repository is public):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The secret is still in git history even after removing the file. To fully remove it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use git-filter-repo (the modern tool — replaces BFG Repo Cleaner)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;git-filter-repo

git filter-repo &lt;span class="nt"&gt;--path&lt;/span&gt; .env &lt;span class="nt"&gt;--invert-paths&lt;/span&gt;
git push &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Force-pushing rewrites history. Coordinate with your team before doing this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checking Your Current Status
&lt;/h2&gt;

&lt;p&gt;To verify your &lt;code&gt;.gitignore&lt;/code&gt; is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See what git is currently tracking&lt;/span&gt;
git ls-files

&lt;span class="c"&gt;# Check if a specific file would be ignored&lt;/span&gt;
git check-ignore &lt;span class="nt"&gt;-v&lt;/span&gt; .env
&lt;span class="c"&gt;# output: .gitignore:2:.env    .env&lt;/span&gt;
&lt;span class="c"&gt;# means: rule on line 2 of .gitignore matches .env — it's ignored ✅&lt;/span&gt;

&lt;span class="c"&gt;# If the file is already tracked (check before adding to .gitignore)&lt;/span&gt;
git status
&lt;span class="c"&gt;# If .env shows up — remove it with: git rm --cached .env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Rule to Remember
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;env&lt;/span&gt;      → &lt;span class="n"&gt;real&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt; — &lt;span class="n"&gt;NEVER&lt;/span&gt; &lt;span class="n"&gt;committed&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;.*    → &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;variants&lt;/span&gt; — &lt;span class="n"&gt;NEVER&lt;/span&gt; &lt;span class="n"&gt;committed&lt;/span&gt;
!.&lt;span class="n"&gt;env&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt; → &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; — &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="n"&gt;committed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;!&lt;/code&gt; exception is the pattern every production team uses.&lt;br&gt;
The &lt;code&gt;.env.example&lt;/code&gt; template is what makes onboarding painless.&lt;br&gt;
The comments in &lt;code&gt;.env.example&lt;/code&gt; are what make maintenance painless.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Article
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Article 3: Managing Environment Variables in Python&lt;/strong&gt; — the full journey from &lt;code&gt;os.getenv()&lt;/code&gt; scattered across files, to Pydantic Settings v2 with typed validation, fail-fast startup, and &lt;code&gt;@lru_cache&lt;/code&gt; for immutable configuration.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of a series on Building a Production AI Platform From Scratch. Full code at: [Once repo is cleaned] Soon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>security</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built a Production-Grade AI Platform From Scratch (Here’s the Exact Folder Structure)</title>
      <dc:creator>Digvijay Singh </dc:creator>
      <pubDate>Sun, 19 Apr 2026 15:41:14 +0000</pubDate>
      <link>https://dev.to/digvijay_singhrajput/i-built-a-production-grade-ai-platform-from-scratch-heres-the-exact-folder-structure-2ij0</link>
      <guid>https://dev.to/digvijay_singhrajput/i-built-a-production-grade-ai-platform-from-scratch-heres-the-exact-folder-structure-2ij0</guid>
      <description>&lt;h2&gt;
  
  
  How I Structured a Production-Grade AI Platform From Scratch
&lt;/h2&gt;

&lt;p&gt;I stopped doing tutorials.&lt;/p&gt;

&lt;p&gt;Not because tutorials are bad. But because after finishing one, I could follow code. I couldn't explain &lt;strong&gt;why&lt;/strong&gt; the code was written that way.&lt;/p&gt;

&lt;p&gt;So I decided to build something real from scratch. No copy-paste. No shortcuts. Every decision justified. Every file explained.&lt;/p&gt;

&lt;p&gt;This is the first article in a series documenting how I build a &lt;strong&gt;production-grade Agentic RAG Document Intelligence System&lt;/strong&gt; — phase by phase, file by file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;GenAI DocQA Platform&lt;/strong&gt; is a system where users upload documents (PDF, DOCX, CSV, PPTX) and ask complex natural language questions. A 10-node LangGraph agent retrieves relevant chunks, reasons over them, self-corrects, and streams sourced answers back to the user.&lt;/p&gt;

&lt;p&gt;Think: mini Perplexity AI + Notion AI + an OpenAI API platform. Built entirely from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total cost to run: $0&lt;/strong&gt; — all free tiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API Framework&lt;/td&gt;
&lt;td&gt;FastAPI + async SQLAlchemy 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Agent&lt;/td&gt;
&lt;td&gt;LangGraph (10-node ReAct workflow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;pgvector + BM25 hybrid search + Cohere reranking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLMs&lt;/td&gt;
&lt;td&gt;Groq (free) → OpenAI → Anthropic (fallback chain)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings&lt;/td&gt;
&lt;td&gt;Sentence-Transformers (local, free, CPU)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Redis (rate limiting + query cache + embeddings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL 16 + pgvector extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;LangSmith + Prometheus + Grafana + RAGAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;JWT + bcrypt + AES-256-GCM + Presidio PII&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Docker Compose + GitHub Actions CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  13 Phases
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What Gets Built&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Project scaffold — this article&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;02&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JWT auth, bcrypt, AES encryption, rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;03&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Document parsers, chunking, WebSocket progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;04&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embeddings, pgvector, hybrid search, reranking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;05&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM router — 7 providers, fallback chain, cost tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;06&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RAG pipeline — prompt engineering, query rewriting, CRAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;07&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LangGraph 10-node agent, self-correction loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MCP integration — external tool calling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;09&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSE streaming, conversation memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Safety layer — PII masking, RAGAS evaluation, CI gates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;11-13&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React frontend, production deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This article covers &lt;strong&gt;Phase 1&lt;/strong&gt; — the complete project scaffold. No AI yet. Just the foundation that everything else sits on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Philosophy: Why This Matters
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, I made one decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every file gets a reason. Every decision gets a justification. Nothing exists "because the tutorial said so."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This forces architectural clarity. When you know WHY each piece exists, you can adapt it. When you only know WHAT it does, you're stuck.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1 File Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;genai-platform/
├── .gitignore                     # what git never tracks
├── .env.example                   # env var template — committed
├── README.md                      # project front door
├── .github/
│   └── workflows/
│       ├── ci.yml                 # runs on every push
│       └── deploy.yml             # runs on main merge only
├── infrastructure/
│   └── docker-compose.yml         # PostgreSQL + Redis + Backend
└── backend/
    ├── requirements.txt           # pinned Python dependencies
    ├── pyproject.toml             # ruff + mypy + pytest config
    ├── Dockerfile                 # multi-stage production build
    ├── alembic.ini                # migration configuration
    ├── alembic/
    │   ├── env.py                 # async migration bridge
    │   └── versions/              # migration files (Phase 2+)
    └── app/
        ├── __init__.py            # package marker + version
        ├── config.py              # Pydantic Settings — all env vars
        ├── main.py                # FastAPI app + lifespan + middleware
        ├── dependencies.py        # get_db, get_redis, get_current_user
        ├── db/
        │   ├── database.py        # async engine + session factory
        │   └── init_db.py         # pgvector extension + admin seed
        ├── monitoring/
        │   ├── logger.py          # structured JSON logging (structlog)
        │   └── metrics.py         # 12 Prometheus metrics defined
        └── api/v1/
            └── health.py          # /health (liveness) + /ready (readiness)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24 files. Let's go through the key decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 1: &lt;code&gt;.gitignore&lt;/code&gt; — What Never Gets Committed
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.gitignore&lt;/code&gt; has one critical pattern most developers miss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The pattern every production codebase uses
.env        # real secrets — never committed
.env.*      # covers .env.local, .env.production, etc.
!.env.example  # ← the ! means EXCEPTION — template IS committed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.env.example&lt;/code&gt; is committed. It has placeholder values. When someone clones the repo they do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# then fill in real values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero guessing about what variables are needed.&lt;/p&gt;

&lt;p&gt;We also ignore uploaded documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uploads/
*.pdf
*.docx
*.pptx
*.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, files go to Cloudflare R2 object storage — not git. Git is for code. Not user data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 2: Environment Variables Done Right
&lt;/h2&gt;

&lt;p&gt;The beginner approach:&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;# scattered across 15 files — dangerous
&lt;/span&gt;&lt;span class="n"&gt;db_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&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_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# returns None silently if missing
&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRIT_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# typo — also None, no warning
&lt;/span&gt;&lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TOKEN_EXPIRE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# crashes here if None
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three problems: typos return &lt;code&gt;None&lt;/code&gt; silently, no types, missing variables don't surface until runtime — deep inside a failing request.&lt;/p&gt;

&lt;p&gt;The production approach — Pydantic Settings:&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;# app/config.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SettingsConfigDict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_validator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lru_cache&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SettingsConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;env_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Required — app refuses to start without these
&lt;/span&gt;    &lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;

    &lt;span class="c1"&gt;# Optional — typed, with defaults
&lt;/span&gt;    &lt;span class="n"&gt;ACCESS_TOKEN_EXPIRE_MINUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;   &lt;span class="c1"&gt;# "15" → int automatically
&lt;/span&gt;    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;                      &lt;span class="c1"&gt;# "true" → bool automatically
&lt;/span&gt;
    &lt;span class="c1"&gt;# Custom validators — fail fast with clear messages
&lt;/span&gt;    &lt;span class="nd"&gt;@field_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_secret_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&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;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY must be at least 32 characters. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate with: openssl rand -hex 32&lt;/span&gt;&lt;span class="sh"&gt;"&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;v&lt;/span&gt;

    &lt;span class="nd"&gt;@field_validator&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_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_database_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+asyncpg://&lt;/span&gt;&lt;span class="sh"&gt;"&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;ValueError&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_URL must use asyncpg driver. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Change postgresql:// to postgresql+asyncpg://&lt;/span&gt;&lt;span class="sh"&gt;"&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;v&lt;/span&gt;

&lt;span class="nd"&gt;@lru_cache&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_settings&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;Settings&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;Settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# read once, cached forever
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;DATABASE_URL&lt;/code&gt; is missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValidationError: DATABASE_URL field required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;SECRET_KEY&lt;/code&gt; is too short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ValidationError: SECRET_KEY must be at least 32 characters.
Generate with: openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear. Specific. Actionable. At startup — not runtime.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@lru_cache()&lt;/code&gt; means the &lt;code&gt;.env&lt;/code&gt; file is read &lt;strong&gt;once&lt;/strong&gt; at startup. Not on every request. Not on every import. Once. Cached forever. This also ensures immutable configuration — the app has one consistent config for its entire lifetime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 3: Async SQLAlchemy 2.0 — Why It Changes Everything
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/db/database.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;async_sessionmaker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DeclarativeBase&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# postgresql+asyncpg:// — MUST be asyncpg
&lt;/span&gt;    &lt;span class="n"&gt;pool_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;# 20 connections in pool
&lt;/span&gt;    &lt;span class="n"&gt;max_overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# 10 extra when pool is full
&lt;/span&gt;    &lt;span class="n"&gt;pool_pre_ping&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="c1"&gt;# test before using (prevents stale connections)
&lt;/span&gt;    &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# log SQL in development
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;AsyncSessionLocal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async_sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expire_on_commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ← CRITICAL — more on this below
&lt;/span&gt;    &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;autoflush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeclarativeBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;expire_on_commit=False&lt;/code&gt; Trap
&lt;/h3&gt;

&lt;p&gt;This is the most common async SQLAlchemy mistake. By default, after &lt;code&gt;commit()&lt;/code&gt;, SQLAlchemy marks all objects as "expired". The next attribute access triggers a new DB query. In sync code — fine. In async code:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ← CRASH in async: MissingGreenlet error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;expire_on_commit=False&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="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ✅ works — values kept in memory
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you actually need fresh data, use &lt;code&gt;await db.refresh(user)&lt;/code&gt; explicitly. &lt;strong&gt;Explicit is better than implicit.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;postgresql+asyncpg://&lt;/code&gt; Not &lt;code&gt;postgresql://&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;One character difference. Completely different behaviour.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;postgresql://&lt;/code&gt; → sync driver → blocks the event loop → your server handles one request at a time during DB calls.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;postgresql+asyncpg://&lt;/code&gt; → async driver → non-blocking → server handles hundreds of concurrent requests during DB calls.&lt;/p&gt;

&lt;p&gt;Our validator in &lt;code&gt;config.py&lt;/code&gt; catches the wrong driver at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValidationError: DATABASE_URL must use asyncpg driver.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Decision 4: FastAPI Application Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynccontextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.middleware.cors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;

&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lifespan&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="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ── STARTUP ──────────────────────────────────────────────
&lt;/span&gt;    &lt;span class="nf"&gt;setup_logging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;# 1. logging first — everything else logs
&lt;/span&gt;    &lt;span class="nf"&gt;setup_prometheus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;     &lt;span class="c1"&gt;# 2. metrics
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;# 3. DB — needs logging ready
&lt;/span&gt;    &lt;span class="nf"&gt;connect_redis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;# 4. Redis
&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;  &lt;span class="c1"&gt;# ← app handles requests here
&lt;/span&gt;
    &lt;span class="c1"&gt;# ── SHUTDOWN ─────────────────────────────────────────────
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aclose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why &lt;code&gt;lifespan&lt;/code&gt; Instead of &lt;code&gt;@app.on_event&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@app.on_event("startup")&lt;/code&gt; is &lt;strong&gt;deprecated&lt;/strong&gt; since FastAPI 0.93. It's still in most tutorials. Don't use it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;lifespan&lt;/code&gt; pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Startup and shutdown in one function — paired naturally&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finally&lt;/code&gt; block ensures cleanup even on crashes&lt;/li&gt;
&lt;li&gt;Testable — can be mocked cleanly&lt;/li&gt;
&lt;li&gt;No deprecation warnings
### The Startup Order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Order matters. Logging must be first so everything after it can produce logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logging → Prometheus → Database → Redis → Ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If database fails at startup — we &lt;strong&gt;raise the exception&lt;/strong&gt;. An app that starts without a database looks healthy but serves broken responses. Fail fast. Fail loudly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware — Three Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Layer 1: CORS — browsers need this to call your API
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_origins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_methods&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;GET&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;POST&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;PUT&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;PATCH&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;DELETE&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;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_headers&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;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Layer 2: Request ID — every request gets a unique ID
&lt;/span&gt;&lt;span class="nd"&gt;@app.middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_request_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind_contextvars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_id&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;
    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_contextvars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="c1"&gt;# Layer 3: Request Logging — every request logged automatically
&lt;/span&gt;&lt;span class="nd"&gt;@app.middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;duration_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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="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="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CORS must be registered first&lt;/strong&gt; — it needs to be on every response including error responses. If registered after your error handler, CORS errors on errors produce confusing network failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 5: Dependency Injection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/dependencies.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_db&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;AsyncGenerator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncSessionLocal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&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;session&lt;/span&gt;        &lt;span class="c1"&gt;# route runs with this session
&lt;/span&gt;            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="c1"&gt;# session closes automatically — even on exception
&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&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;request&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="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In every route:&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.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&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_db&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_current_user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# db is ready, current_user is verified
&lt;/span&gt;    &lt;span class="c1"&gt;# no setup code needed here
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;yield&lt;/code&gt; pattern ensures the session &lt;strong&gt;always closes&lt;/strong&gt;, even if the route raises an exception. No leaked connections.&lt;/p&gt;

&lt;p&gt;For testing:&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;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_db&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_get_db&lt;/span&gt;  &lt;span class="c1"&gt;# swap real DB for test DB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Decision 6: Liveness vs Readiness Probes
&lt;/h2&gt;

&lt;p&gt;Two endpoints. Two completely different questions.&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;# GET /api/v1/health — liveness: is the process alive?
# NEVER checks external services
&lt;/span&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;ok&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;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;__version__&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# GET /api/v1/ready — readiness: can this instance handle traffic?
# Checks ALL dependencies
&lt;/span&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readiness_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;all_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;engine&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&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="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;checks&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="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;status&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;all_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;checks&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="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;status&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;error&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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;status&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;all_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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;status&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&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;JSONResponse&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;all_ready&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;content&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;status&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;ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;all_ready&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_ready&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;checks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;checks&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;&lt;strong&gt;Real scenario — PostgreSQL restarts for 30 seconds:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With one combined endpoint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/health&lt;/code&gt; returns 503&lt;/li&gt;
&lt;li&gt;Kubernetes thinks the process is dead&lt;/li&gt;
&lt;li&gt;Kubernetes kills and restarts the container&lt;/li&gt;
&lt;li&gt;Restart doesn't fix PostgreSQL&lt;/li&gt;
&lt;li&gt;Kubernetes keeps restarting&lt;/li&gt;
&lt;li&gt;This is a crash loop — users see errors for minutes
With two separate endpoints:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/health&lt;/code&gt; returns 200 (process is alive)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ready&lt;/code&gt; returns 503 (can't reach DB)&lt;/li&gt;
&lt;li&gt;Load balancer stops routing to this instance&lt;/li&gt;
&lt;li&gt;Traffic goes to other healthy instances&lt;/li&gt;
&lt;li&gt;Users see nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - PostgreSQL comes back → &lt;code&gt;/ready&lt;/code&gt; returns 200 → traffic resumes
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Decision 7: Structured Logging
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/monitoring/logger.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_logging&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_log_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_logger_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TimeStamper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge_contextvars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# includes request_id
&lt;/span&gt;            &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSONRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ConsoleRenderer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colors&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="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;wrapper_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BoundLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;logger_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LoggerFactory&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;&lt;strong&gt;Development output&lt;/strong&gt; (colored, human-readable):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2025-03-14 10:30:00 [info] document_uploaded  filename=report.pdf size_mb=2.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Production output&lt;/strong&gt; (JSON, machine-readable):&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="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"document_uploaded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"filename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size_mb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-03-14T10:30:00Z"&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;Same logging call. Different format. Zero code changes.&lt;/p&gt;

&lt;p&gt;Usage anywhere in the app:&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;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_logger&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;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunks_created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parent_child&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;234&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;groq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;exc_info&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;request_id&lt;/code&gt; bound in middleware flows through every log line automatically. When something breaks, search &lt;code&gt;request_id = "abc-123"&lt;/code&gt; and see the complete story of that request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 8: Multi-Stage Docker Build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1 — Builder (large, temporary)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; gcc python3-dev libpq-dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# requirements.txt BEFORE app code — enables layer caching&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/install &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Stage 2 — Production (small, deployed)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="c"&gt;# Runtime dependencies only — no build tools&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libpq5 tesseract-ocr curl &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy ONLY compiled packages — not gcc, not make, not compilers&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /install /usr/local&lt;/span&gt;

&lt;span class="c"&gt;# Non-root user — least privilege principle&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;-r&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; appgroup appuser
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /app/uploads &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup ./app /app/app&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 812MB → 298MB. Same application. Same functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer caching rule:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Things that change RARELY → top of Dockerfile
Things that change OFTEN  → bottom of Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;requirements.txt&lt;/code&gt; changes rarely. App code changes constantly. Copy requirements first → pip install is cached → code changes don't trigger pip install.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 9: Alembic Async Bridge
&lt;/h2&gt;

&lt;p&gt;Standard Alembic is synchronous. Our app uses async SQLAlchemy. The bridge:&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;# alembic/env.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_async_migrations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;connectable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connectable&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# run_sync() extracts a sync connection from async
&lt;/span&gt;        &lt;span class="c1"&gt;# Alembic runs inside that sync connection
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;do_run_migrations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connectable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&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;run_migrations_online&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_async_migrations&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;connection.run_sync(do_run_migrations)&lt;/code&gt; is the bridge.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;connection&lt;/code&gt; is async&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_sync()&lt;/code&gt; extracts a sync version&lt;/li&gt;
&lt;li&gt;Alembic runs normally inside &lt;code&gt;do_run_migrations()&lt;/code&gt;
This is the official Alembic async pattern.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Decision 10: GitHub Actions CI
&lt;/h2&gt;

&lt;p&gt;Every push triggers four jobs in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# ruff — style + security issues&lt;/span&gt;
  &lt;span class="na"&gt;typecheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# mypy — type errors&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# pytest with real postgres + redis&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgvector/pgvector:pg16&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;

  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# verify image builds&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;typecheck&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# only if all pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real PostgreSQL and Redis in test job — not mocks&lt;/li&gt;
&lt;li&gt;Docker build only runs after all three pass — fail fast on cheap checks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache: "pip"&lt;/code&gt; in setup-python — subsequent runs are 10× faster&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - &lt;code&gt;cache-from: type=gha&lt;/code&gt; for Docker — layer caching across CI runs
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Running Phase 1
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone and set up&lt;/span&gt;
git clone https://github.com/digvijaysingh21/genai-docqa.git
&lt;span class="nb"&gt;cd &lt;/span&gt;genai-docqa
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env

&lt;span class="c"&gt;# 2. Generate secrets&lt;/span&gt;
openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32  &lt;span class="c"&gt;# paste as SECRET_KEY&lt;/span&gt;
openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32  &lt;span class="c"&gt;# paste as ENCRYPTION_KEY&lt;/span&gt;
&lt;span class="c"&gt;# Get free Groq key at: console.groq.com → paste as GROQ_API_KEY&lt;/span&gt;

&lt;span class="c"&gt;# 3. Start everything&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;infrastructure
docker compose up

&lt;span class="c"&gt;# 4. Test&lt;/span&gt;
curl http://localhost:8000/api/v1/health
&lt;span class="c"&gt;# {"status":"ok","version":"1.0.0","environment":"development"}&lt;/span&gt;

curl http://localhost:8000/api/v1/ready
&lt;span class="c"&gt;# {"status":"ready","checks":{"database":{"status":"ok"},"redis":{"status":"ok"}}}&lt;/span&gt;

&lt;span class="c"&gt;# 5. Open Swagger UI&lt;/span&gt;
open http://localhost:8000/docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Phase 1 Establishes
&lt;/h2&gt;

&lt;p&gt;Before a single AI feature is built, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Deterministic builds (pinned versions)&lt;/li&gt;
&lt;li&gt;✅ Fail-fast configuration (Pydantic validators)&lt;/li&gt;
&lt;li&gt;✅ Async database with connection pooling&lt;/li&gt;
&lt;li&gt;✅ Structured JSON logging with request tracing&lt;/li&gt;
&lt;li&gt;✅ 12 Prometheus metrics defined&lt;/li&gt;
&lt;li&gt;✅ Liveness + readiness health probes&lt;/li&gt;
&lt;li&gt;✅ Multi-stage Docker build (300MB vs 800MB)&lt;/li&gt;
&lt;li&gt;✅ Non-root container user&lt;/li&gt;
&lt;li&gt;✅ Layer-optimised Dockerfile (10s rebuild vs 6min)&lt;/li&gt;
&lt;li&gt;✅ Async Alembic migration bridge&lt;/li&gt;
&lt;li&gt;✅ FastAPI DI system (get_db, get_redis)&lt;/li&gt;
&lt;li&gt;✅ CI pipeline (lint + typecheck + tests + docker build)
This is the foundation. Everything from Phase 2 through Phase 13 sits on top of this.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Next Article
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Auth and Security&lt;/strong&gt; — JWT tokens with 15-minute access + 7-day refresh, bcrypt password hashing, AES-256-GCM encryption for LLM API keys (BYOK pattern), and Redis sliding window rate limiting.&lt;/p&gt;

&lt;p&gt;If you're building along, the full code is at: &lt;a href="https://dev.toadd%20after%20cleanup"&gt;THE REPO will be added soon&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building this project phase by phase. Every decision explained. Follow along for Phase 2.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>fastapi</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to Reset the PostgreSQL `postgres` Password (Forgot Password Fix)</title>
      <dc:creator>Digvijay Singh </dc:creator>
      <pubDate>Fri, 13 Mar 2026 06:03:50 +0000</pubDate>
      <link>https://dev.to/digvijay_singhrajput/how-to-reset-the-postgresql-postgres-password-forgot-password-fix-3hd</link>
      <guid>https://dev.to/digvijay_singhrajput/how-to-reset-the-postgresql-postgres-password-forgot-password-fix-3hd</guid>
      <description>&lt;p&gt;For many developers working with PostgreSQL locally, forgetting the postgres user password is very common.&lt;/p&gt;

&lt;p&gt;When trying to connect using pgAdmin or psql, you might see an error like:&lt;/p&gt;

&lt;p&gt;connection failed: FATAL: password authentication failed for user "postgres"&lt;/p&gt;

&lt;p&gt;This guide shows a simple method to reset the password on Windows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Locate the PostgreSQL Configuration File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the PostgreSQL data folder:&lt;/p&gt;

&lt;p&gt;C:\Program Files\PostgreSQL\16\data&lt;/p&gt;

&lt;p&gt;Find the file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This file controls how PostgreSQL authenticates users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Open pg_hba.conf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the file using Notepad as Administrator.&lt;/p&gt;

&lt;p&gt;Find these lines:&lt;/p&gt;

&lt;p&gt;local   all             all                                  scram-sha-256&lt;br&gt;
host    all             all             127.0.0.1/32         scram-sha-256&lt;br&gt;
host    all             all             ::1/128              scram-sha-256&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Temporarily Disable Password Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Change scram-sha-256 to trust.&lt;/p&gt;

&lt;p&gt;local   all             all                                     trust&lt;br&gt;
host    all             all             127.0.0.1/32            trust&lt;br&gt;
host    all             all             ::1/128                 trust&lt;/p&gt;

&lt;p&gt;Save the file.&lt;/p&gt;

&lt;p&gt;This temporarily allows login without a password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) Restart PostgreSQL Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Press Windows + R&lt;/p&gt;

&lt;p&gt;Type:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.msc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Find the service:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; postgresql-x64-16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Restart the service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5) Open SQL Shell (psql)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Search for:&lt;/p&gt;

&lt;p&gt;SQL Shell (psql)&lt;/p&gt;

&lt;p&gt;Press Enter for all default values:&lt;/p&gt;

&lt;p&gt;Server [localhost]:&lt;br&gt;
Database [postgres]:&lt;br&gt;
Port [5432]:&lt;br&gt;
Username [postgres]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6) Reset the Password&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run the command:&lt;/p&gt;

&lt;p&gt;ALTER USER postgres PASSWORD 'newpassword123';&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;ALTER USER postgres PASSWORD 'MySecurePassword';&lt;/p&gt;

&lt;p&gt;If successful, you will see:&lt;/p&gt;

&lt;p&gt;ALTER ROLE&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7) Restore Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go back to pg_hba.conf and change:&lt;/p&gt;

&lt;p&gt;trust&lt;/p&gt;

&lt;p&gt;back to:&lt;/p&gt;

&lt;p&gt;scram-sha-256&lt;/p&gt;

&lt;p&gt;Restart PostgreSQL again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8) Connect Again&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now connect using pgAdmin:&lt;/p&gt;

&lt;p&gt;Username: postgres&lt;br&gt;
Password: newpassword123&lt;br&gt;
Port: 5432&lt;/p&gt;

&lt;p&gt;The connection should work successfully.&lt;/p&gt;

&lt;p&gt;✅ Final Notes&lt;/p&gt;

&lt;p&gt;Use trust only temporarily&lt;/p&gt;

&lt;p&gt;Always restore scram-sha-256&lt;/p&gt;

&lt;p&gt;This method works for most local PostgreSQL installations on Windows&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>Customizing the Django Panel: A Step-By-Step Guide</title>
      <dc:creator>Digvijay Singh </dc:creator>
      <pubDate>Wed, 18 Sep 2024 17:17:12 +0000</pubDate>
      <link>https://dev.to/digvijay_singhrajput/customizing-the-django-panel-a-step-by-step-guide-c17</link>
      <guid>https://dev.to/digvijay_singhrajput/customizing-the-django-panel-a-step-by-step-guide-c17</guid>
      <description>&lt;p&gt;In this guide I'll walk you through how to modify and extend Django default admin panel/interface, making it more user-friendly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Set up the Project:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start by creating a brand new project and app in Django&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;django-admin startproject myprojectname
cd myprojectname
python manage.py startapp developerscommunity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;** Note**&lt;br&gt;
Do not forgot to add your app ti the INSTALLED_APPS in settings.py&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Run migrations:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py makemigrations
python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Resgister Models in Admin Panel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Register of models is compulsory to see it in django admin 
 interface

  from django.contrib import admin
  from .models import DevCommunity

 admin.site.register(DevCommunity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Above Steps will lead you to Django Admin Panel Now comes the customization part&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Customize the Admin Panel:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;class CustomAdminSite(admin.AdminSite):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;will appear at the top-left corner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;site_header = "Dev  Admin"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;will show in the browser tab&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;site_title = Developer Admin Portal &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;will be displayed on the admin home page.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;index_title = "Welcome to Developer Community" &lt;/p&gt;

&lt;p&gt;custom_admin_site = CustomAdminSite(name="dev_admin")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #All code at one place
  class CustomAdminSite(admin.AdminSite):
     site_header = "Dev  Admin"
     site_title = Developer Admin Portal
     index_title = "Welcome to Developer Community"

  custom_admin_site = CustomAdminSite(name="dev_admin")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. To register:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #Finally register
  custom_admin_site.register(DevCommunity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

</description>
      <category>backend</category>
      <category>webdev</category>
      <category>django</category>
      <category>python</category>
    </item>
    <item>
      <title>Django File Structure for Developers</title>
      <dc:creator>Digvijay Singh </dc:creator>
      <pubDate>Mon, 16 Sep 2024 18:19:14 +0000</pubDate>
      <link>https://dev.to/digvijay_singhrajput/django-file-structure-for-developers-4i68</link>
      <guid>https://dev.to/digvijay_singhrajput/django-file-structure-for-developers-4i68</guid>
      <description>&lt;p&gt;This django file structure guide will walk you through the essential elements of a django project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Project Root Directory&lt;/li&gt;
&lt;li&gt;Project Directory (e.g., you_project_name)&lt;/li&gt;
&lt;li&gt;Applications (Apps)&lt;/li&gt;
&lt;li&gt;Templates Directory&lt;/li&gt;
&lt;li&gt;Static Directory&lt;/li&gt;
&lt;li&gt;Media Directory&lt;/li&gt;
&lt;li&gt;Virtual Environment (venv/)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. Project Root Directory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This directory contains the entire Django project. It contains&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- manage.py&lt;/strong&gt;: It is a command line utility that allows us to interact with project. Mainly use to start development server, create apps, run migrations etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Project Folder&lt;/strong&gt; (Your Project name folder): It contains setting and configurations of our project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Project Directory (e.g., you_project_name)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a folder that has configurations for our Django projects. It include files like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- init.py&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- settings.py&lt;/strong&gt;: Contains setting for our projects such as configurations, database settings, installed apps, allowed hosts, middleware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- urls.py&lt;/strong&gt;: It contains URL for our projects (Routing requests for our views).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- asgi.py&lt;/strong&gt;: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- wsgi.py&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Applications (Apps)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- models.py&lt;/strong&gt;: It contains Data Structure for you project or we can say app's data/ structure of the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- views.py&lt;/strong&gt;: Business logic (handling requests and responses)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- urls.py&lt;/strong&gt;: your app specific url&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- forms.py&lt;/strong&gt;: structure and validation logic for the forms&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- admin.py&lt;/strong&gt;: Django admin panel(Dashboard) by registering the models (by creating a superuser and login to the Django's admin) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- apps.py&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- migrations/&lt;/strong&gt;: Contains database migrations files.Each time you make any changes to your database you will see a new file with some random naes in this folder (e.g. 0001_initial, 0002_model_you_made_or_changes, ...)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Templates Directory&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- base.html&lt;/strong&gt;:This contains shared code which is common in many files for example headers, footers which you want in your multiple pages.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;- other files that extend from base.html for specific views *&lt;/em&gt;: Lets say login.html, home.html etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Static Directory&lt;/strong&gt;:It contains static files such as CSS, JavaScript, images. App specific directories or global one (as per your requirements).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Media Directory&lt;/strong&gt;: User uploaded files for example documents, any other files may be a profile picture of a user etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Virtual Environment (venv/)&lt;/strong&gt;: Make a habit of creating a virtual environment for each of the django project to isolate project dependencies. It is important to note that it is essential for project specific packages without disturbing any global environment.&lt;/p&gt;

&lt;p&gt;your_project_name/&lt;br&gt;
│&lt;br&gt;
├── manage.py&lt;br&gt;
├── your_project_name/&lt;br&gt;
│   ├── &lt;strong&gt;init&lt;/strong&gt;.py&lt;br&gt;
│   ├── settings.py&lt;br&gt;
│   ├── urls.py&lt;br&gt;
│   ├── wsgi.py&lt;br&gt;
│   └── asgi.py&lt;br&gt;
│&lt;br&gt;
├── your_app_one/&lt;br&gt;
│   ├── &lt;strong&gt;init&lt;/strong&gt;.py&lt;br&gt;
│   ├── admin.py&lt;br&gt;
│   ├── apps.py&lt;br&gt;
│   ├── models.py&lt;br&gt;
│   ├── views.py&lt;br&gt;
│   ├── urls.py&lt;br&gt;
│   └── migrations/&lt;br&gt;
│&lt;br&gt;
├── your_app_two/&lt;br&gt;
│   ├── &lt;strong&gt;init&lt;/strong&gt;.py&lt;br&gt;
│   ├── admin.py&lt;br&gt;
│   ├── apps.py&lt;br&gt;
│   ├── models.py&lt;br&gt;
│   ├── views.py&lt;br&gt;
│   └── migrations/&lt;br&gt;
│&lt;br&gt;
├── templates/&lt;br&gt;
│   ├── base.html&lt;br&gt;
│   └── home.html&lt;br&gt;
│&lt;br&gt;
└── static/&lt;br&gt;
    ├── css/&lt;br&gt;
    └── js/&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Understanding file structure before starting any projects in any language is very crucial and essential for efficient project development. I hope now it becomes easier for you all to navigate and manage your code bases.&lt;/p&gt;

&lt;p&gt;Please feel free to comment your thoughts or any tips.&lt;br&gt;
&lt;strong&gt;If you want all essential django commands at one place please comment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BONUS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commands that you should know for manage.py&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    **1. python manage.py runserver ** : To start the server

    **2. python manage.py makemigrations** : Creating new 
         migrations on the changes made in your models.

    **3. python manage.py migrate ** : Applying or unapplying 
         migrations
    **4. python manage.py createsuperuser**: Access to django 
         admin panel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

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