<?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: John Afariogun</title>
    <description>The latest articles on DEV Community by John Afariogun (@john_afariogun_e2351c78af).</description>
    <link>https://dev.to/john_afariogun_e2351c78af</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%2F3568632%2F860220c4-cc82-4e44-acd9-63181e677f66.png</url>
      <title>DEV Community: John Afariogun</title>
      <link>https://dev.to/john_afariogun_e2351c78af</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/john_afariogun_e2351c78af"/>
    <language>en</language>
    <item>
      <title>The Middle Child Syndrome: Why Your Frontend might not be able to Talk to it's Docker Siblings</title>
      <dc:creator>John Afariogun</dc:creator>
      <pubDate>Fri, 20 Feb 2026 15:58:08 +0000</pubDate>
      <link>https://dev.to/john_afariogun_e2351c78af/the-middle-child-syndrome-why-your-frontend-might-not-be-able-to-talk-to-its-docker-siblings-4f99</link>
      <guid>https://dev.to/john_afariogun_e2351c78af/the-middle-child-syndrome-why-your-frontend-might-not-be-able-to-talk-to-its-docker-siblings-4f99</guid>
      <description>&lt;p&gt;&lt;em&gt;Containerizing full-stack apps reveals a harsh reality—your React frontend is the awkward middle child that can't speak to its Docker siblings.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Containerizing a full-stack application is a rite of passage for every DevOps-leaning engineer. You successfully get your Node.js backend talking to PostgreSQL, your Python ML service crunching data, and Redis caching everything in between.&lt;/p&gt;

&lt;p&gt;But then, the "Middle Child" enters the room: &lt;strong&gt;The Frontend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Despite being part of the &lt;code&gt;docker-compose.yml&lt;/code&gt; family, the frontend often feels isolated, unable to speak the same internal language as its Docker siblings. Here's why it happens and how to solve it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Family Reunion That Excluded Frontend
&lt;/h2&gt;

&lt;p&gt;I recently orchestrated a multi-service e-commerce app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Backend (Node.js/Express)    ✅ Connected
ML Service (Python/Flask)    ✅ Connected  
PostgreSQL Database          ✅ Connected
Redis Cache                  ✅ Connected
React Frontend               ❌ Left outside in the cold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inside the Docker bridge network, life was beautiful.&lt;/strong&gt; My backend could reach the ML service simply by using the service name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside the backend container&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://ml-service:5000/recommendations/42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ✅ Works perfectly!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Docker DNS handles the heavy lifting. Services on the same bridge network are family.&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="c1"&gt;# docker-compose.yml - Happy family networking&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;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app-network&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;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;]&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;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend Got Kicked Out (The Harsh Reality)
&lt;/h2&gt;

&lt;p&gt;Then I tried the same thing from my React frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React frontend trying to join the family...&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://backend:8080/api/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sibling rivalry:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;Browser Console:&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;net::ERR_NAME_NOT_RESOLVED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Did This Fail?
&lt;/h3&gt;

&lt;p&gt;The "Middle Child Syndrome" stems from a fundamental misunderstanding of &lt;strong&gt;where the code actually runs&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend code&lt;/strong&gt; runs &lt;em&gt;inside&lt;/em&gt; the Docker container → uses Docker's internal DNS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend code&lt;/strong&gt; is &lt;em&gt;delivered&lt;/em&gt; by Docker, but &lt;strong&gt;executes in the user's browser&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The browser lives on your host machine&lt;/strong&gt; (your device), not inside the Docker bridge network. Your device's DNS has no idea what &lt;code&gt;http://backend&lt;/code&gt; is. The Docker network is a private club your browser doesn't have membership for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser ←─── HTTP ───► ??? ──── Docker Network ───► Services
  │                              (backend, ml, db, redis)
  │
  └── Can't reach Docker DNS directly ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Solutions: Pick Your Poison
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Expose Everything&lt;/strong&gt; (Security Nightmare ⚠️)
&lt;/h3&gt;

&lt;p&gt;The quickest fix is to expose every service to your host machine:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;  &lt;span class="c1"&gt;# Now public on localhost&lt;/span&gt;
  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;  &lt;span class="c1"&gt;# Exposed to internet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React calls &lt;code&gt;http://localhost:8080&lt;/code&gt;, also calls &lt;code&gt;http://localhost:5000&lt;/code&gt; ✅ Works, but you've just exposed all your internal services to the entire internet. In production, this is a massive security "no-go."&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;Backend Proxy&lt;/strong&gt; (Secure &amp;amp; Simple)
