Building a Real-Time B2B Marketplace with Turborepo, Next.js, and NestJS
How we architected SmartList to achieve sub-50ms order routing, resilient WebSockets, and clean multi-tenant catalog synchronization.
Bekzod Ganiyev
When designing a B2B marketplace like SmartList, standard request-response architectures quickly encounter bottlenecks. In a high-velocity environment where multiple wholesalers update stock simultaneously and hundreds of retail buyers place orders within seconds, real-time consistency is non-negotiable.
In this article, I share the key architectural decisions we made, challenges we faced, and how we structured our Turborepo monorepo to maintain developer velocity.
1. The Monorepo Architecture with Turborepo
Managing separate repositories for customer portals, wholesaler dashboards, mobile APIs, and admin panels introduces version drift and duplicated domain models. We leveraged Turborepo to unify our codebase:
apps/
├── web/ # Next.js 15 Client App (App Router, Tailwind v4)
├── admin/ # Shadcn Admin Dashboard
└── api/ # NestJS Fastify Microservices & Realtime Gateway
packages/
├── db/ # Prisma / Drizzle Schema & Migrations
├── shared/ # Shared TypeScript DTOs, Enums, and Zod Validators
└── ui/ # Reusable Design System Components
By sharing validation schemas and types directly between the NestJS backend and Next.js frontend, we eliminated an entire class of runtime contract mismatches.
2. Real-Time Order Routing with Socket.IO and Redis
HTTP polling was completely out of the question for live order states. We implemented a dedicated WebSocket gateway using Socket.IO backed by a Redis Pub/Sub adapter:
- Tenant Isolation: Each wholesaler and buyer connects to authenticated, isolated rooms partitioned by tenant IDs.
- Heartbeat & Reconnection: Built-in backoff algorithms ensure intermittent mobile connections recover state gracefully without flooding the database.
- Eventual Consistency: Critical orders are committed to PostgreSQL inside ACID transactions first, after which atomic events are emitted to the WebSocket cluster.
@WebSocketGateway({ cors: true, namespace: '/realtime' })
export class OrderRealtimeGateway implements OnGatewayConnection {
@WebSocketServer()
server: Server;
async handleOrderCreated(@Payload() data: OrderEventDto) {
// Broadcast instantly to the assigned wholesaler room
this.server.to(`wholesaler:${data.wholesalerId}`).emit('order:new', data);
}
}
3. Database Optimization & Indexing
PostgreSQL handles our transactional data. By using composite indexes on (tenant_id, status, created_at) and leveraging connection pooling with PgBouncer, our average query response times remained well below 15ms even under simulated peak loads.
Conclusion
A well-structured monorepo combined with decoupled real-time primitives gives you the agility to ship fast while maintaining enterprise-grade resilience. The investment in strict TypeScript typing and proactive architecture pays dividends every single day.