Case study · № 062025Team of 4

Expencify

A full-stack expense-management platform where employees submit multi-currency, receipt-backed expenses that flow through configurable multi-step approval chains. Companies self-provision on signup, seven role types get tailored dashboards, receipts are scanned via OCR, and amounts convert to a base currency in real time.

Team4 people
My roleCo-lead · FE + rules
Roles7 types
StackReact · Express
expencify · approval flow
ClientAPIEngineDataReactVite · ZustandExpressJWT · RBAC · 7 rolesApproval enginerules · steps · actionsOCRTesseract.jsCurrencyExchangeRate APIPostgreSQLPrismaCloudinaryreceiptsreceipts
Fig. 1 · Expense management — a data-driven multi-step approval engine, Tesseract OCR receipts, and live FX over Prisma·Postgres. (I co-led the front end + the rule engine.)
01

Context

Expense reimbursement is a small process that goes wrong in expensive ways: people hand-key data off paper receipts, currencies don’t reconcile, and approval routing — who signs off, in what order, by what rule — lives in email and spreadsheets.

Expencify centralises submission, digitises receipts with OCR, normalises currencies automatically, and encodes approval policy as data-driven rules rather than tribal knowledge.

submit → approve
02

My role, honestly

This was a four-person team, and I was one of the two heaviest contributors. I built the first working front end, then owned the pieces I care most about: role-based access control across seven roles, the executive-role data model, the rule-based approval engine, global currency formatting, the reusable email templating, and a repo-wide UI/UX pass. A teammate owned the OCR core and the initial backend scaffold; I integrated against both. I’m calling that out because “I built an expense app” would be a lie — “I built the approval engine and the front end of one” is the truth.

role dashboards
03

The approval engine

The part I’m proudest of is the rules. Approval policy is modelled relationally — ApprovalRuleApprovalStepApprovalAction — so an admin can express sequential chains, percentage-based sign-off, a specific-approver auto-approve, a hybrid of those, or manager-first, each scoped by category and amount with its own priority.

An expense carries its own currentStepIndex through the chain, every action is an audit row with a comment, and the lifecycle (Pending → In Progress → Approved / Rejected) is derived from that trail rather than a status someone flips by hand.

04

Receipts & currency

Receipts upload to Cloudinary and run through Tesseract.js server-side, auto-filling merchant, amount, and date with a stored confidence score (that OCR core was a teammate’s work; I built the currency side of the same flow). Amounts are entered in any currency and converted to the company’s base via the ExchangeRate API, with country-to-currency selection wired in at signup through RestCountries — so a company is set up correctly before its first expense.

05

What I would change

The currency conversion calls an external API inline; I’d cache daily rates and convert against the cache, both for speed and to survive the API being down. And the approval rules deserve a proper builder UI — they’re powerful in the data model but still assembled more manually than an admin should have to.

Built with
React 19ViteZustandTailwindNodeExpressPrismaPostgreSQLJWTTesseract.js (OCR)CloudinaryNodemailer
← PreviousTurnUpNext →MGNREGA Portal
You found the trick.
Thanks for
looking closely.
↑ ↑ ↓ ↓ ← → ← → B A · konami