Skip to content
All work

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.

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

  1. Mobile app

    Expo · admin and rep portals

  2. API

    Express 5 · Zod validation

  3. Database

    PostgreSQL via Prisma

  4. 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

  1. 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.

  2. 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.

  3. 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.

Want something like this built?