<?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: Fiyinfoluwa Ojo</title>
    <description>The latest articles on DEV Community by Fiyinfoluwa Ojo (@ghost_script).</description>
    <link>https://dev.to/ghost_script</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%2F3779752%2F21b10242-d8ec-444b-9869-9d4c55151b4d.jpg</url>
      <title>DEV Community: Fiyinfoluwa Ojo</title>
      <link>https://dev.to/ghost_script</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ghost_script"/>
    <language>en</language>
    <item>
      <title>Day 30: 30 Days of Backend Development - What I Built, What I Learned</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Mon, 23 Mar 2026 21:27:38 +0000</pubDate>
      <link>https://dev.to/ghost_script/day-30-30-days-of-backend-development-what-i-built-what-i-learned-4026</link>
      <guid>https://dev.to/ghost_script/day-30-30-days-of-backend-development-what-i-built-what-i-learned-4026</guid>
      <description>&lt;h2&gt;
  
  
  30 Days. 30 Challenges. 1 Developer.
&lt;/h2&gt;

&lt;p&gt;Today marks the end of the GDGoC Bowen 30-Day &lt;br&gt;
Backend Challenge. Here's everything I built.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built — Day by Day
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Foundation (Days 1-5)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 1 → HTTP &amp;amp; Hello World with FastAPI&lt;/li&gt;
&lt;li&gt;Day 2 → RESTful routes&lt;/li&gt;
&lt;li&gt;Day 3 → Git workflow and branching&lt;/li&gt;
&lt;li&gt;Day 4 → Dynamic path parameters&lt;/li&gt;
&lt;li&gt;Day 5 → Query params &amp;amp; Pydantic validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Data &amp;amp; APIs (Days 6-14)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 6 → SQLAlchemy data models&lt;/li&gt;
&lt;li&gt;Day 7 → GET all items endpoint&lt;/li&gt;
&lt;li&gt;Day 8 → Serializers &amp;amp; DTOs&lt;/li&gt;
&lt;li&gt;Day 9 → POST create endpoint&lt;/li&gt;
&lt;li&gt;Day 10 → Production-ready routes&lt;/li&gt;
&lt;li&gt;Day 11 → DELETE endpoint&lt;/li&gt;
&lt;li&gt;Day 12 → Global error handling&lt;/li&gt;
&lt;li&gt;Day 13 → Pagination &amp;amp; filtering&lt;/li&gt;
&lt;li&gt;Day 14 → Swagger API documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Auth &amp;amp; Security (Days 15-18)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 15 → Bcrypt password hashing&lt;/li&gt;
&lt;li&gt;Day 16 → JWT authentication&lt;/li&gt;
&lt;li&gt;Day 17 → Permissions &amp;amp; ownership&lt;/li&gt;
&lt;li&gt;Day 18 → Environment variables &amp;amp; secrets&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  DevOps &amp;amp; Deployment (Days 19-20)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 19 → Docker containerization&lt;/li&gt;
&lt;li&gt;Day 20 → Deployed to Render &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Advanced (Days 21-24)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 21 → Pytest unit &amp;amp; integration tests&lt;/li&gt;
&lt;li&gt;Day 22 → Database relationships&lt;/li&gt;
&lt;li&gt;Day 23 → CORS &amp;amp; API contracts&lt;/li&gt;
&lt;li&gt;Day 24 → In-memory caching&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Capstone — To-Do App (Days 25-30)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Day 25 → PRD &amp;amp; planning&lt;/li&gt;
&lt;li&gt;Day 26 → Full CRUD API&lt;/li&gt;
&lt;li&gt;Day 27 → 10 automated tests&lt;/li&gt;
&lt;li&gt;Day 28 → Deployed to Render&lt;/li&gt;
&lt;li&gt;Day 29 → Filtering, pagination, docs&lt;/li&gt;
&lt;li&gt;Day 30 → Final polish &amp;amp; README&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Capstone Project
&lt;/h2&gt;

&lt;p&gt;Live URL: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://gdgoc-bowen-todo.onrender.com" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;gdgoc-bowen-todo.onrender.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Docs: &lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://gdgoc-bowen-todo.onrender.com/docs" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;gdgoc-bowen-todo.onrender.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A fully production-ready To-Do App API with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT authentication&lt;/li&gt;
&lt;li&gt;Full CRUD with ownership&lt;/li&gt;
&lt;li&gt;Priority levels and filtering&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;li&gt;10 automated tests&lt;/li&gt;
&lt;li&gt;Full Swagger documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;30 days of consistent building taught me more &lt;br&gt;
than months of passive learning.&lt;/p&gt;