&lt;/h3&gt;

&lt;p&gt;Route frontend requests through your backend, which &lt;em&gt;can&lt;/em&gt; access Docker DNS:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;  &lt;span class="c1"&gt;# Single exposed port&lt;/span&gt;
  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports exposed - internal only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backend proxies ML requests:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backend/server.js&lt;/span&gt;
&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/ml/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mlUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://ml-service:5000/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mlUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React calls &lt;code&gt;/api/ml/recommendations&lt;/code&gt; → Backend proxies to &lt;code&gt;ml-service:5000&lt;/code&gt; ✅ Secure + elegant.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;strong&gt;Nginx Reverse Proxy&lt;/strong&gt; (Production Ready)
&lt;/h3&gt;

&lt;p&gt;This is the most architecturally sound approach. Put a "Gatekeeper" in front of your family:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx&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;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;

  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports exposed&lt;/span&gt;

  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports exposed&lt;/span&gt;

  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports exposed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;nginx.conf:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend:8080/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/ml/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://ml-service:5000/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://frontend:3000/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your frontend only talks to &lt;strong&gt;one&lt;/strong&gt; place: the Nginx port. Nginx lives &lt;em&gt;inside&lt;/em&gt; the Docker network and handles routing to all siblings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser ←─── HTTP ───► Nginx (port 80) ──── Docker Network ───► Services ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Root Cause: Network Architecture Mismatch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docker bridge networks solve:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Service-to-service communication&lt;/li&gt;
