SKIP TO CONTENT
HOME/CASE STUDY

No-Code Workflow Platform · Zino

A platform that builds business apps — without code

Forms, approvals, dashboards and AI agents, assembled visually and running minutes later. Here is how the thing underneath works — and what each decision cost.

Role
Tech lead — builder & runtime
Period
2023 — Present
Stack
React 19 · Go · PostgreSQL · NATS
[01]

What it is

A multi-tenant platform where a company builds its own internal applications from settings rather than source code — workflows, forms, views, roles, triggers, AI agents — and a separate runtime serves those apps to end users.

Dealer managementInsurance pre-authInvoice processingField-service dashboards

The Studio does not generate code. That one distinction drives most of the architecture below.

[02]

The problem under every no-code platform

When a customer invents a field at runtime, it has to go somewhere. There are three classic answers, and each hurts in a different place.

EAV

one row per field value

+ Schema stays fixed

Every list query becomes a pile of self-joins

Pure JSONB

one blob per record

+ Stores any shape, honestly

Filtering, sorting and charts are slow and untyped

Real columns

DDL per tenant

+ Fast and properly typed

Unbounded schema, and DROP COLUMN on live data

Most platforms pick one and live with its weakness. This one refuses to.

[03]

Compile the design graph; never read it at runtime

Letting the runtime read the builder's tables is simple and wrong twice over: a six-to-ten table join on every request, and every builder schema change becomes a runtime schema change. Publishing is a compile step instead.

Design graph

normalized, in the Studio

compile — once, at publish

Runtime image

one document per version

load + swap atomically

In-memory graph

loaded at boot

Then: interpreted per request. No codegen, no per-request compile.

Chose

Deploy compiles the design graph into a runtime image. The runtime never reads the authoring tables.

What it costs

A deploy becomes a real event, not a save — and the compiled image can drift from the authoring model, which means repair tooling.

[04]

JSONB is the truth; the typed table is disposable

The decision I'd defend hardest, because it looks like a compromise and isn't. Because the JSONB is authoritative, the typed table can be dropped, retyped, renamed or rebuilt from nothing — the backfill replays it. That is what makes deploy-time DDL against customer data survivable rather than terrifying.

Source of truth

JSONB record

Every field the customer invented. Authoritative, never rebuilt.

derived at deployreplayable

Disposable

Typed companion table

Real indexed columns, only for fields a view or chart reads. Drop it, rebuild it.

The case that proves someone thought it through is the in-flight one: dropping a column an older still-deployed version references would corrupt running instances. So a deploy unions every active version's field set and aborts on a type conflict rather than guessing.

Chose

JSONB as source of truth, with a per-workflow typed table derived from it at deploy.

What it costs

Every mutation dual-writes, sync is best-effort so drift needs a reconciler, and a full backfill makes deploy a downtime activity by design.

[05]

Versions pinned, with a hotfix door

Instances are pinned to the version they started on, and a published version rejects structural edits. The usual answer stops there — “you can't change a published workflow” — which isn't good enough, because what most needs fixing in production isn't structural.

Frozen

States, transitions, fields — anything structural

Stays open

Permissions, triggers, prefills, rule logic — reaching live instances at the next deploy

Known gap: view configuration isn't versioned, so it can drift from the workflow version it belongs to.

[06]

What I owned

The builder — Studio frontend and backend — and the shared runtime engine, AI agents included. Design-time and runtime, not a single service. Most of the work went into the read and commit paths.

Record-view read path

Composite index, projected selects, SQL resolution hoisted out of the row loop

SQL injection in ORDER BY

Found it; fixed at the sink with an allowlist so callers can't reintroduce it

COUNT(*) OVER() split

Pagination counts were dragging the page off its index

Activity commit path

One transactional update plus a combined upsert; audit moved off the request path

Trigger snapshot serving

Removed live DB reads from the node hot path

Secret redaction

Across trigger logs and audit — a sweep, not a patch