Build NocoDB Cloud

KINDAreplaces $15/mosaves $180/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A no-code database where the difficulty is that users design the schema at runtime: every column type change is a migration on live data, every formula is code someone typed into a cell, and every shared view is a permission decision that has to hold at the row level.

estimated effort multi-daythe files for this build are in the project pack

StorageReal tables in PostgreSQL, one per user tableSchemaUser-driven migrations with a preview and a reversible pathFormulasA parsed expression language, evaluated in a sandboxViewsFilters, sorts and field visibility as saved objects with their own permissionsRealtimeChange broadcast per table with per-row permission filtering

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server, the formula parser and the realtime layer.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why User tables are real tables, which needs a database that handles runtime schema changes without long locks.

    Get it Docker or your package manager. open ↗

    Verify A column type change on a large table completes without an unacceptable lock

  3. installfree

    Why The formula language is the security boundary, and reaching for the language's own evaluator is how this category gets compromised.

    Get it Decide the grammar and the function allow list before writing the parser.

    Verify A written grammar exists and the function list is finite

  4. have readyfree

    Why Migrations, formulas and sorting all behave differently against real data, which contains a note in a number column and a date nobody can parse.

    Get it Import a real spreadsheet you use, including the columns that have inconsistent values.

    Verify At least one column has mixed types and one has values that fail to parse

  5. decidefree

    Why Row-level rules and hidden fields have to be enforced in the query, and retrofitting that after building on client-side filtering is a rewrite of the data access layer.

    Get it Decide whether row rules exist, what a share link may expose, and whether shared views can be edited. Write it down before phase 4.

    Verify A written note states the rules and what a share token grants

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- bases(id, name, owner_id, created_at)
- tables(id, base_id, name, physical_name)
- columns(id, table_id, name, kind, options_json, physical_name, ordinal) · kind includes text, number, date, select, link and formula
- migrations(id, table_id, from_json, to_json, rows_affected, applied_at, reversible_until)
- formulas(column_id, expression, compiled_json, depends_on_column_ids) · dependencies, so a cycle is detectable
- views(id, table_id, kind, filters_json, sorts_json, hidden_column_ids, share_token_hash, share_scope)
- row_permissions(view_id, rule_json) · row-level rules, evaluated server side and never in the client
- A migration records what it changed and remains reversible for a period, because a column type change on real data is not something to do optimistically.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/nocodeMetadata and the user tables themselves.
MIGRATION_REVERSIBLE_DAYSrequired14How long previous values are kept after a type change. The undo window.
FORMULA_EVAL_STRINGSrequiredneverPresent as a constant so the answer is visible. Parsed trees only.
FORMULA_TIMEOUT_MSrequired500A formula that will not finish must not hold a request.
SHARE_TOKEN_BYTESrequired32Share links are bearer credentials. Unguessable, hashed at rest.
MAX_ROWS_PER_TABLErequired1000000Where the generic query patterns stop being adequate, made explicit rather than discovered.

The build, in order

  1. Real tables, defined at runtime

    A user-created table is a real table with real types.

    1. Not text for everything. The cost of that arrives after there is data.

      Files src/schema.ts

    2. So renaming is a metadata change rather than a migration.

      Files src/columns.ts

    3. Application validation is bypassed by every other code path.

      Files src/schema.ts

    done when · tick each as it passes
    watch out
    • Storing every value as text in a generic key-value table makes every filter a scan, every sort wrong for numbers and dates, and every constraint impossible, and by the time that hurts there is user data to migrate. Create real columns with real types, even though it means implementing migrations.
  2. Changing a column that already has data

    A type change previews its damage before doing it.

    1. Counts and examples, before anything changes.

      Files src/migrate.ts

    2. For a period. It is what makes undo real rather than aspirational.

      Files src/migrate.ts

    3. A migration that locks a table for a minute is an outage.

      Files src/migrate.ts

    done when · tick each as it passes
    watch out
    • Converting a text column to a number by dropping whatever fails to parse silently destroys the rows that had a note in them, and nobody notices until someone looks for the note. Count and show the failures first, and keep the original values so the change can be undone.
  3. Formulas, which are user-supplied code

    A formula computes correctly and cannot reach anything it should not.

    1. Never evaluate a string. This is the compromise vector for the category.

      Files src/formula/parse.tssrc/formula/parse.test.ts

    2. An allow list. Anything outside it fails at parse time.

      Files src/formula/functions.ts

    3. Not at evaluation, where it becomes a hang rather than an error.

      Files src/formula/cycles.ts

    done when · tick each as it passes
    watch out
    • Evaluating a formula by passing it to the language's own evaluator gives every user arbitrary code execution on your server, and it is the single most common way this category of product gets compromised. Parse to a restricted expression tree with a fixed function set, and never evaluate a string.
  4. Views that hide what they claim to hide

    A hidden column is absent from the response, not hidden in the client.

    1. Not in the serialiser and certainly not in the client.

      Files src/views.ts

    2. Fetching then filtering means the rows were fetched.

      Files src/permissions.ts

    3. List, single row, errors. Errors are where fields leak.

      Files tests/hidden-fields.test.ts

    done when · tick each as it passes
    watch out
    • Hiding a column in the interface while the API still returns it means anyone who opens the network tab sees the salary field you thought was hidden, and shared views make that a stranger rather than a colleague. Filter fields in the query itself, and assert in the suite that hidden fields are absent from every response shape.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more databases.