$${\color{#e6e6e6}\Huge \text{Stack}\color{#ffb020}\text{F}\color{#ff9628}\text{o}\color{#ff7a2f}\text{r}\color{#ff6433}\text{g}\color{#ff4d36}\text{e}}$$
Visual technology-stack builder.
Drop technologies onto a canvas and it tells you what conflicts, what is missing,
and what connects to what.
Every stack decision is really a question about a relationship. Does this ORM speak to that database? If I take Firebase, what have I just ruled out? I have a queue and a scheduler — do I actually still need a cron service? Those answers normally live in twelve browser tabs, a whiteboard photo, and somebody's memory.
Architecture diagrams don't help, because a diagram is a drawing: it will happily let you wire Prisma to Firestore, and it will never tell you that you have users but no way to log them in. It knows where the boxes are, not what they mean.
StackForge is the other way round. Every technology is a data record with declared capabilities, needs, conflicts and exclusive slots, so the picture is derived from the rules rather than drawn by hand. Put two incompatible things on the canvas and one greys out. Add an API with no database and it rings red until you fix it. The diagram cannot show you a stack that could not exist.
Compatibility, checked as you build
- Hard conflicts grey the block out and say why — declare it once, it applies both ways
- Exclusive slots: two bundlers, two BaaS providers or two unit-test runners can't coexist
- Unmet needs ring a block red until something in the stack provides that capability
- Stack-wide rules catch gaps no single block can see: users with no auth, containers with no CI
A diagram that reads like one
- Wires are planned against every block and every other wire at once — they route around unrelated blocks instead of through them, keep a lane apart where they run together, and arc over one another where they cross
- Arrow direction is one promise everywhere: it points from the thing that depends on, reads from or acts on — to the thing it depends on. Mutual links are arrowed at both ends
- Solid lines are runtime dependencies, dashed lines referential;
1/∗marks give cardinality - Boxes appear and disappear with their contents; the layout re-columns itself to the window
Arranging it by hand
- Dragging shows a ghost of where the block will land rather than dragging the block itself — the wires hold still until you drop, instead of re-routing under the cursor
- Alignment guides while you drag: edges and centres snap to the neighbours they line up with, on each axis independently. Hold Alt to drop exactly where the cursor is
- Blocks align to their own box-mates and boxes to other boxes; Re-arrange puts everything back to the automatic layout
Finding things among 366 entries
- Filter by Build — pick "SaaS product" and every block that plays a part lights up
wearing the part it plays: n8n as
Glue, Stripe asBilling, Vitest asTests - Filter by language or ecosystem tag; either way each box shows how much of it the filter covers
- Click a block to trace it: its arrows animate, neighbours stay lit, everything else fades
- Needed / recommended panel names each missing capability, who wants it, and what would fix it
Yours, on your machine
- Per-technology console URL, username, password and notes, in a drawer beside the block
- Export and import the whole stack as JSON
- Undo on delete, with credentials and position restored intact
Requires Node 20+ and pnpm.
git clone https://github.com/e-mdj7/StackForge.git
cd StackForge
pnpm install
pnpm dev # http://localhost:5173| Script | What it does |
|---|---|
pnpm dev |
Dev server with hot reload |
pnpm build |
Type-check (tsc -b) and build to dist/ |
pnpm preview |
Serve the production build |
pnpm check |
Rule engine, catalog integrity, wire-router and snapping tests |
pnpm lint |
oxlint |
Nothing to configure — there are no environment variables and no services to stand up.
Want something to try it on? examples/stress-test.json is a deliberately overloaded
94-technology stack (231 connections, every box populated) — load it with Import.
Everything runs in your browser. There is no backend, no account, no telemetry, and the
only network request is brand icons from cdn.simpleicons.org.
Saved passwords are encrypted with AES-GCM (PBKDF2, 310k iterations) before they reach
localStorage. The passphrase is held in memory for the session and never written
anywhere, so a stolen localStorage dump is ciphertext. Lose the passphrase and the saved
password is unrecoverable — including by you.
Everything is driven by data, not code. Each technology is one JSON object in
src/catalog/<group>.json:
{
"id": "supabase",
"name": "Supabase",
"group": "backend",
"section": "Platform (BaaS)",
"icon": "supabase",
"color": "#3fcf8e",
"langs": ["sql", "ts"],
"desc": "Postgres with auth, storage, realtime and an auto-generated API the browser calls directly. Use when you want a BaaS but refuse to give up SQL.",
"provides": ["db", "db-sql", "auth", "storage", "realtime", "api", "data-api", "vector"],
"exclusive": ["baas"],
"links": [{ "to": "postgresql", "rel": "extends" }]
}An add-on that only works with one product names that product's id instead of a capability,
so Supabase Edge Functions declare "needs": ["supabase"] and no other database satisfies it.
From those fields alone, src/rules.ts derives every behaviour:
| Field | What it produces |
|---|---|
conflicts |
Hard incompatibility. Firebase lists postgresql, so selecting one greys out the other with "Firebase is not compatible with PostgreSQL". Symmetric — declare it once. |
exclusive |
Slot contention. Firebase and Supabase both claim baas; Vitest and Jest both claim test-unit. Pick one. |
needs |
Red ring + tooltip when nothing in the stack provides that capability (or is that tech, for an id), and an arrow to the provider when something does. |
links |
Typed arrows, drawn only when both ends are on the canvas. Vitest → Vite is tests; Next.js → React is extends. to may name a capability instead of a tech id: GitHub Actions → lint draws to whichever linter is picked. Optional card sets cardinality (n-1, 1-n, n-n). |
langs |
The language-family badge on the block and the language filter. |
provides |
Feeds the stack-wide rules in catalog/rules.json — users with no auth, self-hosted API with no database, containers with no CI. |
Two more catalog files shape the rest:
groups.json— the 15 boxes, their colour, their left-to-right dataflow order, and the sections inside eachbuilds.json— what you might be building. Each build lists roles (Glue,Billing,Data…) and thegroup:Sectionslots that fill them, which is what the Build filter lights up
entries.md is the plain-text plan: what is in, what is queued. To add a technology, copy
a neighbour in the relevant JSON. To add a whole box, add it to groups.json with its
sections, create <id>.json, and add two lines to src/catalog/index.ts.
pnpm check fails loudly on a misspelled section name, a link pointing at a nonexistent
id, a needs capability nothing provides, or a build glob aimed at a section that doesn't exist.
| UI | |
Types, and the test runner via --experimental-strip-types |
|
| Dev server and build | |
Styling, via @tailwindcss/vite |
|
State and localStorage persistence |
|
| Canvas, panning, minimap | |
| Linting | |
| Package manager |
No UI kit, no state library beyond Zustand, no test framework — node --test and
assert do the job.
src/
types.ts the schema every JSON file follows
rules.ts conflicts, missing pieces, arrows (+ rules.test.ts)
routing.ts orthogonal wire router: side choice,
sockets, lane separation, bridges (+ routing.test.ts)
layout.ts auto-arranges boxes and blocks
store.ts zustand + localStorage, filter predicate
crypto.ts AES-GCM for the password field
catalog/ groups.json, rules.json, builds.json,
15 technology files (+ catalog.test.ts)
components/
nodes.tsx block, group box, typed edge
panels.tsx palette, top bar, legend, drawer
examples/
stress-test.json 94 technologies, 231 connections
Adding a technology is a pull request against one JSON file. Run pnpm check first — it
catches almost every mistake you can make in the catalog. For behaviour changes, keep the
test alongside the module it covers.
Built by e-mdj7, co-authored with
Claude Code — which paired on the rule engine,
the orthogonal wire router in src/routing.ts, and the catalog schema.
MIT — do what you want with it. See LICENSE for the exact text. — e-mdj7