&lt;p&gt;The most important lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consistency beats intensity&lt;/li&gt;
&lt;li&gt;Build in public — it keeps you accountable&lt;/li&gt;
&lt;li&gt;Every concept builds on the previous one&lt;/li&gt;
&lt;li&gt;Documentation is part of the product&lt;/li&gt;
&lt;li&gt;Tests give you confidence to keep building&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;This is not the end — it's the foundation.&lt;br&gt;
The next step is building more complex systems,&lt;br&gt;
contributing to open source and preparing for&lt;br&gt;
the Elevate career development meetup.&lt;/p&gt;

&lt;p&gt;Thank you GDGoC Bowen for this challenge. 🙏&lt;/p&gt;

&lt;p&gt;Day 30 done. The journey continues. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Capstone Day 4: Polishing the To-Do App : Filtering, Pagination &amp; Full Documentation</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Mon, 23 Mar 2026 17:53:09 +0000</pubDate>
      <link>https://dev.to/ghost_script/capstone-day-4-polishing-the-to-do-app-filtering-pagination-full-documentation-f06</link>
      <guid>https://dev.to/ghost_script/capstone-day-4-polishing-the-to-do-app-filtering-pagination-full-documentation-f06</guid>
      <description>&lt;h2&gt;
  
  
  Day 4 — Making It Production Ready
&lt;/h2&gt;

&lt;p&gt;The API works. Now let's make it excellent.&lt;br&gt;
Today I added priority levels, filtering, &lt;br&gt;
pagination and full Swagger documentation.&lt;/p&gt;
&lt;h2&gt;
  
  
  New Features
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Priority Levels
&lt;/h3&gt;

&lt;p&gt;Tasks now have 3 priority levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low&lt;/li&gt;
&lt;li&gt;medium
&lt;/li&gt;
&lt;li&gt;high&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Filtering
&lt;/h3&gt;

&lt;p&gt;GET &lt;code&gt;/tasks?priority=high&lt;/code&gt; -&amp;gt; only high priority tasks&lt;br&gt;
GET &lt;code&gt;/tasks?completed=false&lt;/code&gt; -&amp;gt; only incomplete tasks&lt;br&gt;
GET &lt;code&gt;/tasks?priority=high&amp;amp;completed=false&lt;/code&gt; -&amp;gt; combined!&lt;/p&gt;
&lt;h3&gt;
  
  
  Pagination
&lt;/h3&gt;

&lt;p&gt;GET &lt;code&gt;/tasks?page=1&amp;amp;limit=5&lt;/code&gt; -&amp;gt; first 5 tasks&lt;/p&gt;

&lt;p&gt;Response includes metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"totalPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full Swagger Documentation
&lt;/h3&gt;

&lt;p&gt;Every endpoint tagged, summarized and described.&lt;br&gt;
Available at /docs — zero extra work needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Filter by priority
&lt;/h3&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%2Fhdxf00zz3nkgzxv4ks2f.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%2Fhdxf00zz3nkgzxv4ks2f.png" alt="high priority"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter incomplete tasks
&lt;/h3&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%2F1w6lrjgq7wktnqw49c6c.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%2F1w6lrjgq7wktnqw49c6c.png" alt="Incomplete tasks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Swagger docs
&lt;/h3&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%2Fbdexbkaw1ssb6fhmtlqh.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%2Fbdexbkaw1ssb6fhmtlqh.png" alt="swagger docs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Final API Looks Like
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;API info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/status&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Health check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/auth/signup&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/auth/login&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/tasks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Get tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/tasks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Create task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/tasks/:id&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Update task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/tasks/:id&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Delete task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;The difference between a working API and a &lt;br&gt;
great API is the details : filtering, pagination, &lt;br&gt;
documentation and consistent error handling.&lt;br&gt;
These are what separate junior from senior work.&lt;/p&gt;

&lt;p&gt;Day 29 done. 1 more to go. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Capstone Day 3: Deploying the To-Do App API to Render</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Sat, 21 Mar 2026 02:50:32 +0000</pubDate>
      <link>https://dev.to/ghost_script/capstone-day-3-deploying-the-to-do-app-api-to-render-5gc6</link>
      <guid>https://dev.to/ghost_script/capstone-day-3-deploying-the-to-do-app-api-to-render-5gc6</guid>
      <description>&lt;h2&gt;
  
  
  The To-Do App is Live!
