, updated · 4 min read
Our stack and why we chose it
The stack we use across web, mobile and desktop (React, TypeScript, FastAPI, Postgres, Expo, Tauri) and why we chose each part.

Opinions, Not Trends
We picked our stack by shipping projects and keeping what held up once real users, real data and real deadlines arrived, not by following whatever was being discussed that quarter.
React and TypeScript
React is not the newest option, and that is part of the reason we use it. The ecosystem is mature, the hiring pool is deep, and the patterns are widely understood, so handing over to a client's own developers is straightforward. We have shipped Vue and Svelte, but React remains our default because it holds up across project sizes and teams.
We do not ship plain JavaScript. TypeScript catches whole categories of error before they reach production, makes refactoring safe enough to attempt, and turns code review into a discussion about design rather than about typos.
Vite and Tailwind
Vite replaced Webpack for us and we have not gone back. The dev server starts immediately, hot module replacement is reliable, and the configuration stays small. Tailwind keeps styling next to the markup it applies to, which means dead CSS does not accumulate in a file nobody wants to open.
FastAPI and Postgres
Most of what we build needs a real backend, not a thin wrapper around a hosted database. We use FastAPI on Python, with SQLAlchemy in async mode over Postgres and Alembic for migrations. Pydantic handles validation at the edge, so bad data is rejected at the boundary rather than three layers in.
Migrations run as a separate release step, never on application startup, and they stay backward compatible with the previous version for the length of a rolling deploy. That is the difference between deploying on a Tuesday afternoon and deploying at midnight.
One Type System Across the Boundary
The part we would keep if we could keep only one thing: the backend publishes an OpenAPI schema, and the frontend client is generated from it. Change a field name in Python and the TypeScript build fails before anyone opens a browser. No hand-written fetch wrappers drifting quietly out of sync with the API they call.
Expo for Mobile
When a project needs a phone app, we build it with Expo and React Native rather than starting a separate native codebase. The shared language and shared types mean the mobile app is not a fork of the product with its own bugs, and we take it through App Store and Play Store review ourselves rather than handing that back to the client.
Tauri When It Belongs on the Desktop
Some work does not belong in a browser tab. One of our platforms processes scanned documents, which is faster and more private when the processing happens on the operator's own machine. That is a Tauri app: a Rust core, a small Python sidecar for the heavy lifting, and the same web UI we would have built anyway.
AI Where It Beats a Rule
We use Claude in production, but only where a deterministic rule would be worse. Pulling structured fields out of a scanned notice is a genuinely hard parsing problem and a good use of a model. Deciding whether a subscription has lapsed is not. The distinction matters, because a model in the wrong place is an expensive way to make a system unpredictable.
Anything a model produces goes in front of a person before it reaches a customer. Extraction is automated; approval is not.
Turborepo and pnpm
Projects with shared code sit in a monorepo on pnpm workspaces with Turborepo. One component library, one set of types, one place to fix a bug rather than three. pnpm's linking keeps installs fast and disk usage sane once several apps share the same dependencies.
What We Leave Out
We do not add a state library until the server cache is doing its job and something is still missing. We do not add an ORM abstraction on top of the ORM. Every dependency is a thing to upgrade, audit and eventually remove, so it has to earn the place. The best stack decision we make on most projects is the library we decide not to install.


