Case study 02 / 03
TajStore
- React Native
- Expo
- TypeScript
- TanStack Query
- Node.js
- Express
- Prisma
- PostgreSQL
- Docker
A mobile app and API for a wholesale distributor. Sales reps take stock on credit and pay it back with bank-transfer receipts; admins approve payments and track what every rep and supplier owes.
- Role
- Sole developer: requirements, API, mobile app and deployment
- Year
- 2026
- Team
- Solo (191 commits)
- Stack
- React Native, Expo Router, TanStack Query, Zustand, TypeScript, Node.js, Express 5, Prisma, PostgreSQL, Zod, Docker Compose, Caddy, EAS Update
Private business app, so there's no public link.

01 / 07Admin home: money owed to suppliers and the core modules
Problem
The business buys stock from suppliers in batches and hands it to field sales reps on credit. Tracking who holds which stock, who owes what, and which bank transfers really arrived was manual. Mistakes meant lost money.
Constraints
- Money is involved: balances must always add up.
- Sales reps work from Android phones out in the field.
- Updates had to reach the team without waiting on app-store releases.
- A one-person team, so the system had to be simple to deploy, back up and restore.
Architecture
Mobile app
Expo · admin and rep portals
API
Express 5 · Zod validation
Database
PostgreSQL via Prisma
VPS
Docker Compose · Caddy · backups
The app never decides prices or balances. It sends intentions (check out these items, here is a receipt), and the API prices, validates and records them in one place.
Key decisions
Trade-off 1
I chose pricing every checkout on the server, all or nothing over trusting the totals the app sends, because a stale price or a half-applied cart on one phone would quietly corrupt every balance after it.
Trade-off 2
I chose FIFO cost per supplier batch over a single average cost per product, because the owner pays and refunds suppliers batch by batch, so costs had to be traceable to the batch they came from.
Trade-off 3
I chose biometric sign-in through a revocable device token over storing the PIN on the phone, because a lost phone can be cut off from the server, and the PIN never sits on the device.
What I built
- Admin portal: suppliers and delivery batches, stock, sales-rep accounts and credit limits, payment approvals, returns, and a dashboard of money held and owed.
- Sales-rep portal: check out stock up to a credit limit, pay by uploading a transfer receipt, return unsold stock, and see a running ledger.
- Accounts: 6-digit PINs, email verification, rate-limited logins, sign-out of other devices on PIN change, and receipts served only through short-lived signed links.
- Operations: a Docker Compose stack behind Caddy on a VPS, with scripted deploys, backups, backup checks and restore.
- Releases: over-the-air updates with EAS for JavaScript changes; new APK builds only for native changes.
Result
- In use as the business's operations app (version 1.1.0 at the time of writing).
What I'd do next
- An offline queue so reps can prepare checkouts with no signal and sync when they're back online.
- Export ledgers to a spreadsheet for the business's accountant.