&lt;/h2&gt;

&lt;p&gt;After 2 days of building and testing, &lt;br&gt;
the To-Do App API is now deployed and &lt;br&gt;
accessible to the entire world.&lt;/p&gt;

&lt;p&gt;Live URL: &lt;a href="https://gdgoc-bowen-todo.onrender.com" rel="noopener noreferrer"&gt;https://gdgoc-bowen-todo.onrender.com&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;A fully functional Task Management API with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT Authentication (signup &amp;amp; login)&lt;/li&gt;
&lt;li&gt;Full CRUD for tasks&lt;/li&gt;
&lt;li&gt;Task ownership — users only see their own tasks&lt;/li&gt;
&lt;li&gt;Input validation with Pydantic&lt;/li&gt;
&lt;li&gt;10 automated tests all passing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/signup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tasks/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tasks/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Deployment Setup
&lt;/h2&gt;

&lt;p&gt;3 things needed for Render:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requirements.txt -&amp;gt; all dependencies&lt;/li&gt;
&lt;li&gt;.python-version -&amp;gt; pin to 3.11.0&lt;/li&gt;
&lt;li&gt;Start command -&amp;gt; &lt;code&gt;uvicorn main:app --host 0.0.0.0 --port $PORT&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Live Proof
&lt;/h2&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%2Fcvabrcgwu6bmvp5nk20n.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%2Fcvabrcgwu6bmvp5nk20n.png" alt="live img" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgasc7n6sljokjormn19.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%2Flgasc7n6sljokjormn19.png" alt="post with success" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Deployment is the moment your code stops &lt;br&gt;
being yours and starts being useful to others.&lt;br&gt;
Every step from Day 1 to Day 28 led here.&lt;/p&gt;

&lt;p&gt;Day 28 done. 2 more to go. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>backenddevelopment</category>
      <category>devops</category>
    </item>
    <item>
      <title>Capstone Day 2: Testing the To-Do App API with Pytest</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Fri, 20 Mar 2026 00:33:38 +0000</pubDate>
      <link>https://dev.to/ghost_script/capstone-day-2-testing-the-to-do-app-api-with-pytest-1jp6</link>
      <guid>https://dev.to/ghost_script/capstone-day-2-testing-the-to-do-app-api-with-pytest-1jp6</guid>
      <description>&lt;h2&gt;
  
  
  Why Test the Capstone?
&lt;/h2&gt;

&lt;p&gt;Every feature I add could break an existing one.&lt;br&gt;
Automated tests catch that instantly.&lt;br&gt;
no manual Postman testing needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  10 Tests Covering Everything
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auth Tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Signup success&lt;/li&gt;
&lt;li&gt;Duplicate email returns 400&lt;/li&gt;
&lt;li&gt;Login success returns accessToken&lt;/li&gt;
&lt;li&gt;Wrong password returns 401&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Task Tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create task returns 201&lt;/li&gt;
&lt;li&gt;Get tasks returns data array&lt;/li&gt;
&lt;li&gt;Update task marks as completed&lt;/li&gt;
&lt;li&gt;Delete task returns 204&lt;/li&gt;
&lt;li&gt;Get tasks without token returns 401&lt;/li&gt;
&lt;li&gt;Create task without title returns 422&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Test Pattern
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_token&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskuser&lt;/span&gt;&lt;span class="si"&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/signup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&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;password123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&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;password123&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessToken&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every task test creates its own user and token,&lt;br&gt;
tests are completely isolated from each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&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%2Fjvbmvjp14bcp0m3ln44d.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%2Fjvbmvjp14bcp0m3ln44d.png" alt="Results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;10 passed, 0 failed. ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Testing a capstone project is different from &lt;br&gt;
testing isolated endpoints : you need to test &lt;br&gt;
the full flow: signup -&amp;gt; login -&amp;gt; create -&amp;gt; update -&amp;gt; delete.&lt;br&gt;
That's what real integration testing looks like.&lt;/p&gt;

