FurniForms — Configuration Engine for 3D Furniture

November 1, 2025

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

GitHub
LinkedIn