<?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: Michael Onoja</title>
    <description>The latest articles on DEV Community by Michael Onoja (@onoja5).</description>
    <link>https://dev.to/onoja5</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%2F1190193%2F700e68fd-6b9b-49ac-aeb0-6d19623f882a.png</url>
      <title>DEV Community: Michael Onoja</title>
      <link>https://dev.to/onoja5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onoja5"/>
    <language>en</language>
    <item>
      <title>We Built to 100K Users on $20/Month. Now We're Migrating to Azure (And I'm Terrified)</title>
      <dc:creator>Michael Onoja</dc:creator>
      <pubDate>Mon, 08 Dec 2025 14:20:41 +0000</pubDate>
      <link>https://dev.to/onoja5/we-built-to-100k-users-on-20month-now-were-migrating-to-azure-and-im-terrified-26ii</link>
      <guid>https://dev.to/onoja5/we-built-to-100k-users-on-20month-now-were-migrating-to-azure-and-im-terrified-26ii</guid>
      <description>&lt;p&gt;Three years ago, I made a bet: We could build a social commerce platform serving Nigeria on a single budget VPS. No cloud. No auto-scaling. Just me, a server, and a lot of optimization.&lt;/p&gt;

&lt;p&gt;That bet paid off. We scaled to 102,000 users, processed around 2 million monthly events, and maintained roughly 99.2% uptime.&lt;/p&gt;

&lt;p&gt;Now I'm about to reverse that bet. In Q1 2026, we're migrating everything to Azure.&lt;/p&gt;

&lt;p&gt;This post isn't about our technical wins (I cover the architecture in another article). This is about the &lt;strong&gt;mistakes&lt;/strong&gt; we made, the &lt;strong&gt;technical debt&lt;/strong&gt; I'm now paying for, and the &lt;strong&gt;fears&lt;/strong&gt; that wake me up thinking about this migration.&lt;/p&gt;

&lt;p&gt;If you're a solo founder or small team considering "should I start with cloud or build lean first?" — this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision That Started Everything
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;December 2021: The Budget Reality&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We raised about ₦15 million (roughly $10,000 USD). In Silicon Valley, that's a weekend's AWS bill. For us in Nigeria, it needed to last 18 months.&lt;/p&gt;