&lt;p&gt;Day 27 done. 3 more to go. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>backenddevelopment</category>
      <category>testing</category>
      <category>python</category>
    </item>
    <item>
      <title>PRD Day: Planning the To-Do App Backend Before Writing a Single Line of Code</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 16:33:12 +0000</pubDate>
      <link>https://dev.to/ghost_script/prd-day-planning-the-to-do-app-backend-before-writing-a-single-line-of-code-68f</link>
      <guid>https://dev.to/ghost_script/prd-day-planning-the-to-do-app-backend-before-writing-a-single-line-of-code-68f</guid>
      <description>&lt;h2&gt;
  
  
  What is a PRD?
&lt;/h2&gt;

&lt;p&gt;A Product Requirements Document defines what &lt;br&gt;
you're building before you build it.&lt;br&gt;
Today was about reading, understanding and &lt;br&gt;
planning not coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project - To-Do App
&lt;/h2&gt;

&lt;p&gt;A simple task management app with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Full CRUD for tasks&lt;/li&gt;
&lt;li&gt;Data persistence&lt;/li&gt;
&lt;li&gt;Clean REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Selected Feature - Task Management API
&lt;/h2&gt;

&lt;p&gt;I'm building the complete backend API covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/tasks&lt;/code&gt; -&amp;gt; create task&lt;/li&gt;
&lt;li&gt;GET &lt;code&gt;/tasks&lt;/code&gt; -&amp;gt; retrieve all tasks&lt;/li&gt;
&lt;li&gt;PUT &lt;code&gt;/tasks/:id&lt;/code&gt; -&amp;gt; update task&lt;/li&gt;
&lt;li&gt;DELETE &lt;code&gt;/tasks/:id&lt;/code&gt; -&amp;gt; delete task&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My 5-Day Plan
&lt;/h2&gt;

&lt;p&gt;Day 26 -&amp;gt; Models, database setup &amp;amp; full CRUD&lt;br&gt;
Day 27 -&amp;gt; Authentication &amp;amp; ownership&lt;br&gt;
Day 28 -&amp;gt; Testing &amp;amp; validation&lt;br&gt;
Day 29 -&amp;gt; Deployment&lt;br&gt;
Day 30 -&amp;gt; Final polish &amp;amp; documentation&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;The best code starts with a solid plan.&lt;br&gt;
Understanding requirements before writing &lt;br&gt;
code saves hours of refactoring later.&lt;/p&gt;

&lt;p&gt;Day 25 done. The build starts tomorrow. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Capstone Day 1: Building the To-Do App Task Management API</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 16:29:23 +0000</pubDate>
      <link>https://dev.to/ghost_script/capstone-day-1-building-the-to-do-app-task-management-api-5cf9</link>
      <guid>https://dev.to/ghost_script/capstone-day-1-building-the-to-do-app-task-management-api-5cf9</guid>
      <description>&lt;h2&gt;
  
  
  The Capstone Project Begins
&lt;/h2&gt;

&lt;p&gt;Days 26-30 are all about the capstone project :&lt;br&gt;
a full To-Do App API built as a team.&lt;/p&gt;

&lt;p&gt;Today I built the complete Task Management API&lt;br&gt;
with authentication and full CRUD operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI - web framework&lt;/li&gt;
&lt;li&gt;SQLAlchemy - ORM&lt;/li&gt;
&lt;li&gt;SQLite - database&lt;/li&gt;
&lt;li&gt;JWT - authentication&lt;/li&gt;
&lt;li&gt;Pydantic - validation&lt;/li&gt;
&lt;li&gt;CORS - frontend ready&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Data Models
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class User(Base):
    id, email, password, created_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Task(Base):
    id, title, description, completed, 
    user_id, created_at, updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tasks are linked to users : every user &lt;br&gt;
