Skip to content
← Back to projects

StockFlow

Stock, sales and balances for a team of sellers.

Year
2026
Status
Personal project
Stack
TypeScriptNext.js 16React 19Tailwind 4shadcn/uiSupabasePostgreSQLDocker

The problem

Tracking sales, stock and outstanding balances for several sellers by hand falls apart quickly: the stock no longer matches reality, totals depend on who does the maths, and nobody knows exactly who owes what. I wanted an application where those numbers cannot drift apart — including when someone actively tries to make them drift.

The architecture

A Next.js application that only talks to its database from the server, backed by a Supabase instance. The browser receives nothing but HTML and server actions: no key, no database address, so nothing it could query on its own.

no key sentseller sessionBrowserHTML · server actionsNext.js applicationServer rendering · containerSupabasePostgres · RLS and functions
The browser holds no key and cannot ask the database anything directly. And since authorisation lives in Postgres, the entire interface could be deleted without opening a single hole.

The decisions that matter

  1. 01

    Authorisation lives in the database, not the interface

    Every table carries its own access policies, every write function opens with a guard, and direct writes are revoked. The test I apply before adding any check: could the whole interface be deleted without opening a hole? If the answer is no, the check sits in the wrong place.

  2. 02

    No business calculation in TypeScript

    Totals, costs, margins, debts, stock checks: all computed in SQL. Amounts shown while typing are indicative only — the one that counts is recalculated by the database. Without that rule a seller could submit a total that suits them, and two implementations of the same calculation would drift apart anyway.

  3. 03

    No access key in the browser

    The environment variables carry no public prefix: they are read at runtime and never baked into the code sent to the client. Verified rather than assumed — the 36 scripts loaded by the page were inspected one by one, and the Docker image holds no secret.

  4. 04

    An access rule that called itself

    The policy protecting the profiles table calls a function that must read that very table. Read normally, the policy re-triggers inside itself and the query spins forever. The function is therefore declared to run with its owner's rights, letting it read the table without going back through the access check.

A look at it

Screenshot coming soon
Screenshot coming soon