&lt;li&gt;✅ Container DNS resolution
&lt;/li&gt;
&lt;li&gt;❌ Browser-to-container (without port mapping or proxy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The fix requires understanding execution context:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code Location&lt;/th&gt;
&lt;th&gt;Runs Where?&lt;/th&gt;
&lt;th&gt;Can Use Docker DNS?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backend/server.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inside container&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ml-service/app.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inside container&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frontend/src/App.jsx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;In browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Lessons Learned (The Hard Way)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execution Context is King&lt;/strong&gt; - Always ask: "Where is this code &lt;em&gt;actually&lt;/em&gt; running?" If it's a &lt;code&gt;.jsx&lt;/code&gt; file, it runs in the browser, not Docker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One Ingress Point&lt;/strong&gt; - Avoid "Swiss Cheese" security. Expose one port (80/443) and route everything internally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables Save Lives:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CORS is Your Friend&lt;/strong&gt; - Configure properly on all services that Nginx proxies:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Complete Working Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&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;nginx&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;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports - accessed via nginx&lt;/span&gt;

  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports - accessed via nginx&lt;/span&gt;

  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ml-service&lt;/span&gt;
    &lt;span class="c1"&gt;# Internal only&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;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shopmicro&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:alpine&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The frontend middle child finally found its place&lt;/strong&gt; - behind a proxy, secure, and talking to all its Docker siblings through proper networking architecture.&lt;/p&gt;




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

&lt;p&gt;In my next post, I'll share how I took this &lt;strong&gt;ShopMicro&lt;/strong&gt; architecture and scaled it into a &lt;strong&gt;Kubernetes cluster&lt;/strong&gt; with Ingress controllers—where the networking gets even more "fun."&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>fullstack</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>🔔 Notifycli: A Lightweight Go CLI for Firebase Push Notifications</title>
      <dc:creator>John Afariogun</dc:creator>
      <pubDate>Fri, 05 Dec 2025 08:28:00 +0000</pubDate>
      <link>https://dev.to/john_afariogun_e2351c78af/notifycli-a-lightweight-go-cli-for-firebase-push-notifications-74b</link>
      <guid>https://dev.to/john_afariogun_e2351c78af/notifycli-a-lightweight-go-cli-for-firebase-push-notifications-74b</guid>
      <description>&lt;p&gt;Need to send push notifications to your mobile or web clients — from the command line, a backend script, or a CI/CD pipeline — without building a full backend server?&lt;br&gt;
&lt;strong&gt;Notifycli&lt;/strong&gt; is a simple, Go-based CLI that does exactly that: use Firebase Cloud Messaging (FCM) credentials + a device token to send push notifications.&lt;/p&gt;

&lt;p&gt;In this post, I unpack the code, show installation and usage, and highlight how it's built — so you can use or extend it for your own projects.&lt;/p&gt;


&lt;h3&gt;
  
  
  What is Notifycli?
&lt;/h3&gt;

&lt;p&gt;According to the project README:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notifycli is a “Go CLI for sending push notifications to mobile and web devices using Firebase Cloud Messaging.” (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;It supports sending to a single device (via FCM device token), and allows custom data payloads — e.g. a title, body, optional URL — which your app can use for deep linking or redirection. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;It uses a clean project structure based on the popular CLI framework Cobra for subcommands and flag parsing. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because it's a standalone binary (no heavy deps, no web server), it works well for automation: cron jobs, server scripts, deployment hooks, or any backend that just needs to “fire-and-forget” an FCM push.&lt;/p&gt;


&lt;h3&gt;
  
  
  Project Structure &amp;amp; Architecture
&lt;/h3&gt;

&lt;p&gt;Here’s how the repo is structured: (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Notifycli/
 ├── cmd/
 │    ├── root.go         # CLI root command definitions
 │    └── notify.go       # “notify” subcommand: sends a notification
 ├── internal/
 │    └── firebase/
 │         └── client.go  # Firebase FCM client wrapper
 ├── main.go              # Entry point
 ├── go.mod / go.sum      # Module definitions
 ├── test/                # (optional) tests
 └── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cmd/&lt;/code&gt;: Uses Cobra to define commands and flags.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal/firebase/client.go&lt;/code&gt;: Encapsulates FCM initialization, authentication using a Firebase service-account JSON key, and the logic to send notifications via FCM REST API. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Minimal external dependencies — this keeps the binary lightweight and portable.&lt;/li&gt;
&lt;li&gt;Easy to integrate with scripts, cron jobs, CI/CD pipelines — you just call &lt;code&gt;notifcli notify ...&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  How to Build &amp;amp; Use
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Clone &amp;amp; Build
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/johnafariogun/Notifycli.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Notifycli
go build &lt;span class="nt"&gt;-o&lt;/span&gt; notifcli
&lt;span class="c"&gt;# optionally:&lt;/span&gt;
go &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Provide Firebase Credentials
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go to your Firebase project → &lt;strong&gt;Project Settings → Service Accounts → Generate new private key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Download the &lt;code&gt;serviceAccount.json&lt;/code&gt; and place it in your project root (or anywhere you like, but pass its path with &lt;code&gt;--creds&lt;/code&gt;). (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Send a Notification via CLI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;notifcli notify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;DEVICE_FCM_TOKEN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Deployment Successful"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Your service is live."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--creds&lt;/span&gt; /path/to/serviceAccount.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional flag: &lt;code&gt;--url&lt;/code&gt;, to include a link in the data payload (useful for app deep links or redirect URLs). (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That’s all — a simple, one-liner to trigger a push notification from anywhere.&lt;/p&gt;




&lt;h3&gt;
  
  
  Under the Hood: What’s Going On
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The CLI parses flags (token, title, body, url, creds) via Cobra in &lt;code&gt;cmd/notify.go&lt;/code&gt;. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;internal/firebase/client.go&lt;/code&gt; module: initializes a Firebase app using the provided service account, constructs the FCM message (with title/body, and optional data payload containing &lt;code&gt;link&lt;/code&gt;), and sends the request to FCM via HTTPS. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Error handling covers cases like missing credentials, invalid FCM token, or network failures. The CLI prints descriptive error messages. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Because it's a native Go binary, it’s cross-platform (Linux, macOS, etc.) and doesn’t require runtime dependencies beyond Go’s standard library + net/http.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why This Tool Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity &amp;amp; Speed&lt;/strong&gt;: No need to build a full backend just to send notifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation-friendly&lt;/strong&gt;: Great for deployment scripts, CI/CD, cron jobs, or server-side alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable&lt;/strong&gt;: Build once, deploy anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensible&lt;/strong&gt;: You can extend &lt;code&gt;internal/firebase/client.go&lt;/code&gt; to support more advanced FCM features — e.g. topic messages, batch sends, custom data payloads, image notifications. The architecture is clean and modular. (&lt;a href="https://github.com/johnafariogun/Notifycli.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Wrap-up &amp;amp; Final Thoughts
&lt;/h3&gt;

&lt;p&gt;Notifycli is a great example of how a small, focused tool can do one thing well — in this case: send FCM push notifications — and integrate easily into scripts, automation, or backend workflows.&lt;br&gt;
Its clean Go + Cobra structure and minimal dependencies make it perfect for developers or DevOps workflows that don’t need a full backend server just for notifications.&lt;/p&gt;

&lt;p&gt;If you need a lightweight, scriptable, cross-platform tool for mobile/web push notifications — especially useful for deployment alerts, status updates, or automation — give Notifycli a try.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>go</category>
      <category>fcm</category>
      <category>cobra</category>
    </item>
    <item>
      <title>A2A adventures</title>
      <dc:creator>John Afariogun</dc:creator>
      <pubDate>Mon, 03 Nov 2025 17:43:20 +0000</pubDate>
      <link>https://dev.to/john_afariogun_e2351c78af/a2a-adventures-iil</link>
      <guid>https://dev.to/john_afariogun_e2351c78af/a2a-adventures-iil</guid>
      <description>&lt;h2&gt;
  
  
  Building a GitHub Issues Agent with FastAPI and A2A
&lt;/h2&gt;

&lt;p&gt;This project demonstrates a small agent that receives A2A JSON-RPC messages and returns GitHub issues for a repository. It is intentionally minimal — focusing on clear message schemas, simple HTTP fetch logic, and deterministic behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivation
&lt;/h3&gt;

&lt;p&gt;I wanted an agent that can be called by other systems via an A2A JSON-RPC contract and that returns a small, well-formed payload containing issue metadata and a short textual summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI for the HTTP server and lifecycle management&lt;/li&gt;
&lt;li&gt;Pydantic models (in &lt;code&gt;models/a2a.py&lt;/code&gt;) for the message contract&lt;/li&gt;
&lt;li&gt;A single agent implementation (&lt;code&gt;agents/github_issues_agent.py&lt;/code&gt;) that extracts the repository and calls a tool function &lt;code&gt;utils.fetch_issues&lt;/code&gt; to get the data from GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agents/github_issues_agent.py&lt;/code&gt; extracts &lt;code&gt;owner/repo&lt;/code&gt; strings from the message parts. It builds a &lt;code&gt;TaskResult&lt;/code&gt; that includes a textual summary and an artifact containing the full issues payload.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils/fetch_issues&lt;/code&gt; uses &lt;code&gt;httpx&lt;/code&gt; and handles rate-limit behavior by allowing a &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; in the environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check it out at github.com/johnafariogun/github_app&lt;br&gt;
— End&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>python</category>
    </item>
    <item>
      <title>🐱 My HNG 13 Stage 0 Task — Building a Simple Cat Facts API with FastAPI</title>
      <dc:creator>John Afariogun</dc:creator>
      <pubDate>Thu, 16 Oct 2025 10:45:06 +0000</pubDate>
      <link>https://dev.to/john_afariogun_e2351c78af/my-hng-13-stage-0-task-building-a-simple-cat-facts-api-with-fastapi-hj7</link>
      <guid>https://dev.to/john_afariogun_e2351c78af/my-hng-13-stage-0-task-building-a-simple-cat-facts-api-with-fastapi-hj7</guid>
      <description>&lt;p&gt;After getting to the penultimate stage in HNG12 internship earlier this year, my perfectionist tendencies cropped up again and this time I want to get to the ultimate stage. First though I have to start from the scratch so stage 0 it is.&lt;/p&gt;

&lt;p&gt;The goal for Stage 0 was straightforward:&lt;br&gt;&lt;br&gt;
👉 Build a small backend application that returns some personal info — and something extra (Cats facts).&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Project Overview
&lt;/h2&gt;

&lt;p&gt;This project, called &lt;strong&gt;Cat Facts API Integration&lt;/strong&gt;, fetches random cat facts from the public &lt;a href="https://catfact.ninja/fact" rel="noopener noreferrer"&gt;Cat Facts API&lt;/a&gt; and combines them with basic user information.&lt;/p&gt;

&lt;p&gt;It includes three simple endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; → Root welcome route
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/health&lt;/code&gt; → Health check
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/me&lt;/code&gt; → My personal info + a random cat fact
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All built with &lt;strong&gt;FastAPI&lt;/strong&gt;, &lt;strong&gt;httpx&lt;/strong&gt;, and a bit of structured logging.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
HNG13_simple_rest_stage_0/
├── app.py
├── app.log
├── requirements.txt
└── README.md

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;The app includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FastAPI&lt;/code&gt; for routing and documentation
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;httpx&lt;/code&gt; for making async API requests
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;logging&lt;/code&gt; for monitoring
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CORS middleware&lt;/code&gt; for flexible frontend integration if need be
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Endpoints Documentation
&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Root endpoint that returns a welcome message.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Confirms that the API is healthy and reachable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/me&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns user info (email, name, stack) along with a random cat fact.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧪 Example Response
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GET /me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "status": "success",&lt;br&gt;
  "user": {&lt;br&gt;
    "email": "afariogun.john2002@gmail.com",&lt;br&gt;
    "name": "John Afariogun",&lt;br&gt;
    "stack": "Python/FastAPI"&lt;br&gt;
  },&lt;br&gt;
  "timestamp": "2025-10-15T10:00:00Z",&lt;br&gt;
  "fact": "Cats sleep for around 70% of their lives."&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧰 Running the App Locally
&lt;/h2&gt;

&lt;p&gt;To test this project on your system:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
git clone https://github.com/johnafariogun/HNG13_simple_rest_stage_0&lt;br&gt;
cd HNG13_simple_rest_stage_0&lt;br&gt;
pip install -r requirements.txt&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then start the server:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
python app.py&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Visit these URLs in your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://127.0.0.1:8080" rel="noopener noreferrer"&gt;http://127.0.0.1:8080&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://127.0.0.1:8080/me" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://127.0.0.1:8080/docs" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/docs&lt;/a&gt; → to explore the auto-generated FastAPI documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  You can checkout (mine)[&lt;a href="https://hng13simplereststage0-production.up.railway.app/me" rel="noopener noreferrer"&gt;https://hng13simplereststage0-production.up.railway.app/me&lt;/a&gt;] 
&lt;/h2&gt;

&lt;h2&gt;
  
  
  📸 OpenAPI Documentation
&lt;/h2&gt;

&lt;p&gt;FastAPI automatically provides Swagger UI for your routes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🧭 Navigate to &lt;code&gt;/docs&lt;/code&gt;&lt;br&gt;
You’ll see your &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/health&lt;/code&gt;, and &lt;code&gt;/me&lt;/code&gt; routes neatly documented — no extra setup needed!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🌍 Deployment
&lt;/h2&gt;

&lt;p&gt;So I deployed this on Railway&lt;/p&gt;

&lt;p&gt;You can check out how to deploy a fastapi app on railway at the &lt;a href="https://docs.railway.com/guides/fastapi" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;How to set up a simple FastAPI app&lt;/li&gt;
&lt;li&gt;How to call external APIs asynchronously using &lt;code&gt;httpx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to implement logging and error handling&lt;/li&gt;
&lt;li&gt;How to structure JSON responses neatly&lt;/li&gt;
&lt;li&gt;The power of automatic documentation in FastAPI&lt;/li&gt;
&lt;li&gt;How to deploy using Railway&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔚 Conclusion
&lt;/h2&gt;

&lt;p&gt;Stage 0 may look simple, but it’s where the foundation for cleaner, production-ready APIs begins.&lt;/p&gt;

&lt;p&gt;This project taught me how small things — like handling timeouts, errors, and logs — make a huge difference when building real backend systems.&lt;/p&gt;

&lt;p&gt;Next step? &lt;strong&gt;Stage 1 of HNG 13&lt;/strong&gt;, probably focusing on testing, deployment, and CI/CD 🚀&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;👨‍💻 Author:&lt;/strong&gt; John Afariogun&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Python / FastAPI&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="//github.com/johnafariogun"&gt;@johnafariogun&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="//mailto:afariogun.john2002@gmail.com"&gt;afariogun.john2002@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

</description>
      <category>backend</category>
      <category>railway</category>
      <category>cats</category>
      <category>python</category>
    </item>
  </channel>
</rss>
