G-HUB
A company operating system — CRM, omnichannel inbox, HRM and an academy on one backend.
- Year
- 2026
- Client
- G-HUB
- Role
- Lead engineer
- Duration
- Ongoing
Case study
The brief
G-HUB started as a CRM and kept refusing to stay one. Sales wanted deals and pipelines, support wanted every channel in one inbox, marketing wanted campaigns and journeys, HR wanted attendance and onboarding, and training wanted a place to put courses. Each of those is a product on its own. Building five products is how a small team dies, so the job was to build one system those five teams could live in.
The approach
One Express backend over MongoDB, one React SPA, one Flutter app — with the domain split into modules rather than into services. Today that is 215 Mongoose models behind 87 route groups, and 40 feature modules on the client: CRM, deals, omnichannel, broadcast, email journeys, HRM, attendance, academy, inventory, chatbot, ad integrations, an exec dashboard.
A modular monolith was a deliberate choice. The team is small and the domains talk to each other constantly — a deal touches a customer, a conversation, a campaign and an invoice. Splitting that into services this early would have bought distributed transactions and bought nothing else.
The harder parts
Omnichannel is where the real engineering went. Zalo, SMS, Viber, TikTok and web widgets all speak different dialects, deliver out of order, and fail differently. Every inbound message normalises into one conversation model, and every outbound one goes through a queue that knows the channel's rate limits and retry semantics. BullMQ on Redis runs that work off the request path; Socket.IO with a Redis adapter pushes results to whichever process holds the user's socket.
Permissions were the other slow burn. With this many modules, role checks scattered through controllers rot within weeks. CASL moved them into one ability definition the client and server both read, so the UI hides exactly what the API would refuse.
What I'd do differently
I'd have introduced the queue in week one instead of month three. The features that eventually needed it — broadcasts, journeys, imports, AI calls — all started as synchronous handlers, and each one had to be pulled apart later under load.
Stack
- React
- TypeScript
- Vite
- Express 5
- Mongoose 8
- MongoDB
- Redis
- BullMQ
- Socket.IO
- CASL
- Qdrant
- Flutter
- AWS S3 + SES