Malamal
Inventory & Order Management for Small Businesses
A multi-tenant inventory and order management system built for small businesses across five industry verticals, with role-based access and plan-gated features.
About the Project
Malamal is a multi-tenant web application for inventory, order, and stock management. Each business gets its own isolated workspace with a separate product catalog, customer list, team, and subscription.
Small business owners in retail, pharmacy, restaurants, fashion, and hardware — and the managers they invite to help run day-to-day operations. A Super Admin account governs workspace access and approvals.
Built to give small shop owners a single place to manage stock levels, process sales via a POS screen, monitor analytics, and receive alerts — without needing to switch between spreadsheets or separate tools.
What I Built
Designed and built the entire NestJS backend: module structure, multi-tenant data isolation, auth system, BullMQ email queue, cron notification jobs, subscription and quota enforcement, and Swagger API documentation.
Built the Next.js frontend including the POS page, dashboard, analytics, onboarding wizard, subscription billing UI, PWA service worker, and bilingual (English and Bengali) i18n support.
Designed the Prisma schema across seven files covering workspaces, users, products, orders, stock movements, expiry alerts, restock queues, subscriptions, and activity logs with composite indexes for multi-tenant queries.
Configured Redis-backed rate limiting and session storage, JWT access and refresh token rotation with blacklisting, CSRF protection on mutating requests, and HTTP security headers in Next.js.
What It Can Do
Niche-adaptive product forms: Pharma shows expiry date, batch number, and controlled substance flag; Fashion shows size and color variants; Hardware shows part numbers and compatibility; Restaurant shows ingredient and unit fields
Point-of-sale screen with barcode camera scanning via html5-qrcode, product search, customer lookup, cart management, tax calculation, and order submission
Subscription tier enforcement: plan quotas (max products, max orders per month, max managers) checked at write time; feature gates checked at query time; plans cached in Redis with TTL
Web Push notifications (VAPID) and email notifications for low-stock events and order status changes, controlled by per-workspace and per-user preferences
Engineering Deep Dive
Click any card to read the full technical detail.
Challenges & Trade-offs
The real decisions behind the architecture.
How to isolate tenant data without separate databases per workspace
Every domain model carries a tenantId foreign key pointing to the Workspace. All queries filter by tenantId, enforced at the service layer. Composite indexes on (tenantId, createdAt), (tenantId, status), and similar pairs make per-tenant queries efficient.
Simpler to operate than per-tenant databases, but requires disciplined filtering at the service layer. A missed tenantId filter would leak cross-tenant data.
How to handle niche-specific product fields without separate tables per business type
All possible niche fields (expiryDate, batchNumber, controlledSubstance, variantType, availableSizes, availableColors, isIngredient, unit, partNumber, compatibleWith) live on a single Product table. Which fields appear in the UI is determined by the workspace's featureFlags, not the database schema.
Keeps the schema simple and queries uniform, but the Product table has many nullable columns that most business types never use.
How to deliver low-stock and expiry alerts without a real-time event bus
NotificationService uses NestJS @Cron decorators to run an hourly job checking low-stock conditions and workspace-configured reminder times, and a daily midnight cron to refresh expiry alert severities. Individual product events also call notifyLowStockIfDue synchronously on the write path.
Avoids the complexity of a message broker, but the hourly cron means scheduled alerts may fire up to one hour after the configured workspace time.
Building a PWA that works reliably across both Android and iOS install flows
Implemented a usePWAInstall hook that listens for the beforeinstallprompt event on Android and detects iOS Safari via user agent to show manual share-sheet instructions. A custom Next.js Webpack plugin stamps the BUILD_ID into sw.js on each production build so the service worker cache version updates automatically on every deployment.
PWA install is surfaced on both platforms with platform-appropriate UX, and the service worker cache is invalidated on each new build without manual intervention.
Supporting admin impersonation without compromising the standard JWT auth flow
When a Super Admin impersonates an owner, a new token pair is issued with an impersonatingFromId claim embedded in the payload. The JwtAuthGuard reads this claim and attaches it to the session user object. A dedicated exit-impersonation endpoint restores the original Super Admin session. All impersonation events are written to the activity log.
Impersonation works within the existing cookie-based auth flow; the impersonating user's identity is always traceable in the audit trail.
Keeping subscription quota checks from adding N+1 database queries on every write
SubscriptionService.getWorkspacePlanCached fetches and caches the workspace's plan snapshot in Redis on the first call, keyed by tenantId. Subsequent quota checks within the same TTL window skip the database entirely. The cache is invalidated when a workspace changes its plan.
Quota checks add at most one Redis GET per request rather than a fresh database query on every product or order creation.
System Design
What Makes It Fast
Subscription plan data cached in Redis with a configurable TTL to avoid a database read on every quota enforcement check
Prisma schema has composite indexes on every high-traffic query pattern: (tenantId, createdAt), (tenantId, status, createdAt), (tenantId, barcode), (tenantId, expiryDate), (productId, createdAt)
Next.js configured with AVIF and WebP image formats, modular lucide-react tree-shaking via modularizeImports, and 1-year immutable cache headers on _next/static assets in production
Redis client configured with exponential backoff retry strategy, reconnect-on-error for READONLY, ECONNRESET, and ETIMEDOUT errors, and a 5-second command timeout to avoid hanging requests
Security Model
httpOnly cookie-based JWT auth with separate access and refresh tokens; access tokens blacklisted in Redis by jti on logout
CSRF protection: a non-httpOnly CSRF cookie must match the X-CSRF-Token request header on all POST, PATCH, PUT, and DELETE endpoints
checked independently by email and IP address
Password policy enforced on signup and password change via regex before bcrypt hashing with a cost factor of 12
How It Ships
Screenshots





