<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Johnpaul Muoneme on Medium]]></title>
        <description><![CDATA[Stories by Johnpaul Muoneme on Medium]]></description>
        <link>https://medium.com/@johnpaulmuoneme?source=rss-77e23f9d860------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*l0xbvraG017Oq8h52ugWKw.png</url>
            <title>Stories by Johnpaul Muoneme on Medium</title>
            <link>https://medium.com/@johnpaulmuoneme?source=rss-77e23f9d860------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 18 May 2026 11:32:50 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@johnpaulmuoneme/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Setting up a simple Caddy reverse-proxy server on an Amazon EC2 instance — Amazon Linux]]></title>
            <link>https://medium.com/@johnpaulmuoneme/setting-up-a-simple-caddy-reverse-proxy-server-on-an-amazon-ec2-instance-amazon-linux-afd45614500d?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/afd45614500d</guid>
            <category><![CDATA[amazon-linux]]></category>
            <category><![CDATA[caddy]]></category>
            <category><![CDATA[ec2]]></category>
            <category><![CDATA[reverse-proxy]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Sun, 20 Jul 2025 18:51:50 GMT</pubDate>
            <atom:updated>2025-07-20T18:54:10.033Z</atom:updated>
            <content:encoded><![CDATA[<h3>Setting up a simple Caddy reverse-proxy server on an Amazon EC2 instance — Amazon Linux</h3><p>So, here I was deciding what to pick for my Golang app deployment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*w21ywJ0JSD-LvT1b" /><figcaption>Photo by <a href="https://unsplash.com/@victoriano?utm_source=medium&amp;utm_medium=referral">Victoriano Izquierdo</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Building an app to deploy wasn’t the problem, setting up the instance WAS the problem, because I had forgotten a lot, thankfully — AWS looked the same since I last deployed anything.</p><p>One of the fundamental parts of an application deployment is the reverse-proxy server. Ideally, these are the steps to get your application deployed:</p><ul><li>Write some (runnable) code</li><li>Get a VPS provider (for a server)</li><li>Setup a CI process to build and run your code on the server</li><li>Redirect traffic from a specific URL to your app</li></ul><h3>Step 1 — Write some (runnable) code</h3><p>Over here, the amazing “Hello worlds” will wave at us and tell us there’s nothing wrong with that, but I want us to set up a simple API Gateway in code. What’s an API Gateway you may ask? Well, it’s an API that forwards requests to other APIs.</p><pre>// File: cmd/api-gateway/main.go<br>package main<br><br>import (<br> &quot;context&quot;<br> &quot;fmt&quot;<br> &quot;log&quot;<br> &quot;net/http&quot;<br> &quot;os&quot;<br> &quot;os/signal&quot;<br> &quot;syscall&quot;<br> &quot;time&quot;<br>)<br><br>func main() {<br> // Load configuration<br> cfg, err := config.Load()<br> if err != nil {<br>  log.Fatalf(&quot;Failed to load configuration: %v&quot;, err)<br> }<br><br> // Initialize gRPC clients<br> grpcClients, err := clients.NewGRPCClients(cfg)<br> if err != nil {<br>  log.Fatalf(&quot;Failed to initialize gRPC clients: %v&quot;, err)<br> }<br> defer grpcClients.Close()<br><br> // Initialize gateway<br> gw := gateway.New(grpcClients)<br><br> // Setup router<br> router := router.NewGatewayRouter(gw).SetupRouter()<br><br> // Start server<br> srv := &amp;http.Server{<br>  Addr:         fmt.Sprintf(&quot;:%d&quot;, cfg.Port),<br>  Handler:      router,<br>  ReadTimeout:  cfg.ReadTimeout,<br>  WriteTimeout: cfg.WriteTimeout,<br>  IdleTimeout:  cfg.IdleTimeout,<br> }<br><br> // Start server in a goroutine<br> go func() {<br>  log.Printf(&quot;Starting API Gateway on port %d (Instance: %s)&quot;, cfg.Port, cfg.InstanceID)<br>  if err := srv.ListenAndServe(); err != nil &amp;&amp; err != http.ErrServerClosed {<br>   log.Fatal(&quot;Failed to start server:&quot;, err)<br>  }<br> }()<br><br> // Wait for interrupt signal<br> quit := make(chan os.Signal, 1)<br> signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<br> &lt;-quit<br><br> log.Println(&quot;Shutting down server...&quot;)<br> ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)<br> defer cancel()<br><br> if err := srv.Shutdown(ctx); err != nil {<br>  log.Fatalf(&quot;Server forced to shutdown: %v&quot;, err)<br> }<br><br> log.Println(&quot;Server exited&quot;)<br>}</pre><p>Hey! What are you doing? Just copying some code off the internet hoping it works?? Well, that’s too lazy, <em>don’t</em> keep doing that, you’d not really learn the hard things.</p><p>I skipped a lot of parts in the code though — the config settings, the GRPC class, heck I even skipped the router part! Unfortunately, if you can’t write runnable Golang server code without AI help in 2025, you need to jump back into that bootcamp you ran away from.</p><p>Although, if you’re here to pick up some Golang skills, sorry about this joke — I will edit this part to point to a crash course in future.</p><h3>Step 2 — Get a VPS Provider (for a server)</h3><p>Choose who you prefer to get frustrated by for the next couple of months:</p><ul><li>Amazon?</li><li>DigitalOcean?</li><li>Google?</li></ul><p>I chose Amazon’s Web Service, why not? I like to live dangerously.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F88HMaqBTiNUBO%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcXc1dnM3dWM2aHFrNDFwbXZqY29oNXZtZzI3YTVqMGU0ajNmN2hlNSZlcD12MV9naWZzX3NlYXJjaCZjdD1n%2F88HMaqBTiNUBO%2Fgiphy.gif&amp;image=https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcjJua2E1eGJuOHNjZmh3MGppYmNrYWM2NWVidDh5bDA4Z2gzeW42NiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F88HMaqBTiNUBO%2Fgiphy_s.gif&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="239" frameborder="0" scrolling="no"><a href="https://medium.com/media/0ae8d41d3ce4c3649cd0e08f13c578e6/href">https://medium.com/media/0ae8d41d3ce4c3649cd0e08f13c578e6/href</a></iframe><p>AWS’s t3.micro EC2 instance works well for me, I don’t need large storage too, so perfect!</p><h3><strong>Step 3 — Setup a CI process to build and run your code on the server</strong></h3><p>Now, you might be thinking — “What did I do to deserve this kind of LONG article?” — The simple answer, you’re CHOSEN, that’s all.</p><p>Setting up a CI Process is required! I think you should be lazy enough to do this part, stay with me now.</p><p>So, for AWS, all you need is your .pem file for connecting to your instance and the IP address of your instance for SSH connectivity.</p><p>Secondly, I like to set up a workflow that does much — with workflow dispatch that can be copied to anywhere:</p><pre>name: Deploy API Gateway<br><br>on:<br>  push:<br>    branches: [ &quot;main&quot;, &quot;dev&quot; ]<br>  pull_request:<br>    branches: [ &quot;main&quot; ]<br>  workflow_dispatch:<br>    inputs:<br>      action:<br>        description: &#39;Action to perform&#39;<br>        required: true<br>        default: &#39;deploy&#39;<br>        type: choice<br>        options:<br>          - deploy<br>          - start<br>          - stop<br>          - restart<br>          - status<br>          - logs<br>          - setup-service<br><br>env:<br>  GO_VERSION: &quot;1.23.4&quot;<br>  SERVICE_NAME: &quot;api-gateway&quot;<br>  DEPLOY_PATH: &quot;/usr/local/bin&quot;<br>  BINARY_NAME: &quot;api&quot;<br><br>jobs:<br>  # Test stage<br>  test:<br>    runs-on: ubuntu-latest<br>    if: github.event_name != &#39;workflow_dispatch&#39; || github.event.inputs.action == &#39;deploy&#39;<br>    steps:<br>      - uses: actions/checkout@v4<br>      <br>      - name: Set up Go<br>        uses: actions/setup-go@v5<br>        with:<br>          go-version: ${{ env.GO_VERSION }}<br>          cache: true<br>      <br>      - name: Create environment file<br>        run: echo &quot;${{ secrets.ENV }}&quot; &gt; .env<br>        <br>      - name: Run tests<br>        run: go test -v ./...<br>        <br>      - name: Run code analysis<br>        run: go vet ./...<br><br>  # Build stage<br>  build:<br>    needs: test<br>    runs-on: ubuntu-latest<br>    if: github.event_name != &#39;workflow_dispatch&#39; || github.event.inputs.action == &#39;deploy&#39;<br>    steps:<br>      - uses: actions/checkout@v4<br>      <br>      - name: Set up Go<br>        uses: actions/setup-go@v5<br>        with:<br>          go-version: ${{ env.GO_VERSION }}<br>          cache: true<br>      <br>      - name: Install Go dependencies<br>        run: go mod tidy<br>        <br>      - name: Create environment file<br>        run: echo &quot;${{ secrets.ENV }}&quot; &gt; .env<br>        <br>      - name: Build application<br>        run: GOOS=linux GOARCH=amd64 go build -o bin/${{ env.BINARY_NAME }} ./cmd/api-gateway<br>    <br>      - name: Upload build artifact<br>        uses: actions/upload-artifact@v4<br>        with:<br>          name: api-binary<br>          path: bin/${{ env.BINARY_NAME }}<br>          retention-days: 7<br><br>  # Setup service (manual trigger only)<br>  setup-service:<br>    runs-on: ubuntu-latest<br>    if: github.event_name == &#39;workflow_dispatch&#39; &amp;&amp; github.event.inputs.action == &#39;setup-service&#39;<br>    steps:<br>      - uses: actions/checkout@v4<br>      <br>      - name: Set up SSH<br>        run: |<br>          mkdir -p ~/.ssh<br>          chmod 700 ~/.ssh<br>          echo &quot;${{ secrets.SSH_PRIVATE_KEY }}&quot; &gt; ~/.ssh/id_rsa<br>          chmod 600 ~/.ssh/id_rsa<br>          ssh-keyscan -H ${{ secrets.EC2_INSTANCE_IP }} &gt;&gt; ~/.ssh/known_hosts<br>          chmod 644 ~/.ssh/known_hosts<br>      <br>      - name: Setup systemd service<br>        run: |<br>          ssh -i ~/.ssh/id_rsa ec2-user@${{ secrets.EC2_INSTANCE_IP }} &lt;&lt; &#39;EOF&#39;<br>            # Create service user<br>            sudo useradd --system --home /var/lib/${{ env.SERVICE_NAME }} --create-home --shell /bin/false ${{ env.SERVICE_NAME }} || true<br>            <br>            # Create directories<br>            sudo mkdir -p /var/lib/${{ env.SERVICE_NAME }}<br>            sudo mkdir -p /var/log/${{ env.SERVICE_NAME }}<br>            sudo mkdir -p /etc/${{ env.SERVICE_NAME }}<br>            <br>            # Set ownership<br>            sudo chown -R ${{ env.SERVICE_NAME }}:${{ env.SERVICE_NAME }} /var/lib/${{ env.SERVICE_NAME }}<br>            sudo chown -R ${{ env.SERVICE_NAME }}:${{ env.SERVICE_NAME }} /var/log/${{ env.SERVICE_NAME }}<br>            sudo chown -R ${{ env.SERVICE_NAME }}:${{ env.SERVICE_NAME }} /etc/${{ env.SERVICE_NAME }}<br>            <br>            # Create systemd service file<br>            sudo tee /etc/systemd/system/${{ env.SERVICE_NAME }}.service &gt; /dev/null &lt;&lt; &#39;SERVICE_EOF&#39;<br>          [Unit]<br>          Description=API Gateway Service<br>          Documentation=https://github.com/yourorg/your-repo<br>          After=network.target<br>          Wants=network-online.target<br><br>          [Service]<br>          Type=simple<br>          User=${{ env.SERVICE_NAME }}<br>          Group=${{ env.SERVICE_NAME }}<br>          WorkingDirectory=/var/lib/${{ env.SERVICE_NAME }}<br>          EnvironmentFile=/etc/${{ env.SERVICE_NAME }}/.env<br>          ExecStart=${{ env.DEPLOY_PATH }}/${{ env.BINARY_NAME }}<br>          ExecReload=/bin/kill -HUP $MAINPID<br>          KillMode=mixed<br>          KillSignal=SIGINT<br>          TimeoutStopSec=30<br><br>          # Security settings<br>          NoNewPrivileges=true<br>          PrivateTmp=true<br>          ProtectSystem=strict<br>          ProtectHome=true<br>          ReadWritePaths=/var/lib/${{ env.SERVICE_NAME }} /var/log/${{ env.SERVICE_NAME }}<br><br>          # Resource limits<br>          LimitNOFILE=65536<br>          LimitNPROC=4096<br><br>          # Logging<br>          StandardOutput=append:/var/log/${{ env.SERVICE_NAME }}/service.log<br>          StandardError=append:/var/log/${{ env.SERVICE_NAME }}/error.log<br>          SyslogIdentifier=${{ env.SERVICE_NAME }}<br><br>          # Restart policy<br>          Restart=always<br>          RestartSec=5<br>          StartLimitInterval=60<br>          StartLimitBurst=3<br><br>          [Install]<br>          WantedBy=multi-user.target<br>          SERVICE_EOF<br>            <br>            # Reload systemd and enable service<br>            sudo systemctl daemon-reload<br>            sudo systemctl enable ${{ env.SERVICE_NAME }}<br>            <br>            echo &quot;Service setup complete!&quot;<br>            echo &quot;Service file created at: /etc/systemd/system/${{ env.SERVICE_NAME }}.service&quot;<br>            echo &quot;Configuration directory: /etc/${{ env.SERVICE_NAME }}/&quot;<br>            echo &quot;Logs directory: /var/log/${{ env.SERVICE_NAME }}/&quot;<br>          EOF<br><br>  # Service management (manual triggers only)<br>  service-control:<br>    runs-on: ubuntu-latest<br>    if: github.event_name == &#39;workflow_dispatch&#39; &amp;&amp; contains(&#39;start,stop,restart,status,logs&#39;, github.event.inputs.action)<br>    steps:<br>      - name: Set up SSH<br>        run: |<br>          mkdir -p ~/.ssh<br>          chmod 700 ~/.ssh<br>          echo &quot;${{ secrets.SSH_PRIVATE_KEY }}&quot; &gt; ~/.ssh/id_rsa<br>          chmod 600 ~/.ssh/id_rsa<br>          ssh-keyscan -H ${{ secrets.EC2_INSTANCE_IP }} &gt;&gt; ~/.ssh/known_hosts<br>          chmod 644 ~/.ssh/known_hosts<br>      <br>      - name: Execute service action<br>        run: |<br>          ssh -i ~/.ssh/id_rsa ec2-user@${{ secrets.EC2_INSTANCE_IP }} &lt;&lt; EOF<br>            case &quot;${{ github.event.inputs.action }}&quot; in<br>              &quot;start&quot;)<br>                echo &quot;Starting ${{ env.SERVICE_NAME }} service...&quot;<br>                sudo systemctl start ${{ env.SERVICE_NAME }}<br>                ;;<br>              &quot;stop&quot;)<br>                echo &quot;Stopping ${{ env.SERVICE_NAME }} service...&quot;<br>                sudo systemctl stop ${{ env.SERVICE_NAME }}<br>                ;;<br>              &quot;restart&quot;)<br>                echo &quot;Restarting ${{ env.SERVICE_NAME }} service...&quot;<br>                sudo systemctl restart ${{ env.SERVICE_NAME }}<br>                ;;<br>              &quot;status&quot;)<br>                echo &quot;Service status:&quot;<br>                sudo systemctl status ${{ env.SERVICE_NAME }} --no-pager<br>                ;;<br>              &quot;logs&quot;)<br>                echo &quot;Recent service logs:&quot;<br>                sudo journalctl -u ${{ env.SERVICE_NAME }} --no-pager -n 50<br>                echo &quot;&quot;<br>                echo &quot;Recent application logs:&quot;<br>                sudo tail -n 50 /var/log/${{ env.SERVICE_NAME }}/service.log || echo &quot;No service logs found&quot;<br>                echo &quot;&quot;<br>                echo &quot;Recent error logs:&quot;<br>                sudo tail -n 50 /var/log/${{ env.SERVICE_NAME }}/error.log || echo &quot;No error logs found&quot;<br>                ;;<br>            esac<br>            <br>            # Always show final status unless it was a logs command<br>            if [ &quot;${{ github.event.inputs.action }}&quot; != &quot;logs&quot; ]; then<br>              echo &quot;&quot;<br>              echo &quot;Current service status:&quot;<br>              sudo systemctl status ${{ env.SERVICE_NAME }} --no-pager || true<br>            fi<br>          EOF<br><br>  # Deploy to dev<br>  deploy-dev:<br>    if: github.ref == &#39;refs/heads/dev&#39; &amp;&amp; (github.event_name == &#39;push&#39; || (github.event_name == &#39;workflow_dispatch&#39; &amp;&amp; github.event.inputs.action == &#39;deploy&#39;))<br>    needs: build<br>    runs-on: ubuntu-latest<br>    steps:<br>      - name: Download build artifact<br>        uses: actions/download-artifact@v4<br>        with:<br>          name: api-binary<br>          path: bin/<br>      <br>      - name: Make binary executable<br>        run: chmod +x bin/${{ env.BINARY_NAME }}<br>      <br>      - name: Create environment file<br>        run: echo &quot;${{ secrets.ENV }}&quot; &gt; .env<br>      <br>      - name: Set up SSH<br>        run: |<br>          mkdir -p ~/.ssh<br>          chmod 700 ~/.ssh<br>          echo &quot;${{ secrets.SSH_PRIVATE_KEY }}&quot; &gt; ~/.ssh/id_rsa<br>          chmod 600 ~/.ssh/id_rsa<br>          ssh-keyscan -H ${{ secrets.EC2_INSTANCE_IP }} &gt;&gt; ~/.ssh/known_hosts<br>          chmod 644 ~/.ssh/known_hosts<br>      <br>      - name: Deploy to EC2 Dev<br>        run: |<br>          scp -i ~/.ssh/id_rsa bin/${{ env.BINARY_NAME }} .env ec2-user@${{ secrets.EC2_INSTANCE_IP }}:/home/ec2-user/<br>          <br>          ssh -i ~/.ssh/id_rsa ec2-user@${{ secrets.EC2_INSTANCE_IP }} &lt;&lt; &#39;EOF&#39;<br>            # Stop service gracefully<br>            sudo systemctl stop ${{ env.SERVICE_NAME }} || true<br>            <br>            # Update binary<br>            sudo mv /home/ec2-user/${{ env.BINARY_NAME }} ${{ env.DEPLOY_PATH }}/<br>            sudo chmod +x ${{ env.DEPLOY_PATH }}/${{ env.BINARY_NAME }}<br>            sudo chown ${{ env.SERVICE_NAME }}:${{ env.SERVICE_NAME }} ${{ env.DEPLOY_PATH }}/${{ env.BINARY_NAME }}<br>            <br>            # Update environment file<br>            sudo mv /home/ec2-user/.env /etc/${{ env.SERVICE_NAME }}/<br>            sudo chown ${{ env.SERVICE_NAME }}:${{ env.SERVICE_NAME }} /etc/${{ env.SERVICE_NAME }}/.env<br>            <br>            # Start service<br>            sudo systemctl start ${{ env.SERVICE_NAME }}<br>            <br>            # Wait a moment and check status<br>            sleep 3<br>            sudo systemctl status ${{ env.SERVICE_NAME }} --no-pager<br>          EOF<br>      <br>      - name: Health check (Optional)<br>        run: |<br>          sleep 10<br>          # Replace with your actual health check endpoint<br>          if command -v curl &gt;/dev/null 2&gt;&amp;1; then<br>            curl -f -s http://${{ secrets.EC2_INSTANCE_IP }}:${{ secrets.PORT}}/health || echo &quot;Health check endpoint not available&quot;<br>          else<br>            echo &quot;Curl not available, skipping health check&quot;<br>          fi<br></pre><p>Okay, I gave you the workflow file of your dreams, you can worship me now.</p><p>You may be wondering why I gave you this and didn’t give you the code. Simple answer, “I don’t know” — I don’t have the answer to everything. Also, I didn’t give you the .env and secrets that I keep making reference to, so…</p><h3>Step 4 — Redirect traffic from a specific URL to your app</h3><p>Now, we get to talk about the clickbait I used into pulling you into this article — <a href="https://caddyserver.com/"><strong>Caddy</strong></a><strong>.</strong></p><p>Before that though, if you don’t know <a href="https://caddyserver.com/">Caddy</a>, you’re missing out — it’s <a href="https://nginx.org/en/">Nginx’s</a> cousin — written in Golang (so I’m super biased). There’s this <a href="https://blog.9ssi7.dev/why-choose-caddy-server-over-nginx-e49b01c631a1">article</a> that explains why you may choose it even though the article is more biased than I am :) — I love <a href="https://nginx.org/en/">nginx</a> though, but maybe when I have more complex deployments to do — xoxo.</p><p>So, who is Nginx and why are we NOT talking about her today? — Yep it’s a her, stick with me now.</p><p>These guys know how to do one thing well — “reverse proxying”</p><p>They listen on your specified domain and forward the request to the required service, are you with me now?</p><p>So, they are my API Gateway’s Gateway — WORD!</p><p><strong>Installing Caddy on Amazon Linux</strong></p><pre># Add the Caddy repository<br>sudo yum install -y yum-utils<br>sudo yum-config-manager --add-repo https://caddyserver.com/api/download?os=linux&amp;arch=amd64&amp;arm=&amp;p=github.com%2Fcaddyserver%2Fcaddy<br><br># Install Caddy<br>sudo yum install caddy</pre><p>This entire article could simply be the bash script above, Yes. Because the title says so — Why did you click this link without knowing what reverse proxying is? skazhite mne, pozhaluysta!</p><p><strong>The CaddyFile</strong></p><pre>&lt;your-domain.com&gt; {<br>    reverse_proxy [::1]:9000<br>}</pre><p>My app is hosted on port 9000, so I pointed my entire domain there, because I like to live dangerously.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F88HMaqBTiNUBO%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcXc1dnM3dWM2aHFrNDFwbXZqY29oNXZtZzI3YTVqMGU0ajNmN2hlNSZlcD12MV9naWZzX3NlYXJjaCZjdD1n%2F88HMaqBTiNUBO%2Fgiphy.gif&amp;image=https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcjJua2E1eGJuOHNjZmh3MGppYmNrYWM2NWVidDh5bDA4Z2gzeW42NiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F88HMaqBTiNUBO%2Fgiphy_s.gif&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="239" frameborder="0" scrolling="no"><a href="https://medium.com/media/0ae8d41d3ce4c3649cd0e08f13c578e6/href">https://medium.com/media/0ae8d41d3ce4c3649cd0e08f13c578e6/href</a></iframe><h3>Conclusion</h3><p>Doing all these things and feeling like a boss doesn’t mean your app would automatically become available, sorry to burst your bubble. If it worked, Phew! — If it didn’t , you’re on your own — 🌚</p><p><strong>Issues you may face:</strong></p><p><strong>I can’t see my app on that URL</strong></p><ul><li>Are you sure you’ve set up the URL correctly?</li><li>run <strong><em>dig &lt;your-domain.com&gt;</em></strong> from anywhere and check to see if you see your machine IP on the results</li><li>Is your domain registrar aware of this your deployment? If you haven’t heard of <a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-ec2-instance.html">Route 53</a> and you’re using AWS — There’s no way this article worked. If it did, please send me an email so that I can help stop that abomination.</li></ul><p>Thank you for reading this unorganized mess of ideas flying from my head.</p><p>Johnpaul Muoneme: <em>Code Architect —</em><strong><em> 8Ball NG</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=afd45614500d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Midnight Coding Chronicles: Why I Built a Migration Editor Script]]></title>
            <link>https://medium.com/@johnpaulmuoneme/midnight-coding-chronicles-why-i-built-a-migration-editor-script-ea865d6dcf12?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/ea865d6dcf12</guid>
            <category><![CDATA[database-migration]]></category>
            <category><![CDATA[zsh]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[script]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Wed, 15 Jan 2025 23:00:26 GMT</pubDate>
            <atom:updated>2025-01-15T23:00:26.566Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VANVgs0nIYDOL2pk" /><figcaption>Photo by <a href="https://unsplash.com/@mattrbenn?utm_source=medium&amp;utm_medium=referral">Matt Benn</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>— My visual idea of Postgres migrations</h4><p>It was a quiet night, the kind where you can hear your thoughts bounce off the walls. My to-do list for the day had already been ambitious, but a pressing problem emerged that couldn’t wait for the light of day: a tangled mess of database migrations.</p><p>As a software engineer, I’ve always enjoyed (<em>that’s a strong word?</em>) the methodical nature of working with migrations. They’re the lifeblood of evolving systems — incrementally improving schemas while preserving the past. But that (<em>this?</em>) night, I was confronted with a common yet tedious problem: renumbering and restructuring migration files still in development.</p><h3>The Problem: Manual Renumbering Chaos</h3><p>If you’ve ever worked on a project with evolving migrations, you know the drill — adding a new migration to a sequence means renaming several existing files to make room and forgetting to do this properly leads to out-of-order migrations, causing headaches for your team and future-you. While tools exist for production-ready migrations, few handle in-development migrations gracefully.</p><h3>Inspiration Strikes</h3><p>Frustrated by the repetitive renumbering process and aware of the looming morning deadline, I decided to automate this problem. The idea was simple: write a Zsh script that could:</p><ol><li>Insert new migrations seamlessly, renumbering existing files where necessary.</li><li>Create both .up.sql and .down.sql files for the new migration.</li><li>Allow easy reversion of migrations by removing the relevant files and renumbering only the subsequent ones.</li></ol><p>I settled specifically for ZSH just out of habit, it’s not offering any special functionalities (yet).</p><h3>The Solution: Migration Editor Script</h3><p>What emerged in the early hours of the morning was a concise, efficient script. With a single command, I could:</p><ul><li>Add a new migration to a specific folder, ensuring all filenames stayed sequential.</li><li>Remove an existing migration and maintain order without disturbing unrelated files.</li></ul><h3>Why It’s Useful</h3><p>This script filled a glaring gap in my development workflow. It’s not uncommon for migrations to change multiple times before release. Having a tool that lets me focus on the actual migration logic instead of micromanaging file names was a game changer. It also improved collaboration by making the migration structure predictable for everyone on the team.</p><h3>Lessons from Midnight Coding</h3><p>Building this script reminded me of a few things:</p><ol><li><strong>Necessity Breeds Innovation:</strong> The best tools often come from solving immediate, personal pain points.</li><li><strong>Automation is Key:</strong> If something is repetitive and error-prone, automate it. Future-you will thank you.</li><li><strong>Simplicity Wins:</strong> By keeping the script’s functionality focused and intuitive, it became easier to use and maintain.</li></ol><h3>A Call to Action</h3><p>If you’ve ever been in a similar situation, juggling migration files while racing against a deadline, consider giving this script a try. <a href="https://github.com/lord-tx/migration_editor">View the implementation on GitHub</a>. It’s a small tool, but it is saving me hours and a fair share of frustration. Plus, there’s something deeply satisfying about turning a problem into a <em>somewhat</em> polished solution — even if it means burning the midnight oil to do it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea865d6dcf12" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Great Blockchain Heist: Digital Snake Oil]]></title>
            <link>https://medium.com/@johnpaulmuoneme/the-great-blockchain-heist-digital-snake-oil-7c44f3fc48b2?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/7c44f3fc48b2</guid>
            <category><![CDATA[digital-asset]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[bitcoin]]></category>
            <category><![CDATA[snake-oil]]></category>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Sat, 11 Jan 2025 17:08:53 GMT</pubDate>
            <atom:updated>2025-01-11T17:08:53.862Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BfawRvyJy6oIvpDv" /><figcaption>Photo by <a href="https://unsplash.com/@coinhako_official?utm_source=medium&amp;utm_medium=referral">Coinhako</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Picture this: You’re standing in line at a digital coffee shop, clutching your virtual wallet. The person ahead orders a $3 latte, then winces as they’re charged a $47 “convenience fee” to process the transaction. Welcome to the world of Ethereum, where paying more in gas fees than the actual transaction value isn’t a bug — it’s a feature.</p><p>Blockchain evangelists promised us a decentralized utopia. What we got instead was a digital toll booth on every corner, operated by miners who make highway robbers look charitable. Ethereum’s gas fees aren’t just high — they’re a masterclass in absurdity, fluctuating with the reliability of a weather vane in a hurricane.</p><p>But perhaps that’s fitting for a technology that’s essentially a solution searching for a problem. Blockchain: the answer to the question nobody asked, unless that question was “How can we make databases less efficient while consuming the energy output of a small nation?”</p><p>The true joke isn’t the technology itself — it’s the collective suspension of disbelief required to keep this circus running. We’ve created a system where digital artists spend more energy minting an NFT than their great-grandparents used in their entire lifetime, all while preaching about democratizing finance.</p><p>And yet, like a casino where everyone thinks they’ll be the exception, the crypto world keeps spinning. Developers keep building, investors keep investing, and somewhere, a computer is burning enough electricity to power a small city — all to process someone’s $5 transaction with a mere $150 gas fee.</p><p>The emperor isn’t just naked — he’s trying to sell you his invisible clothes as an NFT.</p><p>The real magic of blockchain isn’t in its technology — it’s in its ability to make intelligent people abandon common sense. Venture capitalists who wouldn’t buy a sandwich without a 50-page due diligence report suddenly throw millions at projects whose white papers read like science fiction written by a ChatGPT prompt gone wrong.</p><p>Consider the evolutionary path: Bitcoin appears, promising freedom from banks. Then comes Ethereum, promising to run the world’s computer. Now we have thousands of blockchains, each promising to be the next big thing, while collectively delivering all the utility of a digital pet rock. It’s like watching a game of technological telephone, where each iteration gets progressively more divorced from reality.</p><p>The Layer 2 solutions they’re building? That’s like adding a second story to a house with no foundation. “But it’ll scale!” they cry, as if bolting on complexity to a fundamentally flawed system is the answer. It’s the equivalent of solving a traffic jam by building roads on top of roads — eventually, you’re just creating skyscrapers of problems.</p><p>And let’s talk about the community — a fascinating blend of libertarian idealists, get-rich-quick schemers, and tech enthusiasts who’ve convinced themselves that paying $200 to move $50 is somehow the future of finance. They’ve created a culture where losing money is called “having diamond hands” and common sense is FUD (Fear, Uncertainty, and Doubt).</p><p>The truly ironic part? The same people who rail against traditional finance’s 2% transaction fees will happily pay 50% in gas fees because it’s “decentralized.” It’s as if the mere act of making something more complicated and less efficient somehow makes it more valuable.</p><p>But perhaps the most entertaining aspect is watching blockchain try to solve real-world problems. It’s like watching someone try to eat soup with a fork — technically possible, but there are clearly better tools for the job. Need a database? Here’s a blockchain! Want to track shipping? Blockchain! Have a headache? You guessed it — there’s probably a blockchain solution for that too.</p><p>The emperor is still naked, but now he’s giving a TED talk about how clothes are outdated technology anyway.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c44f3fc48b2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Problems: Why do smart people need to dumb down to communicate?]]></title>
            <link>https://medium.com/@johnpaulmuoneme/ai-problems-why-do-smart-people-need-to-dumb-down-to-communicate-dfc148c33aa1?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/dfc148c33aa1</guid>
            <category><![CDATA[tools]]></category>
            <category><![CDATA[questions]]></category>
            <category><![CDATA[intelligence]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Fri, 09 Aug 2024 10:01:26 GMT</pubDate>
            <atom:updated>2024-08-09T18:12:12.894Z</atom:updated>
            <content:encoded><![CDATA[<p>These days, smart people (or slightly above average intelligence people) have to try to dumb THEMSELVES down to sound like they actually didn’t cheat on a test or they actually wrote an article. This needs to stop.</p><p>I think people simply need to use the tools at their disposal to try to get better instead of hating on these tools and claiming that every smart person is high on GPT.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BuRKXMcHsmh4rxYE" /><figcaption>Photo by <a href="https://unsplash.com/@elimendeinagella?utm_source=medium&amp;utm_medium=referral">Elimende Inagella</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this AGE or dare I say ‘Period’ of the ‘AI’ fad, there have been series of incidents that have caused this article to spawn out of the brain of an angry writer onto the inter-webs to be consumed not by the target audience (aka Naysayers), but by people that share a similar sentiment with me — Smarty Pants.</p><p>As a Software Engineer, I’m held to high standards (fortunately) when there’s a need to produce working and usable software but my opinions are blatantly disregarded concerning how that software should be built or how it even works! This is me digressing because I’m in the angry mood, but there’s a reason for that, AI.</p><p>Artificial Intelligence is simply ‘smarter computing’ — but the term ‘smarter’ seems not to be able to be comprehended by people. It isn’t a brand new thing but an enhancement on the existing functionalities of computers made possible by an enormous amount of data being excreted by the holy and infallible minds on the internet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FSQBEMOdoH0XhRop" /><figcaption>Photo by <a href="https://unsplash.com/@barbarazandoval?utm_source=medium&amp;utm_medium=referral">Barbara Zandoval</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>There are 2 things I want people who read this article to do:</p><h3>Stop blaming your lack of verbal dexterity and awareness on the existence of Artificial Intelligence.</h3><h3>Stop looking for every avenue to SCREAM Bloody Murder! when someone sounds or writes like they have read a book or two.</h3><p>Before we go into the ethics of the usability of AI, let us start by understanding what the possible reasons for it were. A couple of my friends have compared it to the days of the <strong>calculator </strong>(that’s the little machine we use to <em>add, subtract, divide and multiply </em>for my default intelligence brethren), where the new portable machine made it possible to make complex calculations without the need to shed brain cells like lookup-tables storing complex results. It made it so that people who could do those things now looked like entertainers (<em>every single person that knows the first 50 digits of Pi is always looking for an avenue to say it — </em><strong>Entertainer</strong>).</p><p>The advent of computing caused a relegation of mundane things to the metal beasts we basically currently survive on, so why is it that we are riding on the wave of AI both super-positively — like its a fix-it-all solution and super-negatively — like it will bring upon the end of human intelligence (like humanity hasn’t already crossed that bridge since social media).</p><p>There are 3 things (<em>more like 2 things</em>) I want people who read this article to know:</p><h3>Artificial Intelligence has given us a lot of tools like LLMs (ChatGPT, BERT) and Generative AI like MidJourney (<em>not-so-mid if you ask me</em>).</h3><h3>Please learn to use them to ENHANCE yourself as an individual and improve your workflow.</h3><h3>Don’t rely on Pre-AI generic methods for evaluating people in tests or interviews, use reliable methods like situational-evaluation etc.</h3><p>SideNote: It’s funny how the world has always wanted people to have access to knowledge, but it’s like everyone is now scared that every other person <em>MAY</em> be on some very accessible knowledge — like isn’t that what we wanted.</p><p>NOTE: Don’t school me on how AI may be wrong at times, simply perform knowledge verification as we do with our textbooks and articles.</p><p>ALSO: I find it hard to stay talking about one thing, so I digress a lot, please ask me questions if you need specifics.</p><p><em>Don’t be bitter, be better.</em></p><p>Thank you for reading this article.</p><p>Johnpaul Muoneme</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dfc148c33aa1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code-Block: The Epic Time-waster]]></title>
            <link>https://medium.com/@johnpaulmuoneme/code-block-the-epic-time-waster-0bdd63306d80?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/0bdd63306d80</guid>
            <category><![CDATA[burnout]]></category>
            <category><![CDATA[weak]]></category>
            <category><![CDATA[code-block]]></category>
            <category><![CDATA[tired]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Mon, 27 May 2024 17:14:25 GMT</pubDate>
            <atom:updated>2024-05-27T17:14:25.534Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zaSzBYg_5z6yv5Xy" /><figcaption>Photo by <a href="https://unsplash.com/@gianreichmuth?utm_source=medium&amp;utm_medium=referral">Gian Reichmuth</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>There’s a reason you clicked this article. It’s not because this has any code snippets you need to solve the problem you’re looking for.</p><p>Take and eat.</p><pre>// Function to generate the Fibonacci sequence up to the nth term<br>func fibonacci(n int) []int {<br>    fib := make([]int, n)<br>    fib[0], fib[1] = 0, 1<br>    for i := 2; i &lt; n; i++ {<br>        fib[i] = fib[i-1] + fib[i-2]<br>    }<br>    return fib<br>}</pre><p>Oh! well, look at that. It’s the fibonacci generator in <a href="https://en.wikipedia.org/wiki/Go_(programming_language)">Go (Programming Language).</a></p><p>How many times has this function been written? Your first guess will be wrong (99.9% probability).</p><p>The seemingly useless function has helped a lot of undergraduate students and self-taught programmers understand recursion. I’m also not about to talk about the Mathematical implementation. I’m just partial to actual code in this article (No hard feelings).</p><p>I’m actually having a seemingly interesting code-block, that is what inspired me to write this article. It’s mainly a jumble of words that seem to reflect the state of my mind while I struggle to concentrate hard enough to figure out how to continue with what I’m working on, I really hope this isn’t ‘burnout’ disguised as code-block. Oh well, I may never know.</p><blockquote>There’s the proverbial noose around my motivation’s neck as it stands on the ledge of purpose.</blockquote><p>And because AI can’t help me render that, here’s an equivalent image of how I feel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WkZjNik02jftdQP72QZqOg.jpeg" /></figure><p>Also, sorry if I didn’t offer any solutions in this article. If I did, the article’s title would have been different.</p><p>Thank you for reading.</p><p>Johnpaul Muoneme</p><p>I’m tired.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0bdd63306d80" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Backward Compatible Code ← → Forward Compatible Thinking]]></title>
            <link>https://medium.com/@johnpaulmuoneme/backward-compatible-code-forward-compatible-thinking-50b91d631724?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/50b91d631724</guid>
            <category><![CDATA[backwards-compatibility]]></category>
            <category><![CDATA[code]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[backend]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Fri, 24 May 2024 03:34:52 GMT</pubDate>
            <atom:updated>2024-05-24T03:34:52.497Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/974/1*JswZGcUMIsMJc-P3OmYi8Q.png" /></figure><p>Backward compatible code <strong>← →</strong> Forward compatible thinking is a principle that emphasises writing code that can gracefully accommodate changes in both past and future environments. Here’s what it means in practice:</p><h3>Backward Compatible Code</h3><ul><li>This aspect focuses on ensuring that your code remains functional and compatible with older versions of dependencies, platforms, or APIs.</li><li>It involves writing code that can run without issues on older versions of libraries, frameworks, or platforms, ensuring a smooth transition for users who haven’t upgraded yet.</li><li>Backward compatible code allows existing users to upgrade their software without facing compatibility issues or breaking changes.</li></ul><p>Let’s say we have an API that serves users their account balance such as the following</p><pre>GET /api/account/{accountId}/balance<br><br>RESPONSE<br>{<br>  &quot;balance&quot;: 1000.50,<br>  &quot;currency&quot;: &quot;USD&quot;,<br>  &quot;lastUpdated&quot;: &quot;2024-05-23T12:00:00Z&quot;<br>}</pre><p>Here’s how the API might work:</p><ul><li>Method: GET</li><li>Path: /api/account/{accountId}/balance</li><li>Description: Retrieve the account balance for the user with the specified accountId.</li><li>NOTE: Although simple, we have to assume this is a secure banking system so users do not have access to PATHs that may contain other users’ information (e.g. userId).</li></ul><p>If this API functionality is to be updated, the assumption is that breaking backwards compatibility is bad, even in situations where we may not need the accountId object anymore, the preferred approach is still allow the user pass the values, as long as we DO NOT officially propose a complete system overhaul.</p><p>Moving from this:</p><pre>GET /api/account/{accountId}/balance<br><br>RESPONSE<br>{<br>  &quot;balance&quot;: 1000.50,<br>  &quot;currency&quot;: &quot;USD&quot;,<br>  &quot;lastUpdated&quot;: &quot;2024-05-23T12:00:00Z&quot;<br>}</pre><p>To this:</p><pre>GET /api/account/{accountId}/balance<br><br>RESPONSE<br>{<br>  &quot;balance&quot;: {<br>    &quot;amount&quot;: 1000.50,<br>    &quot;currency&quot;: &quot;USD&quot;<br>  },<br>  &quot;lastUpdated&quot;: &quot;2024-05-23T12:00:00Z&quot;,<br>  &quot;accountStatus&quot;: &quot;active&quot;,<br>  ...<br>}</pre><p>Is a very terrible way of migration, as existing systems may not be able to accomodate for this.</p><pre>GET /api/account/{accountId}/balance <br>GET /api/account/balance</pre><p>Instead, offering a clearer or newer way of accessing the same object type in form of versioning works, or simply returning the same object with the old fields unaffected while enhancing with newer fields.</p><pre>RESPONSE<br>{<br>  &quot;balance&quot;: 1000.50,<br>  &quot;currency&quot;: &quot;USD&quot;,<br>  &quot;lastUpdated&quot;: &quot;2024-05-23T12:00:00Z&quot;,<br>  &quot;accountStatus&quot;: &quot;active&quot;,<br>  &quot;new_balance&quot;: {<br>    &quot;amount&quot;: 1000.50,<br>    &quot;currency&quot;: &quot;USD&quot;<br>  },<br>  ...<br>}</pre><p>This may seem repetitive, but WOULD those few bytes you <strong>save</strong> actually <strong>SAVE</strong> you from the <strong>infuriated clients</strong> and <strong>developers</strong> that are currently depending on your systems? — I think not.</p><h4>Code Level Backward Compatibility</h4><p>Most times, we face a situation where we need to make certain functionality more dynamic. For example, migrating the simple function below:</p><pre>/// Combine two objects using an implementation hidden functionality<br>int combine(Object a, Object b){<br>  ...<br>}</pre><p>To one of the following</p><pre>/// Force the users to add a combineFunction or else their function won&#39;t work<br>int combine(Object a, Object b, Function combineFunction){<br>  ...<br>}</pre><pre>/// Add an optional function parameter with a DEFAULT VALUE to the parameter list<br>int combine(Object a, Object b, {Function combineFunction = add()}){<br>  ...<br>}</pre><blockquote>If it feels like you’re going to break some old things with your new implementation, try to ensure that you’ve had enough sleep. <br>— Johnpaul Muoneme</blockquote><h3><strong>Forward Compatible Thinking</strong></h3><ul><li>Forward-compatible thinking involves anticipating and preparing for future changes, updates, or advancements in technology.</li><li>It means designing your codebase with flexibility and extensibility in mind, so it can adapt to future requirements and updates without requiring significant rework.</li><li>This includes considering potential future changes in APIs, platform requirements, or business needs and designing your code to accommodate them seamlessly.</li></ul><h3>Practical Strategies</h3><p>To achieve backward compatibility and forward-compatible thinking, we would need to do the following:</p><ul><li><strong>Version Checking</strong>: Check the version of dependencies or platforms your code relies on and adjust behaviour accordingly to maintain compatibility with older versions.</li><li><strong>Feature Detection</strong>: Use feature detection techniques to determine whether certain functionality is available in the current environment and provide alternative implementations if necessary.</li><li><strong>API Design</strong>: Design clear and stable APIs that are unlikely to change frequently, but also allow for extension and evolution without breaking existing implementations.</li><li><strong>Documentation and Communication</strong>: Document any compatibility considerations, version requirements, or potential breaking changes to help users understand how to use your code effectively across different environments.</li><li><strong>Testing</strong>: Write comprehensive tests that cover various use cases and edge cases, including scenarios where backward compatibility or forward compatibility may be crucial.</li></ul><blockquote>By embracing the principle of “Backward Compatible Code | Forward Compatible Thinking,” you can build software that not only works reliably in current environments but also remains adaptable and resilient to future changes and advancements. — Johnpaul Muoneme</blockquote><p>Thank you for reading 👏🏽</p><p>Johnpaul Muoneme<br>Code Architect — 8Ball NG 🎱🇳🇬</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50b91d631724" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[User Sessions: Necessary or Stressful?]]></title>
            <link>https://medium.com/@johnpaulmuoneme/user-sessions-necessary-or-stressful-d2f85b9d0806?source=rss-77e23f9d860------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2f85b9d0806</guid>
            <category><![CDATA[session-management]]></category>
            <category><![CDATA[user-study]]></category>
            <category><![CDATA[user-access-control]]></category>
            <category><![CDATA[sessions]]></category>
            <category><![CDATA[user-experience]]></category>
            <dc:creator><![CDATA[Johnpaul Muoneme]]></dc:creator>
            <pubDate>Sun, 11 Feb 2024 16:54:28 GMT</pubDate>
            <atom:updated>2024-02-11T16:54:28.241Z</atom:updated>
            <content:encoded><![CDATA[<h3>Hey there!</h3><p>So, user sessions are basically like a passport for your interaction with a website or app. When you log in, you’re starting a session, and as long as you’re active, it keeps track of your actions and preferences. If you’re inactive for a while, it might log you out for security. It’s like having a virtual ticket to stay connected until you’re done using the platform.</p><p>So, say <strong>cheese!</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4jriV2OjQ8wuJ5IY" /><figcaption>Photo by <a href="https://unsplash.com/@stenedit?utm_source=medium&amp;utm_medium=referral">Kyryl Levenets</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>So why would users need to have passports to access your app?</h3><p>Well, it’s pretty simple: You want to know who they are so you can understand what they’re doing on your platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VS0hSOJBNB0B0cLG" /><figcaption>Photo by <a href="https://unsplash.com/@laughayette?utm_source=medium&amp;utm_medium=referral">Marten Newhall</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><strong>Personalisation:</strong> By knowing who’s using the app, you can tailor the experience to their preferences. Think about how Netflix recommends shows based on your watch history or how Amazon suggests products based on your past purchases.</p><p><strong>Security:</strong> Having users log in helps ensure that only authorised individuals are accessing sensitive information or performing certain actions within the app. It’s like having a bouncer at the door of a club to keep out unwanted guests.</p><p><strong>Analytics:</strong> Understanding who your users are and how they interact with your app can provide valuable insights for improving the user experience. You can track things like which features are most popular, where users are dropping off, and how long they’re spending in the app.</p><p><strong>Communication:</strong> User sessions allow you to communicate with users more effectively. For example, you can send targeted notifications or emails based on their behaviour or preferences, leading to higher engagement and retention rates.</p><h3><strong>So what have we learned?</strong></h3><p>User sessions might seem like a hassle, but they’re essential for creating personalised, secure, and engaging experiences for users. So next time you log in, remember that little passport is there to make your journey smoother.</p><p>Until next time!</p><p><em>Johnpaul Muoneme</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2f85b9d0806" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>