The problem
Designers at furniture companies need a way to build parametric configurators for their 3D products — picking dimensions, materials, modules, finishes — without writing code. Existing form tools (Google Forms, Typeform) flatten everything into a questionnaire, but a real furniture configurator is a logic graph where one choice changes what's available downstream.
What I built
A backend "form builder" where each form is itself a configurator. The system is composed of:
- Configurators — the top-level form, with versioning, soft-delete, and JSON export/import
- Items / Item Types / Tree — hierarchical sections and fields, nested as a tree
- Attributes / Variable Types — typed fields the user fills in
- Conditions / Operations / Dependencies — the rule engine that evaluates "if material = oak AND width > 200, then hide drawer option"
- Catalog — articles, categories, folders synced from Odoo
Architecture decisions
- Prisma + Postgres over a document store — the rule engine needs strict foreign-key integrity between items, conditions, and dependencies. A relational dependency graph is far easier to reason about (and query) as rows and joins than as nested documents. Prisma's typed client and migrations also cut down on schema-drift bugs, and Postgres's JSONB columns cover the few genuinely flexible fields (like condition operands) without giving up relational guarantees everywhere else.
- Dependency chain evaluation — conditions form a DAG per configurator. On a field change, only its direct dependents (tracked via a materialized dependency-edge table) are re-evaluated; if one of those changes too, its own dependents cascade next. This avoids re-checking the entire tree on every input.
- Export/import strategy — a full configurator export serializes the tree, conditions, and variable types into versioned JSON. Import re-creates all IDs and remaps internal references inside a single transaction, so a clone is atomic: either the whole configurator lands, or none of it does.
- Redis — used for two things: caching resolved catalog/article lookups (the Odoo sync is comparatively slow), and as the BullMQ queue backend for async catalog re-sync jobs, so request-time paths stay fast.
What I'd do differently
- Materialize the dependency graph via a recursive CTE or a dedicated closure table sooner — early versions re-walked the whole tree per condition check, which hurt once configurators grew large.
- Version the rule-engine schema separately from the general configurator schema, so new condition operators don't require a full-app migration.
Tech stack
NestJS · Prisma · PostgreSQL · Redis · TypeScript · Docker · OAuth