// 05 · inventory
Stockhub
Multi-tenant stock management for restaurant chains and franchises.
STACK
NestJS 11 · Next.js 16 · TypeScript · PostgreSQL 18 · Prisma 6 · Redis · JWT · TailwindCSS · ShadCN UI
STATUS
private
Stockhub is a proper multi-tenant SaaS for companies that move a lot of stock between locations — restaurant chains, franchises, multi-warehouse operations. It is a rewrite from scratch on a modern stack after the v1 proved the use case.
Architecture
- Backend — NestJS 11 modular services. Prisma 6 as the ORM against PostgreSQL 18. Redis for session store and cache. JWT auth with refresh-token rotation.
- Frontend — Next.js 16 (App Router, Server Components), TypeScript strict, TailwindCSS with ShadCN UI. Axios client with interceptors for token refresh.
- Database — 26 tables in one Postgres schema; multi-tenancy enforced at the query layer with
companyIdscoping on every read/write.
Feature set
- Product catalogue — SKU + barcodes, simple and composite products (recipes), expiry dates, multiple units of measure, deep categorisation.
- Multi-warehouse — any number of locations (warehouse, restaurant, kitchen), transfers with approvals, geolocation per warehouse.
- Real-time monitoring — instant stock events, low-stock alerts, automatic reorder points, full movement history.
- Purchase orders — full approval workflow, multi-supplier, price/cost tracking, partial receptions.
- RBAC — role-based access control backed by a dedicated permission table with 43 predefined permissions. Permissions are checked per-route.
- Multi-session management — active sessions listed per user with the ability to revoke any session remotely in real time.
- Audit log — every write is recorded with who/when/what/before/after.
A slice of the auth middleware
// backend — NestJS permission guard
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(ctx: ExecutionContext): boolean {
const required = this.reflector.get<string[]>('permissions', ctx.getHandler());
if (!required) return true;
const { user } = ctx.switchToHttp().getRequest();
const userPerms = new Set(user.permissions);
return required.every((perm) => userPerms.has(perm));
}
}
// usage
@Get('products/:id')
@Permissions('inventory.products.read')
async getProduct(@Param('id') id: string) { /* ... */ }
Why a v2
The original Stockhub (Python + Jinja templates + raw WebSockets) proved the idea but painted itself into a corner on the authorization model and the front-end. V2 keeps the domain learnings and rebuilds on top of a stack that scales: typed end-to-end, modular on the server, component-based on the client.
26
DB tables
43
RBAC permissions
multi
tenant isolation