&lt;p&gt;We ran the numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: AWS from Day One (rough estimate)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;t3.medium instance: ~$40/month
&lt;/li&gt;
&lt;li&gt;RDS MySQL: ~$50/month
&lt;/li&gt;
&lt;li&gt;S3 + CloudFront: ~$30/month
&lt;/li&gt;
&lt;li&gt;Data transfer: ~$20/month
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: ~$140/month ≈ $2,520 over 18 months&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: Budget VPS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS hosting: $20/month
&lt;/li&gt;
&lt;li&gt;Domain: $12/year
&lt;/li&gt;
&lt;li&gt;SSL: Free (Let's Encrypt)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: ~$20/month ≈ $360 over 18 months&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Savings: roughly $2,160&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That $2,160 was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6 months of a junior developer, or
&lt;/li&gt;
&lt;li&gt;User acquisition for thousands of users, or
&lt;/li&gt;
&lt;li&gt;A few extra months of runway
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose Option B.&lt;/p&gt;

&lt;p&gt;At the time, it felt smart. Today, I'm not so sure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Got Right
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I Became a Really Good Optimizer
&lt;/h3&gt;

&lt;p&gt;When you can't throw money at problems, you learn to actually solve them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Our feed query&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Started at: ~6.4 seconds (joining 7 tables)
&lt;/li&gt;
&lt;li&gt;Optimized to: ~280 ms (denormalized + caching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I'd been on AWS, I might have just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upgraded the database instance
&lt;/li&gt;
&lt;li&gt;Added read replicas
&lt;/li&gt;
&lt;li&gt;Thrown a managed cache at it
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How MySQL query planning actually works
&lt;/li&gt;
&lt;li&gt;When denormalization makes sense
&lt;/li&gt;
&lt;li&gt;Strategic caching patterns
&lt;/li&gt;
&lt;li&gt;Proper indexing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made me a better engineer.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I Understood Every Bottleneck
&lt;/h3&gt;

&lt;p&gt;When your server crashes, you don't have a DevOps team to call. You &lt;em&gt;are&lt;/em&gt; the DevOps team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: The image upload disaster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Synchronous image processing was holding connections for 8–15 seconds. At 50K users, this killed the server.&lt;/p&gt;

&lt;p&gt;On a fully managed stack, I might have just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaled horizontally &lt;/li&gt;
&lt;li&gt;Never understood &lt;em&gt;why&lt;/em&gt; it was slow
&lt;/li&gt;
&lt;li&gt;Accumulated more inefficient code
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Async job processing
&lt;/li&gt;
&lt;li&gt;Queue management
&lt;/li&gt;
&lt;li&gt;Background workers
&lt;/li&gt;
&lt;li&gt;Resource optimization
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gave me a deep understanding of the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. We Actually Made Money
&lt;/h3&gt;

&lt;p&gt;Rough current metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue: ₦850,000/month
&lt;/li&gt;
&lt;li&gt;Infrastructure cost: ₦25,000/month
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure margin: 97%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a heavier cloud stack at this stage, those margins would likely have been much thinner.&lt;/p&gt;

&lt;p&gt;For a bootstrapped team, that difference mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Got Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I Built Technical Debt Into the Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The mistake: Everything runs on one server.&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;Single VPS:
├── Web server (Nginx)
├── Application (Laravel)
├── Database (MySQL)
├── Cache (Redis)
├── Queue workers
└── Cron jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works… until something breaks. Then &lt;em&gt;everything&lt;/em&gt; breaks.&lt;/p&gt;

&lt;p&gt;What I should have done (even on bare metal):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate the database&lt;/li&gt;
&lt;li&gt;Separate Redis&lt;/li&gt;
&lt;li&gt;Separate app and background workers&lt;/li&gt;
&lt;li&gt;At least design with separation in mind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I have to untangle this while keeping 102K users online.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I Avoided Managed Services Too Long
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The mistake: I manage everything manually.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Things I do by hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database backups (daily scripts)&lt;/li&gt;
&lt;li&gt;Security updates (manual &lt;code&gt;apt-get&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;SSL renewal (Let's Encrypt automation I maintain)&lt;/li&gt;
&lt;li&gt;Log rotation and retention&lt;/li&gt;
&lt;li&gt;Monitoring scripts and alerting bots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time spent on ops per week: &lt;strong&gt;8–12 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even on a budget, some managed pieces would have been worth it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managed backups&lt;/li&gt;
&lt;li&gt;Automated security patching&lt;/li&gt;
&lt;li&gt;Proper observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those 8–12 hours each week are expensive at CTO rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I Optimized for the Wrong Metric
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The mistake: I optimized for server cost, not business cost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting cost saved:&lt;/strong&gt; $2,160 over 18 months.&lt;/p&gt;

&lt;p&gt;What I &lt;em&gt;didn't&lt;/em&gt; do because I was babysitting infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ship a better iOS/Android experience sooner&lt;/li&gt;
&lt;li&gt;Implement marketplace v2 earlier&lt;/li&gt;
&lt;li&gt;Add real-time notifications&lt;/li&gt;
&lt;li&gt;Build proper internal analytics&lt;/li&gt;
&lt;li&gt;Focus on expansion outside our first market&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The opportunity cost is easily larger than the hosting savings.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. I Accumulated Monitoring Debt
&lt;/h3&gt;

&lt;p&gt;My monitoring started like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost/health &lt;span class="o"&gt;||&lt;/span&gt; telegram_alert &lt;span class="s2"&gt;"Server down!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It &lt;em&gt;works&lt;/em&gt;, but it only tells me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That something broke&lt;/li&gt;
&lt;li&gt;After users start to feel it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it doesn't tell me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why it broke&lt;/li&gt;
&lt;li&gt;What's slow&lt;/li&gt;
&lt;li&gt;How things are trending over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proper observability (like Application Insights or similar) would have caught a lot earlier what I currently discover from user complaints.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. I Have No Tested Disaster Recovery Plan
&lt;/h3&gt;

&lt;p&gt;I &lt;em&gt;do&lt;/em&gt; have backups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;--all-databases&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup.sql
rsync backup.sql user@backup-server:/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I hadn't done (for a long time):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular restore tests&lt;/li&gt;
&lt;li&gt;Measured recovery time&lt;/li&gt;
&lt;li&gt;Documented recovery steps&lt;/li&gt;
&lt;li&gt;Considered geographic redundancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's not a real DR strategy. That's just "I hope this file works when I need it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm Migrating to Azure in 2026
&lt;/h2&gt;

&lt;p&gt;It's not because the VPS stopped working. It's because I'm hitting ceilings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ceiling 1: Scaling Beyond ~150K Users
&lt;/h3&gt;

&lt;p&gt;Current bottlenecks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single database struggling with writes&lt;/li&gt;
&lt;li&gt;One server handling all traffic&lt;/li&gt;
&lt;li&gt;Manual, error-prone scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To reach 500K+ users, I'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database replication or a more scalable database&lt;/li&gt;
&lt;li&gt;Load balancing&lt;/li&gt;
&lt;li&gt;Multiple app instances&lt;/li&gt;
&lt;li&gt;CDN and distributed caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; build all this yourself on VPSs, but it's complex and fragile. Azure gives you managed building blocks for these patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ceiling 2: Operational Burden on One Person
&lt;/h3&gt;

&lt;p&gt;Right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm the only person who can deploy&lt;/li&gt;
&lt;li&gt;I'm the only person who can debug production&lt;/li&gt;
&lt;li&gt;Real vacations are hard&lt;/li&gt;
&lt;li&gt;3 AM alerts come to my phone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That doesn't scale.&lt;/p&gt;

&lt;p&gt;On Azure, with managed services and clearer separation, more of the team can own operations, and the platform doesn't depend on me being awake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ceiling 3: We Can Now Afford Better Infrastructure
&lt;/h3&gt;

&lt;p&gt;2021: every ₦1,000 mattered.&lt;br&gt;
2025/2026: we have recurring revenue.&lt;/p&gt;

&lt;p&gt;We can now justify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$500–$800/month on infrastructure&lt;/li&gt;
&lt;li&gt;Proper monitoring and logging&lt;/li&gt;
&lt;li&gt;Managed database/caching&lt;/li&gt;
&lt;li&gt;A lot less stress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's time to graduate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Terrifies Me About This Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fear #1: Cost Explosion
&lt;/h3&gt;

&lt;p&gt;We've all heard stories like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We migrated to cloud. First month: $300. Second month: $1,200. Third month: $4,800. Someone left a test resource running."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My nightmare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Misconfigured auto-scaling&lt;/li&gt;
&lt;li&gt;No guardrails&lt;/li&gt;
&lt;li&gt;Surprise bill that burns months of runway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigation I'm planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure budgets and cost alerts&lt;/li&gt;
&lt;li&gt;Internal hard limits on what we provision&lt;/li&gt;
&lt;li&gt;Start with minimal services&lt;/li&gt;
&lt;li&gt;Gradual scaling, not "all-in" from day one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll still be watching bills nervously at first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fear #2: Downtime During Migration
&lt;/h3&gt;

&lt;p&gt;Our users rely on the platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~102K active users&lt;/li&gt;
&lt;li&gt;500+ vendors earning income&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bad migration could literally pause people's livelihoods.&lt;/p&gt;

&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run VPS and Azure in parallel for a while&lt;/li&gt;
&lt;li&gt;Gradual traffic shift (1% → 10% → 50% → 100%)&lt;/li&gt;
&lt;li&gt;Clear rollback strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there's always that "what if we missed something?" feeling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fear #3: Over-Engineering in the Cloud
&lt;/h3&gt;

&lt;p&gt;Cloud makes it very easy to overdo things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too many databases&lt;/li&gt;
&lt;li&gt;Overcomplicated microservices&lt;/li&gt;
&lt;li&gt;Three different messaging systems&lt;/li&gt;
&lt;li&gt;"Because we can" architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we actually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Service (compute)&lt;/li&gt;
&lt;li&gt;Managed MySQL (or equivalent)&lt;/li&gt;
&lt;li&gt;Redis (caching)&lt;/li&gt;
&lt;li&gt;Blob Storage (files)&lt;/li&gt;
&lt;li&gt;CDN (static assets)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything beyond that has to prove it's necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fear #4: Vendor Lock-In
&lt;/h3&gt;

&lt;p&gt;On VPS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can move providers in a day or two&lt;/li&gt;
&lt;li&gt;Everything is relatively portable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Azure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'll be using Azure-specific services and tooling&lt;/li&gt;
&lt;li&gt;Moving away later will be harder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer standard protocols and services where possible&lt;/li&gt;
&lt;li&gt;Avoid Azure-specific APIs until they're truly needed&lt;/li&gt;
&lt;li&gt;Keep an exit strategy in mind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vendor lock-in is real, and I want to go in with open eyes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fear #5: My Optimizations Becoming Problems
&lt;/h3&gt;

&lt;p&gt;I've leaned heavily on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Denormalization&lt;/li&gt;
&lt;li&gt;Custom caching layers&lt;/li&gt;
&lt;li&gt;Manual query tuning&lt;/li&gt;
&lt;li&gt;Homegrown job orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I worry about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which of these patterns will still make sense in a managed, distributed environment&lt;/li&gt;
&lt;li&gt;Which optimizations might become unnecessary or even harmful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some will transfer directly. Some I'll need to unlearn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Plan (3 Phases, 6 Months)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Hybrid Mode (Q1 2026)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget:&lt;/strong&gt; $100–$150/month&lt;/p&gt;

&lt;p&gt;Move to Azure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure CDN (static assets)&lt;/li&gt;
&lt;li&gt;Azure Blob Storage (uploads)&lt;/li&gt;
&lt;li&gt;Application Insights (observability)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay on VPS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application server&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Background jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Goal: learn Azure on low risk, measure impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Compute Migration (Q2 2026)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget:&lt;/strong&gt; $300–$500/month&lt;/p&gt;

&lt;p&gt;Move to Azure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure App Service (application)&lt;/li&gt;
&lt;li&gt;Azure Database for MySQL&lt;/li&gt;
&lt;li&gt;Azure Cache for Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep on VPS temporarily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background jobs and some cron tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Goal: main user traffic on Azure; VPS as safety net.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Full Cloud (Q3 2026)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget:&lt;/strong&gt; $500–$800/month&lt;/p&gt;

&lt;p&gt;Move to Azure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Functions (background processing)&lt;/li&gt;
&lt;li&gt;Azure Service Bus (job queue, if needed)&lt;/li&gt;
&lt;li&gt;Remaining services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS entirely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Goal: cloud-native, managed, and much less manual ops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions I Need Answered
&lt;/h2&gt;

&lt;p&gt;If you've migrated from self-hosted to Azure (or AWS), I'd love your input:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cost Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How do you prevent cost surprises?&lt;/li&gt;
&lt;li&gt;Which Azure services tend to cause unexpected spend?&lt;/li&gt;
&lt;li&gt;What cost optimizations do you apply from day one?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Migration Approach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Did you do lift-and-shift first or re-architect immediately?&lt;/li&gt;
&lt;li&gt;What do you regret (either way)?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Downtime Risk
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How did you handle database migration and cutover?&lt;/li&gt;
&lt;li&gt;How did you validate the new stack before full switch?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. What Broke
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What worked fine on VPS / bare metal but broke in cloud?&lt;/li&gt;
&lt;li&gt;Any issues with file system assumptions, cron jobs, or long-lived connections?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. What You Wish You'd Known
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hidden costs (data transfer, logging, etc.)?&lt;/li&gt;
&lt;li&gt;Region selection surprises?&lt;/li&gt;
&lt;li&gt;Managed-service gotchas?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'm Learning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lesson 1: "Start With Cloud" vs "Start Lean" Depends
&lt;/h3&gt;

&lt;p&gt;If you're optimizing for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learning → VPS is great&lt;/li&gt;
&lt;li&gt;Speed to market → Cloud is usually better&lt;/li&gt;
&lt;li&gt;Cost at 1K users → VPS often wins&lt;/li&gt;
&lt;li&gt;Cost at 100K users → Cloud may win&lt;/li&gt;
&lt;li&gt;Your personal time → Cloud usually wins&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lesson 2: Technical Debt Is Real Debt
&lt;/h3&gt;

&lt;p&gt;I saved about $2,160 on hosting.&lt;/p&gt;

&lt;p&gt;I accumulated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual ops burden (8–12 hours/week)&lt;/li&gt;
&lt;li&gt;Delayed features&lt;/li&gt;
&lt;li&gt;Fragile architecture&lt;/li&gt;
&lt;li&gt;Migration complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For learning, I'd do it again. For business, the trade-off is less clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: Know When to Graduate
&lt;/h3&gt;

&lt;p&gt;There's a time to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap and squeeze every resource&lt;/li&gt;
&lt;li&gt;Invest in stability and scalability&lt;/li&gt;
&lt;li&gt;Professionalize your infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I bootstrapped well. I might have stayed in that phase too long.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 4: Budget Constraints Teach You
&lt;/h3&gt;

&lt;p&gt;Being forced to optimize made me better at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query optimization&lt;/li&gt;
&lt;li&gt;Resource management&lt;/li&gt;
&lt;li&gt;System design under constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't regret the VPS era. But I'm ready for the cloud era.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources Helping Me Plan
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Microsoft Learn: Azure fundamentals&lt;/li&gt;
&lt;li&gt;Azure Architecture Center&lt;/li&gt;
&lt;li&gt;Community posts, talks, and case studies&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Azure Pricing Calculator&lt;/li&gt;
&lt;li&gt;Azure Cost Management (once live)&lt;/li&gt;
&lt;li&gt;Terraform for infrastructure as code&lt;/li&gt;
&lt;li&gt;Azure DevOps / GitHub Actions for CI/CD&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Microsoft Tech Community&lt;/li&gt;
&lt;li&gt;Dev.to&lt;/li&gt;
&lt;li&gt;Local Nigerian tech ecosystem&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Dev.to: &lt;a class="mentioned-user" href="https://dev.to/onoja5"&gt;@onoja5&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>cloudmigration</category>
      <category>startup</category>
    </item>
    <item>
      <title>If you're building for emerging markets, this is for you.I'm sharing the complete architecture that took Ayema from 0 to 100K users in Nigeria, including the mistakes, the midnight server crashes, and the technical decisions that actually mattered.</title>
      <dc:creator>Michael Onoja</dc:creator>
      <pubDate>Sun, 30 Nov 2025 14:10:56 +0000</pubDate>
      <link>https://dev.to/onoja5/if-youre-building-for-emerging-markets-this-is-for-you-im-sharing-the-complete-architecture-1gc5</link>
      <guid>https://dev.to/onoja5/if-youre-building-for-emerging-markets-this-is-for-you-im-sharing-the-complete-architecture-1gc5</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l" class="crayons-story__hidden-navigation-link"&gt;Scaling to 100K Users: Architecture Lessons from Building Nigeria's Social Commerce Platform&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/onoja5" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1190193%2F700e68fd-6b9b-49ac-aeb0-6d19623f882a.png" alt="onoja5 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/onoja5" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Michael Onoja
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Michael Onoja
                
              
              &lt;div id="story-author-preview-content-3068501" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/onoja5" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1190193%2F700e68fd-6b9b-49ac-aeb0-6d19623f882a.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Michael Onoja&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 28 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l" id="article-link-3068501"&gt;
          Scaling to 100K Users: Architecture Lessons from Building Nigeria's Social Commerce Platform
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              12&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            15 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How I Fixed a Website That Wouldn’t Open on GLO (Nigeria).The Complete Guide Nobody Told Me.</title>
      <dc:creator>Michael Onoja</dc:creator>
      <pubDate>Sun, 30 Nov 2025 11:08:16 +0000</pubDate>
      <link>https://dev.to/onoja5/how-i-fixed-a-website-that-wouldnt-open-on-glo-nigeriathe-complete-guide-nobody-told-me-3b33</link>
      <guid>https://dev.to/onoja5/how-i-fixed-a-website-that-wouldnt-open-on-glo-nigeriathe-complete-guide-nobody-told-me-3b33</guid>
      <description>&lt;p&gt;If you host websites as a Nigeria, especially on &lt;strong&gt;Namecheap&lt;/strong&gt; with &lt;strong&gt;Cloudflare&lt;/strong&gt;, you may eventually face the strange problem I faced:&lt;/p&gt;




&lt;h2&gt;
  
  
  Your website loads on MTN, Airtel, 9mobile, broadband, and VPN…
&lt;/h2&gt;

&lt;p&gt;…but NOT on &lt;strong&gt;GLO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERR_CONNECTION_TIMED_OUT
Network error occurred
This site can’t be reached
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the craziest part?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cPanel works&lt;/li&gt;
&lt;li&gt;Direct server IP works&lt;/li&gt;
&lt;li&gt;VPN works&lt;/li&gt;
&lt;li&gt;DNS is correct&lt;/li&gt;
&lt;li&gt;SSL is fine&lt;/li&gt;
&lt;li&gt;Server is online&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet GLO refuses to open the domain.&lt;/p&gt;

&lt;p&gt;This is exactly what happened to me, and after hours of deep testing, I found the true cause.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;The Real Cause. Cloudflare’s Proxy + GLO Routing&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Cloudflare has two modes:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔶 &lt;strong&gt;Proxied (Orange Cloud)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Traffic passes through Cloudflare’s network → filtered → secured&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚪ &lt;strong&gt;DNS Only (Grey Cloud)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Traffic goes directly to your hosting server&lt;br&gt;
No Cloudflare filtering or acceleration&lt;/p&gt;

&lt;p&gt;Here’s the critical insight:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GLO’s network sometimes fails to reach Cloudflare’s orange-cloud proxy.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain.com -&amp;gt; 🔶 Proxied  → fails on GLO
domain.com -&amp;gt; ⚪ DNS Only → works on GLO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment I turned off proxy for the root domain and the &lt;code&gt;www&lt;/code&gt; record,&lt;br&gt;
GLO visitors could instantly access the site again.&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;The Fix (Instant, Clean, and 100% Working)&lt;/strong&gt;
&lt;/h1&gt;
&lt;h2&gt;
  
  
  1. Turn OFF Cloudflare Proxy (Orange → Grey)
&lt;/h2&gt;

&lt;p&gt;Go to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloudflare → DNS → Records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change:&lt;/p&gt;

&lt;h3&gt;
  
  
  A Record:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: yourdomain.com
Proxy: 🔶 Proxied → ⚪ DNS Only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CNAME Record:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: www
Target: yourdomain.com
Proxy: 🔶 Proxied → ⚪ DNS Only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⏱ Wait 1–3 minutes&lt;br&gt;
📶 Try again on GLO&lt;/p&gt;

&lt;p&gt;💥 &lt;strong&gt;Solved.&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  2. Make Sure cPanel, Email &amp;amp; FTP Records Are DNS-Only
&lt;/h1&gt;

&lt;p&gt;These must &lt;strong&gt;always&lt;/strong&gt; be grey:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cpanel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webmail&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;whm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cpcontacts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cpcalendars&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ftp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webdisk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Cloudflare proxies them (orange):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email fails&lt;/li&gt;
&lt;li&gt;cPanel login fails&lt;/li&gt;
&lt;li&gt;FTP fails&lt;/li&gt;
&lt;li&gt;SSL fails&lt;/li&gt;
&lt;li&gt;GLO access fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch them to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS Only (Grey Cloud)&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;How I Confirmed the Issue Was Cloudflare&lt;/strong&gt;
&lt;/h1&gt;
&lt;h3&gt;
  
  
  VPN ON → Website opens
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Direct IP → Opens
&lt;/h3&gt;
&lt;h3&gt;
  
  
  cPanel → Opens
&lt;/h3&gt;
&lt;h3&gt;
  
  
  GLO → Times out
&lt;/h3&gt;

&lt;p&gt;This proved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server is fine&lt;/li&gt;
&lt;li&gt;DNS is fine&lt;/li&gt;
&lt;li&gt;Hosting is fine&lt;/li&gt;
&lt;li&gt;SSL is fine&lt;/li&gt;
&lt;li&gt;Only Cloudflare proxy was blocking GLO routing&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;Testing After the Fix&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;On GLO network (no VPN), visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourdomain.com
https://www.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both load:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You fixed it.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;My Cloudflare Firewall Rules (Safe for Nigerian Traffic)&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Here are useful rules that protect your site WITHOUT blocking real users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Block suspicious AJAX bot requests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(http.request.uri.path contains "/includes/ajax" 
and not http.user_agent contains "Mozilla")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge high-risk countries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ip.src.country in {"CN" "PK" "RU" "TR" "VN"})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Slow down homepage bot floods
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(http.request.uri.path eq "/" and not cf.client.bot)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These reduce load without affecting Nigeria’s ISPs.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Can I Still Use Cloudflare? Yes.&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Even with DNS-only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your DNS is faster&lt;/li&gt;
&lt;li&gt;Your SSL is still managed&lt;/li&gt;
&lt;li&gt;Firewall rules still work&lt;/li&gt;
&lt;li&gt;Turnstile CAPTCHA still works&lt;/li&gt;
&lt;li&gt;Analytics still works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You only lose:&lt;br&gt;
Cloudflare WAF + CDN caching&lt;br&gt;
(which you can re-enable later with Cloudflare Pro)&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Why Not Keep Cloudflare Proxied?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;If you want Cloudflare Proxy &lt;strong&gt;and&lt;/strong&gt; Nigeria reliability:&lt;/p&gt;

&lt;h3&gt;
  
  
  → Upgrade to Cloudflare Pro
&lt;/h3&gt;

&lt;p&gt;Pro has better routing for Nigerian ISPs.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;If your site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loads on MTN / Airtel / WiFi&lt;/li&gt;
&lt;li&gt;Loads on VPN&lt;/li&gt;
&lt;li&gt;Loads on direct IP&lt;/li&gt;
&lt;li&gt;Does NOT load on GLO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn OFF Cloudflare proxy (orange → grey)&lt;/li&gt;
&lt;li&gt;Make sure &lt;code&gt;www&lt;/code&gt; is also grey&lt;/li&gt;
&lt;li&gt;Fix your cPanel/email DNS records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;This simple fix saved me hours. I hope it saves you too.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;About the Author&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;**A Senior Software Engineer, DevOps Specialist, and Co-founder of Ayema Networks.I builds and scales digital platforms, optimizes infrastructure, and publishes real-world DevOps/Cloudflare guides to help developers across Africa solve tough hosting problems quickly.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>ai</category>
      <category>cloudflarechallenge</category>
      <category>programming</category>
    </item>
    <item>
      <title>Scaling to 100K Users: Architecture Lessons from Building Nigeria's Social Commerce Platform</title>
      <dc:creator>Michael Onoja</dc:creator>
      <pubDate>Fri, 28 Nov 2025 16:18:55 +0000</pubDate>
      <link>https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l</link>
      <guid>https://dev.to/onoja5/scaling-to-100k-users-architecture-lessons-from-building-nigerias-social-commerce-platform-e1l</guid>
      <description>&lt;p&gt;&lt;strong&gt;How We Scaled Nigeria's Social Commerce Platform to 100K Users (And The Architecture That Almost Broke)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I still remember the night our server crashed at 3 AM. We'd just hit 50K users, and I was convinced we'd made a terrible architecture decision six months earlier. Turns out, I was both right and wrong.&lt;/p&gt;

&lt;p&gt;Let me tell you the real story of building Ayema, Nigeria's homegrown social commerce platform, from zero to 102,000 active users, 2 million engagement events, and actually getting vendors paid for their content and product. This isn't going to be a sanitized case study. I'm going to show you what worked, what failed spectacularly, and the specific technical decisions that made the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem We Set Out to Solve
&lt;/h2&gt;

&lt;p&gt;Here's the thing about building tech in Nigeria: the infrastructure challenges force you to make different choices than developers in Silicon Valley.&lt;/p&gt;

&lt;p&gt;Nigerian vendors, creators and small businesses were stuck using global platforms that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assumed consistent high-speed internet &lt;/li&gt;
&lt;li&gt;Optimized for desktop and mobile experiences (90% of our market is mobile)&lt;/li&gt;
&lt;li&gt;Handled payments through systems not integrated with local banks&lt;/li&gt;
&lt;li&gt;Didn't understand the social commerce culture here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed to build something different. A platform where a Kogi based fashion designer could post content, sell products, and actually get paid, all within one ecosystem that works on 4G connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Initial Architecture Decision (That Everyone Questioned)
&lt;/h2&gt;

&lt;p&gt;When we started designing Ayema's architecture in 2021, we made a controversial choice: &lt;strong&gt;I went with PHP/Laravel instead of the trendy Node.js/React full-stack everyone was using.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My co-founder literally asked me: "Are you sure? PHP?"&lt;/p&gt;

&lt;p&gt;Here's why I made that call:&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend: PHP/Laravel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Architecture:
- Laravel 8+ (now on 10)
- Modular API structure
- MySQL database
- VPS: 6GB RAM, 120GB SSD, 2.93TB bandwidth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Laravel worked for us:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mature ecosystem&lt;/strong&gt; - When you're building in Nigeria, you can't afford to debug framework issues at 2 AM. Laravel's maturity meant fewer surprises.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Eloquent ORM&lt;/strong&gt; - Managing relationships between users, posts, products, transactions, and payments would have been hell with raw SQL. Eloquent made it elegant:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Getting a user's posts with products and payment status&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'transactions.payment'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&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;p&gt;&lt;strong&gt;Built-in features we needed immediately&lt;/strong&gt; - Authentication, job queues, event broadcasting. We didn't have time to wire these together from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modular from day one&lt;/strong&gt; - We structured it so each major feature (social, marketplace, payments, content monetization) lived in its own module. When we hit scaling issues, we could optimize individual parts without refactoring everything.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Frontend: ReactJS + Blade Templates
&lt;/h3&gt;

&lt;p&gt;Yes, we're using both. And yes, it's intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReactJS&lt;/strong&gt; for the dynamic social features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time post feeds&lt;/li&gt;
&lt;li&gt;Interactive comments and reactions&lt;/li&gt;
&lt;li&gt;Marketplace product browsing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Blade templates&lt;/strong&gt; for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static pages (faster load times)&lt;/li&gt;
&lt;li&gt;SEO-critical content&lt;/li&gt;
&lt;li&gt;Admin dashboard (doesn't need SPA complexity)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the controversial part: &lt;strong&gt;we serve a hybrid.&lt;/strong&gt; Critical user paths are ReactJS for interactivity. Everything else? Server-rendered Blade templates.&lt;/p&gt;

&lt;p&gt;Why? Because a 2MB React bundle takes 30+ seconds to load on spotty 3G. A 50KB Blade-rendered page? 2 seconds.&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;// Our React entry point is lean&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Feed&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/Feed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Marketplace&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/Marketplace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Only hydrate specific islands, not the entire page&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feed-root&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="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Feed&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feed-root&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketplace-root&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="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Marketplace&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketplace-root&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This "islands of interactivity" approach kept our initial page loads under 3 seconds even on slow connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Design That Saved Us
&lt;/h2&gt;

&lt;p&gt;At 10K users, our naive database structure started showing cracks. By 30K, queries were taking 8+ seconds. Here's what we learned the hard way:&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Mistake: Over-Normalization
&lt;/h3&gt;

&lt;p&gt;My computer science professors would be proud of my original schema - perfectly normalized to 5NF. My users? They were getting timeout errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; To load a user's feed, we were joining 7 tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;post_media&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post_media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;reactions&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query took &lt;strong&gt;6.4 seconds&lt;/strong&gt; at 50K users.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Strategic Denormalization
&lt;/h3&gt;

&lt;p&gt;I know, I know. But hear me out. We added computed columns and caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add cached counts to posts table&lt;/span&gt;
&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reactions_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comments_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shares_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'last_activity_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Update these via database triggers and Laravel events&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostReacted&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reactions_count'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'feed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&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;Result? Feed queries dropped from &lt;strong&gt;6.4 seconds to 280ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The lesson: Denormalization isn't dirty when you're serving real users on real infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2AM Crisis: When 50K Users Broke Everything
&lt;/h2&gt;

&lt;p&gt;Remember that crash I mentioned? Here's what happened:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11:43 PM&lt;/strong&gt; - Everything's running smoothly&lt;br&gt;&lt;br&gt;
&lt;strong&gt;12:18 AM&lt;/strong&gt; - Response times creep up to 2 seconds&lt;br&gt;&lt;br&gt;
&lt;strong&gt;1:34 AM&lt;/strong&gt; - Database connection pool exhausted&lt;br&gt;&lt;br&gt;
&lt;strong&gt;2:47 AM&lt;/strong&gt; - Complete platform outage  &lt;/p&gt;

&lt;p&gt;I woke up to 47 WhatsApp messages and a dead server.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Root Cause
&lt;/h3&gt;

&lt;p&gt;Our image upload system was synchronous. When a user uploaded a photo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PHP received the file&lt;/li&gt;
&lt;li&gt;Validated it (held the connection)&lt;/li&gt;
&lt;li&gt;Resized it to 5 different sizes (held the connection)&lt;/li&gt;
&lt;li&gt;Uploaded to storage (held the connection)&lt;/li&gt;
&lt;li&gt;Updated database&lt;/li&gt;
&lt;li&gt;Returned response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With 50K+ users posting images simultaneously, our server ran out of worker processes. New requests just... queued. Forever.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Solution: Async Job Processing
&lt;/h3&gt;

&lt;p&gt;We moved heavy operations to Laravel queues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before - synchronous nightmare&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// This took 8-15 seconds per image&lt;/span&gt;
    &lt;span class="nv"&gt;$resized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;resizeImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'thumbnail'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'small'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'medium'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'large'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;uploadToStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After - async, returns immediately&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Store original, return immediately&lt;/span&gt;
    &lt;span class="nv"&gt;$tempPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'temp'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Process async&lt;/span&gt;
    &lt;span class="nc"&gt;ProcessImage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tempPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Image processing...'&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Upload endpoint response time: 8s → 340ms&lt;/li&gt;
&lt;li&gt;Server capacity increased 4x&lt;/li&gt;
&lt;li&gt;User experience: Instant feedback with background processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Payment Integration That Nearly Killed Our Momentum
&lt;/h2&gt;

&lt;p&gt;Building payments in Nigeria is... special. We integrated with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paystack&lt;/strong&gt; - Our primary payment gateway for card payments and bank transfers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutterwave&lt;/strong&gt; - Secondary gateway for redundancy and alternative payment methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal wallet system&lt;/strong&gt; - For peer-to-peer transfers and creator earnings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bank transfer verification&lt;/strong&gt; - Manual and automated reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge wasn't the payment providers - Paystack and Flutterwave are excellent, with solid APIs and great documentation. The challenge was building the business logic around multiple payment flows while staying compliant with Nigerian financial regulations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture Challenge
&lt;/h3&gt;

&lt;p&gt;Users could earn money from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Content monetization (people reacting to their posts)&lt;/li&gt;
&lt;li&gt;Product sales (via marketplace)&lt;/li&gt;
&lt;li&gt;Affiliate commissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And they needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Withdraw to their bank accounts (via Paystack/Flutterwave)&lt;/li&gt;
&lt;li&gt;Transfer to other Ayema users (internal wallet)&lt;/li&gt;
&lt;li&gt;Pay vendors (escrow for marketplace transactions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All while complying with SCUML (Special Control Unit against Money Laundering) regulations and maintaining accurate financial records.&lt;/p&gt;

&lt;p&gt;Here's our wallet transaction table structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wallet_transactions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'content_earning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'product_sale'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'affiliate_commission'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'withdrawal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'refund'&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fee'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// pending, processing, completed, failed&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'payment_channel'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// bank, wallet, etc&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'processed_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&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;The key insight: &lt;strong&gt;Use UUIDs for transaction IDs&lt;/strong&gt;. When reconciling with payment gateways, auto-incrementing IDs exposed how many transactions we were processing. UUIDs kept that private.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Multiple Payment Gateways
&lt;/h3&gt;

&lt;p&gt;We use both Paystack and Flutterwave for redundancy and to give users options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'paystack'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PaystackProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'flutterwave'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;FlutterwaveProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;initiateWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$bankDetails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Try primary provider first&lt;/span&gt;
        &lt;span class="nv"&gt;$provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'paystack'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$transfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Convert to kobo&lt;/span&gt;
                &lt;span class="s1"&gt;'recipient'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$bankDetails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'account_number'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'bank_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$bankDetails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'bank_code'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'reason'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ayema wallet withdrawal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'reference'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateReference&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;recordTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paystack'&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="nc"&gt;PaymentProviderException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Fallback to secondary provider&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Paystack failed, trying Flutterwave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;initiateWithFlutterwave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$bankDetails&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Verify webhook signature&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid signature'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Handle different event types&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'event'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'charge.success'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handleSuccessfulPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'transfer.success'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handleSuccessfulWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'transfer.failed'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handleFailedWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Paystack-Signature'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
                  &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'verif-hash'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'paystack'&lt;/span&gt; 
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.paystack.secret_key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.flutterwave.secret_hash'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$computedSignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha512'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$computedSignature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$signature&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;&lt;strong&gt;Important lessons from payment integration:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always verify webhook signatures&lt;/strong&gt; - We had an incident where someone sent fake webhook calls trying to credit accounts. Signature verification saved us.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Idempotency is critical&lt;/strong&gt; - Payment gateways sometimes send duplicate webhooks. Use unique references:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handleSuccessfulPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if already processed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WalletTransaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$reference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Duplicate webhook received'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$reference&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Process payment...&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;Handle failures gracefully&lt;/strong&gt; - Sometimes withdrawals fail after money is debited. We implemented automatic refunds:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handleFailedWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WalletTransaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reference'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$reference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transaction&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$transaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'processing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Refund to wallet&lt;/span&gt;
        &lt;span class="nc"&gt;WalletService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$transaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$transaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'refund'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"refund:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$reference&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$transaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Notify user&lt;/span&gt;
        &lt;span class="nv"&gt;$transaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WithdrawalFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transaction&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bank account verification before withdrawal&lt;/strong&gt; - Both Paystack and Flutterwave provide APIs to verify account details. We call this before processing withdrawals to prevent sending money to wrong accounts:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;verifyBankAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$bankCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withHeaders&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'Authorization'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.paystack.secret_key'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.paystack.co/bank/resolve'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'account_number'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'bank_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$bankCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;successful&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'valid'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'account_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.account_name'&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'valid'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&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;This prevents the frustrating experience of users withdrawing to wrong account numbers and losing money.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Double-Entry Bookkeeping Pattern
&lt;/h3&gt;

&lt;p&gt;To ensure money never vanished, we implemented double-entry bookkeeping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WalletService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fromUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$toUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fromUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$toUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Debit sender&lt;/span&gt;
            &lt;span class="nc"&gt;WalletTransaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fromUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'reference'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateReference&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="c1"&gt;// Credit recipient&lt;/span&gt;
            &lt;span class="nc"&gt;WalletTransaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$toUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'reference'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateReference&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="c1"&gt;// Update cached balances&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fromUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$toUser&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern saved us during an incident where a payment gateway sent duplicate callbacks. Because we checked for unique references, no double-payments occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing for Nigerian Internet Reality
&lt;/h2&gt;

&lt;p&gt;The biggest lesson: &lt;strong&gt;You cannot build for Nigerian users the same way you build for US/European users.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Optimization Pipeline
&lt;/h3&gt;

&lt;p&gt;Original images from users' phones were 3-8MB. Loading a feed of 20 posts meant 60-160MB of data. On a 500MB/month plan (common in Nigeria), that's... not going to work.&lt;/p&gt;

&lt;p&gt;Our solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageOptimizationPipeline&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;through&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nc"&gt;RemoveEXIFData&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Remove location/camera data&lt;/span&gt;
                &lt;span class="nc"&gt;ResizeToMax1080&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Cap maximum dimension&lt;/span&gt;
                &lt;span class="nc"&gt;ConvertToWebP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// 30% smaller than JPEG&lt;/span&gt;
                &lt;span class="nc"&gt;CompressTo75Quality&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Sweet spot for size/quality&lt;/span&gt;
                &lt;span class="nc"&gt;GenerateThumbnail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Instant previews&lt;/span&gt;
                &lt;span class="nc"&gt;UploadToCDN&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Serve from nearest edge&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;thenReturn&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;&lt;strong&gt;Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average image size: 3.2MB → 180KB (94% reduction)&lt;/li&gt;
&lt;li&gt;Feed load on 3G: 45s → 4.2s&lt;/li&gt;
&lt;li&gt;User complaints about data usage: 89% reduction&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Response Compression
&lt;/h3&gt;

&lt;p&gt;We implemented aggressive response compression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Middleware for API responses&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectsJson&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Minify JSON&lt;/span&gt;
        &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="no"&gt;JSON_UNESCAPED_SLASHES&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;JSON_UNESCAPED_UNICODE&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Gzip if client supports it&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Accept-Encoding'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'gzip'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gzencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Encoding'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gzip'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&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;API payloads dropped from ~120KB to ~22KB average.&lt;/p&gt;

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

&lt;p&gt;Here's our actual production setup:&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider: Custom VPS 
OS: AlmaLinux 8
RAM: 12GB
Storage: 120GB SSD
Bandwidth: 2.93TB/month
Virtualization: KVM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why not AWS/Google Cloud?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simple math:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS t3.medium equivalent: $35-40/month + bandwidth costs&lt;/li&gt;
&lt;li&gt;Our VPS: $25/month, all-inclusive&lt;/li&gt;
&lt;li&gt;Savings over 2 years: $360+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At our stage, that money went into user acquisition instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Optimization
&lt;/h3&gt;

&lt;p&gt;MySQL configuration tuned for our workload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="c"&gt;# Connection pool
&lt;/span&gt;&lt;span class="py"&gt;max_connections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;200&lt;/span&gt;
&lt;span class="py"&gt;thread_cache_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;16&lt;/span&gt;

&lt;span class="c"&gt;# Buffer pool (4GB on 12GB server)
&lt;/span&gt;&lt;span class="py"&gt;innodb_buffer_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;4G&lt;/span&gt;
&lt;span class="py"&gt;innodb_log_file_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;

&lt;span class="c"&gt;# Query cache for repeated queries
&lt;/span&gt;&lt;span class="py"&gt;query_cache_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;query_cache_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;

&lt;span class="c"&gt;# Temp tables
&lt;/span&gt;&lt;span class="py"&gt;tmp_table_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;64M&lt;/span&gt;
&lt;span class="py"&gt;max_heap_table_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;64M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combined with proper indexing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Critical indexes for feed queries&lt;/span&gt;
&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'last_activity_at'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reactions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'post_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'post_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// Prevent duplicate reactions&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Caching Strategy
&lt;/h3&gt;

&lt;p&gt;Three-tier caching:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application cache (Redis)&lt;/strong&gt; - User sessions, frequently accessed data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query cache&lt;/strong&gt; - MySQL query cache for repeated queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cache (Cloudflare)&lt;/strong&gt; - Static assets and API responses with low mutation rates
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: Caching user profile data&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"profile:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user.profile.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'followers'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&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;span class="c1"&gt;// Invalidate on profile update&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"profile:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&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;h2&gt;
  
  
  Security &amp;amp; Compliance (The Boring But Critical Stuff)
&lt;/h2&gt;

&lt;p&gt;Building a platform handling payments means you can't skip security. Here's what we implemented:&lt;/p&gt;

&lt;h3&gt;
  
  
  API Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rate limiting per user&lt;/span&gt;
&lt;span class="nc"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Encrypted API endpoints&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'auth:sanctum'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'throttle:api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'verify.signature'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/transactions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TransactionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'create'&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;h3&gt;
  
  
  Data Protection (NITDA Compliance)
&lt;/h3&gt;

&lt;p&gt;Nigeria's NITDA requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User consent for data collection&lt;/li&gt;
&lt;li&gt;Right to data deletion&lt;/li&gt;
&lt;li&gt;Secure storage of personal information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDataExportJob&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'profile'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;only&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'phone'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="s1"&gt;'posts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;products&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'transactions'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'reactions'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"exports/user-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;".json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_PRETTY_PRINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Send download link to user&lt;/span&gt;
        &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataExportReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportPath&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;h3&gt;
  
  
  SSL &amp;amp; Encryption
&lt;/h3&gt;

&lt;p&gt;All data in transit encrypted via SSL certificates (Let's Encrypt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Auto-renewal script&lt;/span&gt;
certbot renew &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="nt"&gt;--agree-tos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database credentials encrypted using Laravel's built-in encryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'database.connections.mysql.password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PASSWORD_ENCRYPTED'&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;h2&gt;
  
  
  Monitoring &amp;amp; Debugging at Scale
&lt;/h2&gt;

&lt;p&gt;At 100K users, you can't manually check if things are working. We automated everything:&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Custom monitoring middleware&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerformanceMonitor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Log slow requests&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$duration&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Slow request detected'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'duration'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'ip'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ip&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;span class="c1"&gt;// Track metrics&lt;/span&gt;
        &lt;span class="nc"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api.requests'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'endpoint'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nc"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api.response_time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'endpoint'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&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;h3&gt;
  
  
  Error Tracking
&lt;/h3&gt;

&lt;p&gt;Every exception gets logged with context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Exception&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'exception'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFile&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="s1"&gt;'line'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLine&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="s1"&gt;'trace'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTraceAsString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="s1"&gt;'input'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;except&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'password'&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;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&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;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated Alerts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Send Telegram alert for critical issues&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;DatabaseConnectionException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.telegram.org/bot'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TELEGRAM_BOT_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/sendMessage'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'chat_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TELEGRAM_ADMIN_CHAT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"🚨 DATABASE DOWN&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
                  &lt;span class="s2"&gt;"Time: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
                  &lt;span class="s2"&gt;"Error: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&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;h2&gt;
  
  
  The Features That Drove Growth
&lt;/h2&gt;

&lt;p&gt;Technical architecture alone doesn't get you 100K users. Here's what actually drove adoption:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Content Monetization That Actually Works
&lt;/h3&gt;

&lt;p&gt;Unlike global platforms where only mega-influencers make money, we enabled monetization from day one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// User earns when someone reacts to their post&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReactionController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$reaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Reaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'post_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// like, love, wow, etc&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Creator earns ₦1 per reaction&lt;/span&gt;
        &lt;span class="nc"&gt;WalletService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'content_earning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"reaction:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$reaction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Notify creator&lt;/span&gt;
        &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EarnedFromContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$reaction&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&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;This simple feature drove engagement because creators could actually see real money from their content.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Free Internet Initiative
&lt;/h3&gt;

&lt;p&gt;This was my idea: Partner with institutions to provide free Wi-Fi powered by Starlink, branded with Ayema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We pay for Starlink hardware and subscription&lt;/li&gt;
&lt;li&gt;Institution provides physical space&lt;/li&gt;
&lt;li&gt;Users connect through Ayema-branded portal&lt;/li&gt;
&lt;li&gt;They discover the platform naturally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Results at Ahmadu Bello University, Zaria:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8,000+ students connected&lt;/li&gt;
&lt;li&gt;2,400 signed up for Ayema&lt;/li&gt;
&lt;li&gt;30% conversion rate (insane for organic growth)&lt;/li&gt;
&lt;li&gt;Cost per acquisition: ₦830 (vs ₦2,500 for paid ads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The technical implementation was straightforward - captive portal redirecting to our registration:&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;// Captive portal detection and redirect&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;captive.ayema.ng&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="c1"&gt;// User is on WiFi login page&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&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;mac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;mac&lt;/span&gt;&lt;span class="dl"&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;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;ap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Track unique device&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;/api/wifi/connect&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abu-zaria&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="c1"&gt;// Redirect to signup with pre-filled referral&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/register?source=wifi-abu&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Integrated Marketplace with Real Shipping
&lt;/h3&gt;

&lt;p&gt;Nigerian SMEs couldn't easily sell online. Most platforms either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required technical skills to set up&lt;/li&gt;
&lt;li&gt;Charged fees&lt;/li&gt;
&lt;li&gt;Didn't integrate local logistics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built it directly into the social feed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// User posts a product, it appears as a feed item&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductPost&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toFeedItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'product'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'images'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'in_stock'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'shipping_available'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ships_nationwide&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'created_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'reactions_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;reactions_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'comments_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments_count&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sellers could post a product like they post regular content. Buyers could purchase directly in the feed. No context switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Metrics That Matter
&lt;/h2&gt;

&lt;p&gt;After 12 months, here's where we stand:&lt;/p&gt;

&lt;h3&gt;
  
  
  User Engagement
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;102,000 active users&lt;/strong&gt; (users who opened the app in last 30 days)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 million engagement events&lt;/strong&gt; (posts, reactions, comments, purchases)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;695,000 page views&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Average session duration: 8.4 minutes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daily active users: 31,000 (30% DAU/MAU ratio)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Platform Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;99.2% uptime&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Average API response time: 340ms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image load time: 1.8s average&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database query time: 180ms average&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Commercial Success
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;₦25 million in total funding received&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;500+ active vendors&lt;/strong&gt; on marketplace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4.8★ rating on Google Play Store&lt;/strong&gt; (1,000+ downloads)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AdSense approved&lt;/strong&gt; (earning USD from international ads)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Infrastructure Efficiency
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost per user: ₦180/month&lt;/strong&gt; (including hosting, bandwidth, storage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server costs: ₦25,000/month&lt;/strong&gt; ($25 USD)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN costs: ₦8,000/month&lt;/strong&gt; (Cloudflare free tier + paid features)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;Building with hindsight is easy, but here are genuine mistakes I'd fix:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with Proper Testing Earlier
&lt;/h3&gt;

&lt;p&gt;We didn't write tests until we had 20K users. Big mistake. When we finally added tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @test */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user_can_withdraw_funds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'wallet_balance'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/withdrawals'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'bank_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'058'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'account_number'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0123456789'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wallet_balance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wallet_transactions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'withdrawal'&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test would have caught a bug where failed withdrawals still deducted money from user wallets. We only found it after 12 users reported it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Implement Feature Flags from Day One
&lt;/h3&gt;

&lt;p&gt;Rolling back a broken feature meant deploying code. With feature flags, we could toggle features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Should've used this pattern from the start&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Feature&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'marketplace-v2'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'marketplace.v2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'marketplace.v1'&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;Would have saved us during the "marketplace redesign that broke everything" incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Think About Mobile Data Costs Earlier
&lt;/h3&gt;

&lt;p&gt;We optimized for data after launching. Should have been core from day one. Every feature should answer: "How much data does this use?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for Builders in Emerging Markets
&lt;/h2&gt;

&lt;p&gt;If you're building for Nigeria, or similar markets:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Internet Assumptions
&lt;/h3&gt;

&lt;p&gt;Don't assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✗ Consistent broadband connections&lt;/li&gt;
&lt;li&gt;✗ Unlimited data plans&lt;/li&gt;
&lt;li&gt;✗ Users on latest devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, design for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Intermittent 3G/4G&lt;/li&gt;
&lt;li&gt;✓ 500MB - 2GB monthly data budgets&lt;/li&gt;
&lt;li&gt;✓ Android devices from 2018-2020&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Payment Integration
&lt;/h3&gt;

&lt;p&gt;Don't assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✗ Credit card prevalence&lt;/li&gt;
&lt;li&gt;✗ Simple payment flows&lt;/li&gt;
&lt;li&gt;✗ Instant settlements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, design for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Bank transfers as primary method&lt;/li&gt;
&lt;li&gt;✓ Manual reconciliation processes&lt;/li&gt;
&lt;li&gt;✓ 24-48 hour settlement delays&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. User Behavior
&lt;/h3&gt;

&lt;p&gt;Don't assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✗ Users read instructions&lt;/li&gt;
&lt;li&gt;✗ Users understand technical jargon&lt;/li&gt;
&lt;li&gt;✗ Users want complex features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, design for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Intuitive, icon-based interfaces&lt;/li&gt;
&lt;li&gt;✓ Familiar patterns from WhatsApp/Facebook&lt;/li&gt;
&lt;li&gt;✓ Simple flows with clear outcomes&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;For those who want the TL;DR:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;PHP 8.1 / Laravel 10&lt;/li&gt;
&lt;li&gt;MySQL 8.0&lt;/li&gt;
&lt;li&gt;Redis for caching and queues&lt;/li&gt;
&lt;li&gt;Laravel Sanctum for API authentication&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;ReactJS 18 for interactive components&lt;/li&gt;
&lt;li&gt;Blade templates for static content&lt;/li&gt;
&lt;li&gt;Tailwind CSS for styling&lt;/li&gt;
&lt;li&gt;Axios for API calls&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;VPS (AlmaLinux 8, 12GB RAM, 120GB SSD)&lt;/li&gt;
&lt;li&gt;Cloudflare for CDN and DNS&lt;/li&gt;
&lt;li&gt;Let's Encrypt for SSL&lt;/li&gt;
&lt;li&gt;Starlink for initiative WiFi&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Git for version control&lt;/li&gt;
&lt;li&gt;GitHub Actions for CI/CD&lt;/li&gt;
&lt;li&gt;Laravel Forge for server management&lt;/li&gt;
&lt;li&gt;Daily automated backups&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Custom logging to files + database&lt;/li&gt;
&lt;li&gt;Telegram for critical alerts&lt;/li&gt;
&lt;li&gt;Google Analytics for user behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open Sourcing Components
&lt;/h2&gt;

&lt;p&gt;I'm working on open-sourcing parts of our stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Image optimization pipeline&lt;/strong&gt; - The entire flow from upload to CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wallet system&lt;/strong&gt; - Double-entry bookkeeping for Laravel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed algorithm&lt;/strong&gt; - Our approach to relevance ranking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first PWA utilities&lt;/strong&gt; - For handling intermittent connections&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow me here on Dev.to and &lt;a href="https://github.com/onoja5" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; for updates.&lt;/p&gt;

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

&lt;p&gt;Our roadmap for the next 12 months:&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Migrate to microservices (social, marketplace, payments as separate services)&lt;/li&gt;
&lt;li&gt;[ ] Implement GraphQL for more efficient mobile data usage&lt;/li&gt;
&lt;li&gt;[ ] Build native iOS app (currently using web wrapper)&lt;/li&gt;
&lt;li&gt;[ ] Add offline-first capabilities (service workers + IndexedDB)&lt;/li&gt;
&lt;li&gt;[ ] Real-time notifications via WebSockets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Launch Ayema Pay (standalone wallet app)&lt;/li&gt;
&lt;li&gt;[ ] Ayema Ride (ride-hailing integration)&lt;/li&gt;
&lt;li&gt;[ ] Digital Library (textbooks and educational content)&lt;/li&gt;
&lt;li&gt;[ ] Bill payments (airtime, data, electricity)&lt;/li&gt;
&lt;li&gt;[ ] Expand to Ghana, Kenya, South Africa&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scale
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Target: 500,000 active users by Q4 2026&lt;/li&gt;
&lt;li&gt;[ ] 10,000+ active vendors&lt;/li&gt;
&lt;li&gt;[ ] ₦100 million annual revenue&lt;/li&gt;
&lt;li&gt;[ ] Partner with 50+ institutions for WiFi initiative&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building Ayema taught me that great architecture isn't about using the latest tech. It's about understanding your users' reality and making technical decisions that serve them.&lt;/p&gt;

&lt;p&gt;We didn't use Kubernetes, or microservices, or serverless functions. We used boring, proven technology and focused on solving real problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow internet? Optimize everything for data usage&lt;/li&gt;
&lt;li&gt;Payment complexity? Build simple flows with clear feedback&lt;/li&gt;
&lt;li&gt;Limited reach? Partner with institutions for physical access points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building something similar, my advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start simple&lt;/strong&gt; - Monolith &amp;gt; microservices until you know your bottlenecks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure everything&lt;/strong&gt; - You can't optimize what you don't measure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk to users&lt;/strong&gt; - Your assumptions about their needs are probably wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy fast&lt;/strong&gt; - Perfect code that ships in 6 months loses to good code that ships in 2 weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize for iteration speed&lt;/strong&gt; - Ability to fix things quickly &amp;gt; having no bugs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let's Connect
&lt;/h2&gt;

&lt;p&gt;I'm always happy to talk architecture, scaling challenges, or building for emerging markets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Twitter/X:&lt;/strong&gt; &lt;a href="https://twitter.com/onoja55" rel="noopener noreferrer"&gt;@michaelokonja&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://linkedin.com/in/onoja" rel="noopener noreferrer"&gt;Michael Okpotu Onoja&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:michael.onoja@ayema.ng"&gt;michael.onoja@ayema.ng&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try Ayema:&lt;/strong&gt; &lt;a href="https://ayema.ng" rel="noopener noreferrer"&gt;https://ayema.ng&lt;/a&gt;, &lt;a href="https://play.google.com/store/apps/details?id=ayema.social.media" rel="noopener noreferrer"&gt;Download on Google Play&lt;/a&gt; | &lt;a href="https://apps.apple.com/us/app/ayema/id6746725342" rel="noopener noreferrer"&gt;Download on App Store&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions about our architecture? Drop them in the comments. I'll answer every single one.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #webdev #architecture #scaling #laravel #php #reactjs #startup #africa #socialmedia #ecommerce&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