only sees their own tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API Endpoints
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;POST&lt;/em&gt; &lt;code&gt;/auth/signup&lt;/code&gt; -&amp;gt; Register a new user&lt;br&gt;
&lt;em&gt;POST&lt;/em&gt; &lt;code&gt;/auth/login&lt;/code&gt; -&amp;gt; Login and get JWT token&lt;br&gt;
&lt;em&gt;GET&lt;/em&gt; &lt;code&gt;/tasks&lt;/code&gt; -&amp;gt; Get all tasks for logged-in user&lt;br&gt;
&lt;em&gt;POST&lt;/em&gt; &lt;code&gt;/tasks&lt;/code&gt; -&amp;gt; Create a new task&lt;br&gt;
&lt;em&gt;PUT&lt;/em&gt; &lt;code&gt;/tasks/:id&lt;/code&gt; -&amp;gt; Update or complete a task&lt;br&gt;
&lt;em&gt;DELETE&lt;/em&gt; &lt;code&gt;/tasks/:id&lt;/code&gt; -&amp;gt; Delete a task permanently&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Signup &amp;amp; Login
&lt;/h3&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%2Fi4p29mgmtv67vpk7ks3o.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%2Fi4p29mgmtv67vpk7ks3o.png" alt="signup" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7reyluwfeym6iuw7jwil.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%2F7reyluwfeym6iuw7jwil.png" alt="login" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Task
&lt;/h3&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%2Fhsovtjvoaou0au881a2t.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%2Fhsovtjvoaou0au881a2t.png" alt="create task" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get All Tasks
&lt;/h3&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%2Fw3qulotssqh8cnlkexk6.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%2Fw3qulotssqh8cnlkexk6.png" alt="Get tasks" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mark Task as Completed
&lt;/h3&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%2Fjuh2x9jzq61aaqugiw4g.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%2Fjuh2x9jzq61aaqugiw4g.png" alt="Mark Complete" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete Task
&lt;/h3&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%2Fbo60eu8khztivdtewzu5.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%2Fbo60eu8khztivdtewzu5.png" alt="Delete task" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building a real product feels different from &lt;br&gt;
daily exercises. Every decision matters :&lt;br&gt;
the field names, the response shapes, the &lt;br&gt;
status codes because a frontend team is &lt;br&gt;
depending on this API to be consistent and reliable.&lt;/p&gt;

&lt;p&gt;Day 26 done. 4 more to go. 🔥&lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>webdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Caching Intro: Making Your API Lightning Fast with In-Memory Cache</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 14:22:11 +0000</pubDate>
      <link>https://dev.to/ghost_script/caching-intro-making-your-api-lightning-fast-with-in-memory-cache-197f</link>
      <guid>https://dev.to/ghost_script/caching-intro-making-your-api-lightning-fast-with-in-memory-cache-197f</guid>
      <description>&lt;h2&gt;
  
  
  Why Caching?
&lt;/h2&gt;

&lt;p&gt;Every database query takes time. If 1000 users &lt;br&gt;
request the same categories list every minute,&lt;br&gt;
that's 1000 database queries for identical data.&lt;/p&gt;

&lt;p&gt;Caching stores the result in memory after the &lt;br&gt;
first query. Everyone else gets it instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cache Logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;CACHE_TTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_from_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&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;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;expires_at&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; CACHE HIT&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;data&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; CACHE EXPIRED&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; CACHE MISS&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="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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="n"&gt;data&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;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CACHE_TTL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cache Miss vs Cache Hit
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.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;/categories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_categories&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_from_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_categories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&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;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Hit the database only on cache miss
&lt;/span&gt;    &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;set_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_categories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&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;source&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;database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First request -&amp;gt; source: "database" (Cache Miss)&lt;br&gt;
Second request -&amp;gt; source: "cache" (Cache Hit)&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman Proof
&lt;/h2&gt;

&lt;h3&gt;
  
  
  First request - Cache Miss
&lt;/h3&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%2F6omuk8vaoiq7wghgsy05.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%2F6omuk8vaoiq7wghgsy05.png" alt="cache miss"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Second request - Cache Hit
&lt;/h3&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%2F9qj2mj5y8aflbks1fia3.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%2F9qj2mj5y8aflbks1fia3.png" alt="Cache hit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache TTL
&lt;/h2&gt;

&lt;p&gt;After 60 seconds the cache expires automatically.&lt;br&gt;
The next request hits the database again and &lt;br&gt;
refreshes the cache. This ensures data stays &lt;br&gt;
reasonably fresh without hammering the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Caching is one of the highest ROI optimizations &lt;br&gt;
in backend development. Identify routes that &lt;br&gt;
don't change often and cache them.&lt;br&gt;
For production use Redis instead of in-memory &lt;br&gt;
cache  it persists across server restarts &lt;br&gt;
and works across multiple instances.&lt;/p&gt;

&lt;p&gt;Day 24 done. 6 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>CORS &amp; API Contracts: Preparing Your Backend for Frontend Collaboration</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:22:15 +0000</pubDate>
      <link>https://dev.to/ghost_script/cors-api-contracts-preparing-your-backend-for-frontend-collaboration-4old</link>
      <guid>https://dev.to/ghost_script/cors-api-contracts-preparing-your-backend-for-frontend-collaboration-4old</guid>
      <description>&lt;h2&gt;
  
  
  What is CORS?
