Architecture Decisions: How We Built JiraniHub with React, TypeScript & PostgreSQL

Architecture Decisions: How We Built JiraniHub with React, TypeScript & PostgreSQL
When we started building JiraniHub, we had to make foundational architecture decisions that would affect the platform for years. Here's what we chose and why.
The Requirements
JiraniHub manages gated residential communities. That means:
Multi-tenancy: Each estate is a separate tenant with isolated data
Real-time updates: Security alerts, announcements, voting
Mobile-first: Most users access via mobile browsers
Offline capability: Internet isn't always reliable in some estates
Compliance: KRA/eTIMS integration for Kenyan tax requirements
Frontend: React 18 + TypeScript + Vite 5
Why React 18?
React 18 gives us:
Concurrent rendering: Better UX for complex dashboards
Suspense: Better loading states for slow connections
Automatic batching: Fewer re-renders = better performance on mobile
Why TypeScript?
For a multi-tenant SaaS, type safety isn't optional. When you're managing data for 26+ estates, a type error could mean showing Estate A's data to Estate B's residents. TypeScript catches these at compile time.
Why Vite 5?
Fast HMR: Sub-second hot module replacement during development
Optimized builds: Smaller bundle sizes = faster load times on mobile
Native ES modules: No bundling overhead in development
Backend: Express.js + PostgreSQL + Prisma
Why Express.js?
We evaluated NestJS, Fastify, and Koa. We chose Express because:
Mature ecosystem: Every library supports Express
Simplicity: Easy to onboard new developers
Middleware pattern: Perfect for multi-tenant request handling
Why PostgreSQL?
Row-level security: Native multi-tenancy support
JSON columns: Flexible schema for estate-specific configurations
Full-text search: Built-in search across announcements, documents
Proven at scale: Used by Instagram, Spotify, Notion
Why Prisma?
Type-safe queries: End-to-end type safety from DB to frontend
Migration system: Version-controlled schema changes
Multi-tenant aware: Easy to scope queries to specific tenants
Multi-Tenancy: Schema-Based Isolation
We chose schema-based multi-tenancy over row-based or database-based:
-- Each estate gets its own schema
CREATE SCHEMA estate_001;
CREATE SCHEMA estate_002;
-- Tables in each schema
CREATE TABLE estate_001.residents (...);
CREATE TABLE estate_002.residents (...);
Why schema-based?
Strong isolation: No risk of data leakage between estates
Easy backups: Backup/restore per estate
Scalable: Add new estates without schema changes
Performance: PostgreSQL handles thousands of schemas efficiently
Authentication: Session-Based with HttpOnly Cookies
We chose session-based auth over JWT:
app.use(session({
store: new PrismaSessionStore(prisma),
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
Why not JWT?
No token storage issues: JWTs stored in localStorage are vulnerable to XSS
Easy revocation: Delete session = immediate logout
Smaller payload: Session ID vs entire JWT in every request
Deployment: Google Cloud via Replit
We deploy on Google Cloud infrastructure via Replit:
Free tier: Perfect for pre-revenue startups
Auto-deploy: Push to GitHub = auto-deploy
Built-in CI/CD: No DevOps overhead
Global CDN: Fast load times across Africa
What We'd Do Differently
Start with tRPC instead of REST: End-to-end type safety would save us API debugging time
Use Redis for sessions: Database-backed sessions work but Redis is faster
Implement CQRS earlier: Separating reads/writes would improve dashboard performance
Conclusion
Our stack choices reflect our priorities: type safety, multi-tenancy, mobile performance, and developer experience. We're serving 26+ estates and 500+ members with this architecture, and it's holding up well.
We're building in public. Follow our journey: @jiranihub
We're BCUSHA Ltd, a Nairobi-based PropTech startup. JiraniHub serves 26+ estates and 500+ members across 6 countries. Open source (BSL 1.1). Building in public.

