By Bigwig |
Most final year CS students build a project that lives on a USB drive, gets presented to a panel, and is never opened again.
Mine is live. It has real businesses on it. Real transactions. Real data.
I'm a final year Networking and Software Systems student at Presbyterian University Ghana (PUG), based in the Kwahu area of the Eastern Region. While preparing for my academic defense, I independently built and deployed StockMaster Ghana — a multi-tenant SaaS POS and business management system serving small and medium businesses in my community.
This is what I learned. Not from a tutorial. From actually doing it.
First, What Is Multi-Tenancy?
Before I get into the lessons, let me explain what I mean by multi-tenant — because it took me a while to really get it.
A single-tenant app is like a house. One family lives there. Everything is built for them.
A multi-tenant app is like an apartment building. Many tenants share the same infrastructure — same roof, same plumbing, same electricity grid — but each tenant has their own private space and can't access anyone else's.
In SaaS terms: one codebase, one database, many businesses — each seeing only their own data.
Single-tenant: App A → Database A
App B → Database B (separate deployments)
Multi-tenant: App → Database
├── Tenant A's data
├── Tenant B's data
└── Tenant C's data (one deployment)
Getting this architecture right is where most beginners (myself included, at first) trip up.
The Stack I Chose and Why
- Frontend: React + Vite
- Backend/Database: Supabase (PostgreSQL)
- Auth: Supabase Auth
- Deployment: Vercel
- Payments: MTN MoMo API integration (for the Ghanaian market)
I chose this stack for one reason: I could move fast without a dedicated backend server. Supabase gave me a full PostgreSQL database, authentication, Row-Level Security, and real-time subscriptions — all without spinning up a separate API server. For a solo student developer, that's a superpower.
Lesson 1: Row-Level Security Is Not Optional
This was my biggest early mistake.
I launched the first version of StockMaster Ghana without properly enabling Row-Level Security (RLS) on all my Supabase tables. Technically, each business had a tenant_id column — but without RLS enforced at the database level, the only thing keeping Business A from seeing Business B's data was my frontend logic.
Frontend logic can be bypassed. A database policy cannot.
Here's the kind of RLS policy I ended up implementing:
-- Enable RLS on the sales table
ALTER TABLE sales ENABLE ROW LEVEL SECURITY;
-- Each tenant can only see their own sales
CREATE POLICY "Tenants see own sales"
ON sales
FOR ALL
USING (tenant_id = auth.uid());
After I enabled RLS across all 12 tables, I could sleep at night knowing that even if someone manipulated a request, the database itself would reject unauthorised access.
Lesson: Never trust the frontend to enforce data isolation. Do it at the database level.
Lesson 2: The Duplicate INSERT Bug That Almost Broke Everything
At one point, I had a live business — MMAT Plus Hub, a coated peanuts production company — actively using the system. When they recorded a sale, it was occasionally being inserted into the database twice.
Two records. One transaction. Double the stock deduction. Chaos.
The root cause? A React state update was triggering my form submission handler twice under certain conditions — a classic issue with useEffect dependencies and event handlers not being properly cleaned up.
The fix involved:
// Adding a submission lock to prevent duplicate inserts
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSale = async (saleData) => {
if (isSubmitting) return; // Guard against double submission
setIsSubmitting(true);
try {
await supabase.from('sales').insert(saleData);
} finally {
setIsSubmitting(false);
}
};
But the deeper fix was adding a Postgres trigger that automatically reconciled financial records on the production line — so even if a duplicate slipped through, the financial totals would self-correct.
Lesson: Optimistic UI is great. Idempotent database operations are essential.
Lesson 3: One 8,600-Line File Is a Time Bomb
Early in the project, I had one massive file that did everything. Components, API calls, state management, utility functions — all 8,600 lines of it.
It worked. Until it didn't.
Adding a new feature meant scrolling through thousands of lines trying to remember where something lived. Fixing one bug would break something three sections down. Code reviews (even solo ones) became painful.
I eventually did a full modular refactor — splitting the monolith into 13 named modules:
src/
├── components/
│ ├── Sales/
│ ├── Inventory/
│ ├── Reports/
│ └── Dashboard/
├── hooks/
│ ├── useSales.js
│ ├── useInventory.js
│ └── useTenantData.js
├── lib/
│ ├── supabase.js
│ └── helpers.js
└── pages/
The refactor took a painful weekend. But after it, adding new features became genuinely fun again.
Lesson: Start with structure. Refactoring a live production app is far more stressful than getting the architecture right early.
Lesson 4: Building for Your Own Community Is a Cheat Code
I'm from Kwahu. I built StockMaster Ghana for businesses in Kwahu. That context gave me advantages no amount of market research could replicate.
I knew that:
- Many SMEs here deal in cash and MoMo, not card payments
- Business owners often share devices with staff — so role-based access control mattered a lot
- Debt tracking (customers buying on credit) is a core part of how local trade works
- Seasonal patterns (Kwahu Easter festival, for example) cause major inventory spikes
These weren't features I found in a product requirements document. They came from conversations with actual users — people I knew.
When a business like MMAT Plus Hub (which processes coated peanuts for sale) needed a production line reconciliation feature, I could sit with the owner, understand the workflow, and build it in a week.
Lesson: Proximity to your users is a competitive advantage. Build for people you can actually talk to.
Lesson 5: Academic Projects and Real Products Need Different Hats
Here's something nobody tells you: building a live product while also writing an academic proposal about it creates a tension you have to manage carefully.
My department assigned me a different research topic for my final year project — a QR-Based Thesis Deposition System for PUG. That's a genuinely separate problem from StockMaster Ghana, and keeping them distinct matters for academic integrity.
The lesson I learned: your real-world experience makes you a better researcher, but your research topic needs to stand on its own merits — with its own problem statement, methodology, and contribution to knowledge.
Don't try to retroactively academise something you already built. Build something new, informed by what you've learned.
Lesson: Production experience sharpens your academic instincts. Keep the two outputs honest and distinct.
What I'd Tell My Past Self
If I could go back to when I started StockMaster Ghana, I'd say:
- Enable RLS on day one. Not after you have real users.
- Modularise early. One file feels fast until it doesn't.
- Write down every bug you fix. Those are your best blog posts, conference talks, and interview stories.
- Ship to real users as fast as possible. Feedback from a real business using your system daily is worth more than any tutorial.
- Your geography is not a limitation. Building in Kwahu, Ghana gave me a unique product with a real user base. That's more than most CS graduates can say.
What's Next
StockMaster Ghana is live and growing. My final year project (the QR-Based Thesis Deposition System) is in proposal stage. And I'm documenting all of it here on dev.to and on CyberSense Ghana's platforms.
If you're a student developer — especially one building in Africa — I'd love to connect. The problems here are real, the users are real, and the opportunity to build something that matters is very real.
Drop a comment. Let's talk.
Follow CyberSense Ghana @cybersense101 on TikTok, Instagram, and Facebook for cybersecurity awareness content built for West Africa.
Tags: #saas #webdev #beginners #programming #supabase #react #africa
Top comments (0)