&lt;/h2&gt;

&lt;p&gt;When a React frontend on localhost:3000 tries to &lt;br&gt;
call an API on localhost:8000, the browser blocks it.&lt;br&gt;
That's CORS protection and it's a good thing.&lt;/p&gt;

&lt;p&gt;But your own frontend should be allowed through.&lt;br&gt;
That's what CORS configuration does.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enabling CORS in FastAPI
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.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="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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000&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;http://localhost:5173&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_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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One middleware call, every response now includes &lt;br&gt;
the correct CORS headers automatically.&lt;/p&gt;
&lt;h2&gt;
  
  
  API Contract : camelCase for Frontend
&lt;/h2&gt;

&lt;p&gt;Backend Python uses &lt;code&gt;snake_case&lt;/code&gt; by default.&lt;br&gt;
But JavaScript frontends expect camelCase.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;created_at -&amp;gt; use createdAt&lt;/li&gt;
&lt;li&gt;category_id -&amp;gt; use categoryId&lt;/li&gt;
&lt;li&gt;access_token -&amp;gt; use accessToken&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small change, big impact on frontend collaboration.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Consistent Response Shape
&lt;/h2&gt;

&lt;p&gt;Every endpoint follows the same contract:&lt;/p&gt;

&lt;p&gt;GET &lt;code&gt;/items&lt;/code&gt; returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;POST &lt;code&gt;/auth/login&lt;/code&gt; returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accessToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokenType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontend developers can rely on these shapes &lt;br&gt;
without reading your source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OpenAPI Contract
&lt;/h2&gt;

&lt;p&gt;FastAPI auto generates an OpenAPI spec at &lt;code&gt;/openapi.json&lt;/code&gt;&lt;br&gt;
This is your official API contract &lt;br&gt;
frontend teams can import it into Postman &lt;br&gt;
and know exactly what every endpoint expects and returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Home endpoint showing CORS enabled
&lt;/h3&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%2Fi6kv9qh4bf7x66zjxtfi.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%2Fi6kv9qh4bf7x66zjxtfi.png" alt="home endpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Items with camelCase fields
&lt;/h3&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%2Fl2y895uns4dkj1y6la8u.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%2Fl2y895uns4dkj1y6la8u.png" alt="camelCase fields"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;CORS and consistent field naming aren't &lt;br&gt;
afterthoughts, they're part of being a &lt;br&gt;
good backend developer who works well with &lt;br&gt;
frontend teams. Your API is only as good &lt;br&gt;
as how easy it is to integrate with.&lt;/p&gt;

&lt;p&gt;Day 23 done. 7 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Relationships &amp; Nested Resources: One-to-Many with SQLAlchemy</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Tue, 17 Mar 2026 01:52:13 +0000</pubDate>
      <link>https://dev.to/ghost_script/relationships-nested-resources-one-to-many-with-sqlalchemy-35n1</link>
      <guid>https://dev.to/ghost_script/relationships-nested-resources-one-to-many-with-sqlalchemy-35n1</guid>
      <description>&lt;h2&gt;
  
  
  Modeling Real-World Complexity
&lt;/h2&gt;

&lt;p&gt;Real data has relationships. A phone belongs to &lt;br&gt;
Electronics. A keyboard belongs to Accessories.&lt;br&gt;
Flat data doesn't capture this relationships do.&lt;/p&gt;
&lt;h2&gt;
  
  
  The One-to-Many Relationship
&lt;/h2&gt;

&lt;p&gt;One Category has many Items.&lt;br&gt;
One Item belongs to one Category.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Category(Base):
    __tablename__ = "categories"
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)
    items = relationship("Item", back_populates="category")

class Item(Base):
    __tablename__ = "items"
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    price = Column(Numeric(10, 2), nullable=False)
    category_id = Column(Integer, ForeignKey("categories.id"))
    category = relationship("Category", back_populates="items")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ForeignKey links items to categories.&lt;br&gt;
