DEV Community

Nebosa Company
Nebosa Company

Posted on

Building a no-code app builder out of small pure modules, and testing each one on its own

I'm a solo founder based in Europe, building an app builder (Appdor) that turns a spreadsheet into a small business app. This post is about how the code is organised, because that choice has mattered more than any framework decision. Appdor is a plain JavaScript single-page app on Vite, with Postgres behind it. There is no UI framework doing the heavy lifting. What does the heavy lifting is a rule I set early.

The rule: every feature starts as a pure module

A feature like "sort and filter rows" or "evaluate a formula" begins as a function (or a small set of functions) that takes data and returns data. No DOM, no network, no globals. It lives in its own folder under src/ and has a mirrored test file. The thin layer that touches the page is written afterwards, and is deliberately dull.

Two examples from the repo:

  • The formula engine (src/formula/): a tokenizer, a parser, an evaluator and a function registry, each in its own file, plus a dialect layer that translates formulas written for other spreadsheets. Because the evaluator only sees values and a registry, a test can feed it a string and assert on the result. There is also a compiler that turns a subset of formulas into SQL, so the same expression can run on the server when the data is too big to bring to the browser.
  • The views engine (src/views/): sort, filter, group and summarise over an array of records. The grid, the board and the calendar all call the same function and differ only in how they draw the result.

Why this pays off

  1. Tests are cheap, so they exist. A pure function needs no browser or mock server. The suite has thousands of tests and runs quickly because most of the code under test never touches a page.
  2. The same module runs in more than one place. Validation, for instance, runs when you edit a cell, when you submit a public form and when you import a file. One implementation means one set of bugs.
  3. The glue layer stays thin enough to read. The grid's cell editing is a good example. A small module decides how to coerce and validate what you typed; the DOM code just calls it and shows the toast.

Where it hurts (honest list)

  • One large file never got split. A single module, the main application file, grew into a tangle where about a third of the functions depend on each other in a cycle. Pure modules around it are tidy; the file itself is not, and extracting a function out of it is harder than it should be because one test suite loads parts of it as text.
  • Guard rails become part of the product. Several rules are enforced by tests that read the source: a bundle-size budget that fails the build, a check that every user-visible string has a translation key, and a check that new modules are actually imported from somewhere. They catch real mistakes, but they also fail for boring reasons, and they depend on the tree being stable when they run.
  • "Tested" is not "reachable". I once shipped a module with good tests that nothing imported. The suite was green, and the feature did not exist in the built bundle. Now there is a check that greps the output, not just the import graph.
  • Pure modules don't remove integration bugs. A date handed from one module to another in the wrong shape passes both modules' tests. You still need a few tests that go through the real glue, and a real browser run before you trust a release.

What I'd do again

Start with the data structure and the function that transforms it. Write the failing test with a hand-made input. Only then draw anything. When I broke this order, I ended up with logic buried in event handlers that I could only test by clicking.

What I'd do differently

Set a rule on day one that no file may import from a "god module", and ban "temporary" functions that hold state at module level. Both are cheaper to prevent than to remove.

If you build UI-heavy tools in plain JS, how do you keep logic out of the event handlers? I'd like to compare notes. (Appdor is at appdor.com if you're curious what these modules add up to; it's an early product from one person, with plenty of rough edges.)

Top comments (0)