Skip to content
Apps / admin apps from configurationLowcode StudioCase study

The screen is the JSON.

Search fields, columns, buttons and the API behind the rows all come from one document. Point at a key to see where it lands.

pages/order-list.jsonv3 · live
The page designer
The same document in the designer. Nothing on this screen is code written for it.

Architecture

Studio writes JSON, runtime renders it

The studio keeps metadata in MySQL and publishes JSON documents to an appdata volume served at /resources. The runtime reads only those, so published apps keep working with the studio's database down.

Fig. 1

From a designed page to a running one

Publishing is one request: the server runs the numbered steps under a per-file lock, and a stale designer gets the newer document back (dashed). The runtime resolves APIs by name, hosts from env.json, and runs designer code with globals shadowed.

Fig. 2

Publishing in one locked step

async publish({ config, logger, backup }) {return withLock(pageKey, async () => {const stored = await read(pageKey)if (stored.version > config.version)return CONFLICT(stored) // someone published firstconst next = { ...stored, ...config, version: config.version + 1, status: ONLINE }if (backup) await write(backupKey(`${Date.now()}.json`), next) // the server names itawait db.transaction(async (t) => {await PageLogger.create(logger, t); markOnline(page, app, t)await write(pageKey, next) // temp file + rename})})}

The server holds a per-file lock for the write only — pages stay unlocked while being designed. If someone published first, the newer document comes back; the file is written inside the transaction, so a failed write records nothing.

Fig. 3

The stack, layer by layer

L01Studio consoleManage apps and their inputsApps, pages, APIs, dictionaries, functions and environment variablesOne shared CRUD model and one sync view across every listBuilt on lowcode-kit: Tailwind + RadixL02DesignerThe page editorConfigurers for fields, columns, buttons, forms and CSSGenerators turn field names into columns or form itemsDrafts survive a refreshL03RuntimeDocument to working screenDispatcher resolves APIs by name and applies formattersDesigner code runs via new Function, globals shadowed — tidiness, not a sandboxWidgets, rules and converters keyed by nameL04Studio APIMetadata and publishinglowcode-server: a 480-line decorator layer on ExpressSequelize models, versioned migrations and the publish logAdmin sessions, throttled logins, rate-limited uploadsEvery published file written atomically under a per-file lock

Each band owns what sits inside it; arrows run in the direction work travels.

Fig. 4

From an endpoint to a live screen

1RegisterAdd endpoints and each system's host variable.2DesignConfigure the page on the canvas, on real or mock data.3PublishOne request: version check, backup, log, then the file — under a lock.4LoadThe runtime fetches the documents — no studio database.5RenderWidgets resolved by name; button events dispatched.

One unit of work, from entry to the point someone reads the result.