The relationship() tells SQLAlchemy how to &lt;br&gt;
navigate between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested JSON Response
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /items/1 returns:
{
  "id": 1,
  "name": "Laptop",
  "price": 999.99,
  "category": {
    "id": 1,
    "name": "Electronics",
    "description": "Electronic devices and gadgets"
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The category is nested inside the item response —&lt;br&gt;
no extra API call needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category with All Items
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /categories/1 returns:
{
  "id": 1,
  "name": "Electronics",
  "items": [
    {"id": 1, "name": "Laptop", "price": 999.99},
    {"id": 2, "name": "Phone", "price": 699.99},
    {"id": 3, "name": "Headphones", "price": 199.99}
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Postman Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Item with nested category
&lt;/h3&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%2Flcd18bay3fhietz9ok84.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%2Flcd18bay3fhietz9ok84.png" alt="postman item" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Category with all its items
&lt;/h3&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%2Fi7fflbjryyygut10i47b.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%2Fi7fflbjryyygut10i47b.png" alt="category items" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Relationships are what make a database relational.&lt;br&gt;
Model your data the way the real world works —&lt;br&gt;
things belong to other things, and your API &lt;br&gt;
should reflect that naturally.&lt;/p&gt;

&lt;p&gt;Day 22 done. 8 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>database</category>
    </item>
    <item>
      <title>Unit &amp; Integration Testing: Proving Your Code Works with Pytest</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Sat, 14 Mar 2026 11:38:25 +0000</pubDate>
      <link>https://dev.to/ghost_script/unit-integration-testing-proving-your-code-works-with-pytest-4ok</link>
      <guid>https://dev.to/ghost_script/unit-integration-testing-proving-your-code-works-with-pytest-4ok</guid>
      <description>&lt;h2&gt;
  
  
  Why Write Tests?
&lt;/h2&gt;

&lt;p&gt;Manual testing means opening Postman every time &lt;br&gt;
you make a change. Automated tests run in seconds &lt;br&gt;
and catch bugs before they reach production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests vs Integration Tests
&lt;/h2&gt;

&lt;p&gt;Unit tests -&amp;gt; test individual functions/logic in isolation&lt;br&gt;
Integration tests -&amp;gt; test full endpoints with real HTTP requests&lt;/p&gt;

&lt;h2&gt;
  
  
  My 3 Unit Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reject negative price
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_price_validation_rejects_negative&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nc"&gt;ItemDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Reject zero price
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_price_validation_rejects_zero&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nc"&gt;ItemDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Accept positive price
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_price_validation_accepts_positive&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ItemDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Integration Tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Signup success
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_signup_success&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testuser&lt;/span&gt;&lt;span class="si"&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/signup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&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;testpassword123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&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;email&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Duplicate email returns 400
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_signup_duplicate_email&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# signup twice with same email
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Login returns token
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_login_success&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Protected route without token returns 401
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_protected_without_token&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;/protected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Results
&lt;/h2&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%2Fiulk0ho3vja4kz8b3kak.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%2Fiulk0ho3vja4kz8b3kak.png" alt="passed test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;8 passed, 0 failed. ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Tests are not extra work, they're insurance.&lt;br&gt;
Every test you write is a bug you'll never &lt;br&gt;
have to debug manually in production.&lt;/p&gt;

&lt;p&gt;Day 21 done. 9 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>webdev</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploying a FastAPI App to Render: Taking Your API Live</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Fri, 13 Mar 2026 21:01:05 +0000</pubDate>
      <link>https://dev.to/ghost_script/deploying-a-fastapi-app-to-render-taking-your-api-live-59bp</link>
      <guid>https://dev.to/ghost_script/deploying-a-fastapi-app-to-render-taking-your-api-live-59bp</guid>
      <description>&lt;h2&gt;
  
  
  From Local to Live
&lt;/h2&gt;

&lt;p&gt;Running an API on localhost is great for development.&lt;br&gt;
But a real backend needs to be accessible to the world.&lt;br&gt;
Today I deployed my FastAPI app to Render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Render?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Free tier available&lt;/li&gt;
&lt;li&gt;Connects directly to GitHub&lt;/li&gt;
&lt;li&gt;Auto-deploys on every push to main&lt;/li&gt;
&lt;li&gt;Environment variables built in&lt;/li&gt;
&lt;li&gt;Zero server configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;3 files needed for deployment:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;fastapi&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=0.129.0&lt;/span&gt;
&lt;span class="py"&gt;uvicorn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=0.41.0&lt;/span&gt;
&lt;span class="py"&gt;sqlalchemy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=2.0.41&lt;/span&gt;
&lt;span class="py"&gt;pydantic&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=2.11.5&lt;/span&gt;
&lt;span class="py"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=4.3.0&lt;/span&gt;
&lt;span class="py"&gt;pyjwt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=2.10.1&lt;/span&gt;
&lt;span class="py"&gt;python-dotenv&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=1.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;.python-version&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;3.11.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This pins the Python version- critical because &lt;br&gt;
newer versions can break dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Start Command
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;uvicorn main:app --host 0.0.0.0 --port $PORT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$PORT&lt;/code&gt; is automatically provided by Render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables on Render
&lt;/h2&gt;

&lt;p&gt;Never hardcode secrets in your code.&lt;br&gt;
Set them in Render's dashboard under Environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SECRET_KEY&lt;/li&gt;
&lt;li&gt;ALGORITHM&lt;/li&gt;
&lt;li&gt;DATABASE_URL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Live API
&lt;/h2&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%2Ffoxz82c8xeoubq5al6fm.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%2Ffoxz82c8xeoubq5al6fm.png" alt="Live api browser" width="772" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F122rxws2wtx94l5582eg.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%2F122rxws2wtx94l5582eg.png" alt="signup postman" width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live URL: &lt;a href="https://gdgoc-bowen-api.onrender.com" rel="noopener noreferrer"&gt;https://gdgoc-bowen-api.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Deployment is not the scary part, it's just &lt;br&gt;
connecting your GitHub repo to a platform and &lt;br&gt;
setting your environment variables correctly.&lt;br&gt;
The hard work is writing production-ready code.&lt;/p&gt;

&lt;p&gt;Day 20 done. 10 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dockerizing a FastAPI App: Eliminating "It Works on My Machine"</title>
      <dc:creator>Fiyinfoluwa Ojo</dc:creator>
      <pubDate>Mon, 09 Mar 2026 06:36:53 +0000</pubDate>
      <link>https://dev.to/ghost_script/dockerizing-a-fastapi-app-eliminating-it-works-on-my-machine-2ifm</link>
      <guid>https://dev.to/ghost_script/dockerizing-a-fastapi-app-eliminating-it-works-on-my-machine-2ifm</guid>
      <description>&lt;h2&gt;
  
  
  What is Docker?
&lt;/h2&gt;

&lt;p&gt;Docker packages your app and all its dependencies &lt;br&gt;
into a container a lightweight, portable unit that &lt;br&gt;
runs exactly the same on any machine.&lt;/p&gt;

&lt;p&gt;No more "it works on my machine" problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dockerfile
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FROM python:3.11-slim&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WORKDIR /app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;COPY requirements.txt .&lt;br&gt;
&lt;code&gt;RUN pip install --no-cache-dir -r requirements.txt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY . .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPOSE 8000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Line by line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FROM → base image (Python 3.11)&lt;/li&gt;
&lt;li&gt;WORKDIR → working directory inside container&lt;/li&gt;
&lt;li&gt;COPY requirements.txt → copy dependencies first&lt;/li&gt;
&lt;li&gt;RUN pip install → install dependencies&lt;/li&gt;
&lt;li&gt;COPY . . → copy all app code&lt;/li&gt;
&lt;li&gt;EXPOSE 8000 → open port 8000&lt;/li&gt;
&lt;li&gt;CMD → command to start the app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  docker-compose.yml
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  api:
    build: .
    ports:
      - "8000:8000"
    command: python -m uvicorn main:app --host 0.0.0.0 --port 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Compose links services together &lt;br&gt;
useful when you add a database container later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build &amp;amp; Run
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker build -t gdgoc-bowen-api.&lt;/code&gt;&lt;br&gt;
&lt;code&gt;docker-compose up --build&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof it Builds
&lt;/h2&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%2Fv0nodzrfcdpxybp8izj9.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%2Fv0nodzrfcdpxybp8izj9.png" alt="docker build" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Docker is not just a DevOps tool, it's a &lt;br&gt;
backend developer's best friend. Every production &lt;br&gt;
app runs in containers today. Learning Docker &lt;br&gt;
early puts you ahead of most developers.&lt;/p&gt;

&lt;p&gt;Day 19 done. 11 more to go. &lt;/p&gt;

&lt;h1&gt;
  
  
  GDGoCBowen30dayChallenge
&lt;/h1&gt;

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