Engineering

Migrating a live database without downtime

A no-code platform does not hand you a database. It hands you a CSV per table, emailed to you when the export job finishes. Xano's own documentation describes exactly that: pick a table, choose Export CSV, wait for the email. Nothing in that file knows which booking belongs to which coach. That gap is where most migrations break, and it is why we run four stages instead of one copy.

The IDs break before anything else does

Xano, Airtable and Bubble give every row an integer that counts up. Row 1, row 2, row 3814. A Postgres schema built today usually wants uuid instead, and PostgreSQL's documentation gives the reason plainly: a uuid holds a 128-bit value defined by RFC 9562, while sequence generators are only unique within a single database. Good trade for anything that will ever run across more than one service. The catch arrives immediately. Generate fresh UUIDs and every foreign key in your CSV export now points at an integer that exists nowhere.

On the Stay World Class migration we kept both keys. Every table got a new uuid primary key and a legacy_id column holding the old Xano integer. The transform stage resolved relationships by joining on legacy_id rather than trusting row order, which is the assumption that quietly ruins these jobs. Sort a CSV differently and a naive importer hands a booking to the wrong coach without erroring once.

I would take an ugly legacy_id column in production over a clean schema I cannot audit. Months later, when somebody asks why a record looks wrong, that column is the only thing that lets you walk back to the original row in the platform you left.

Extract, transform, load, and the stage teams skip

Extraction from a no-code platform is manual and per table. Xano exports one table or view at a time and emails you when the file is ready, and imports above 5,000 records run in the background on separate infrastructure. There is no pg_dump equivalent, no schema, no constraints. You are reconstructing the shape of the data from the outside, which means the transform stage carries more weight than it would in a Postgres-to-Postgres move.

Transform is where types get pinned down, JSON blobs get normalized into real columns, and foreign keys get rewritten through the legacy_id map. Load is the least interesting part if you have done the first two. Supabase's restore guide shows the pattern: run psql with --single-transaction and ON_ERROR_STOP=1, and set session_replication_role to replica before the data file so foreign key triggers stay quiet while rows land out of order.

Then comes verify, which almost nobody runs, because the application boots after load and booting feels like success. Verify means row counts per table against the source, a query for orphaned foreign keys in every relation, and a handful of records checked by hand against the old system. On Stay World Class this stage is what caught the mismatches while both systems were still up, rather than three weeks later from a customer email.

Both systems running at once

Between two PostgreSQL databases, parallel operation is a solved problem. Logical replication gives you a publisher and a subscriber, and the subscriber behaves like any ordinary Postgres instance while it catches up. The documentation is blunt about the one rule that matters: a single subscription produces no conflicts when the application treats the subscriber as read-only, and conflicts do arise once something else writes to the same tables.

Leaving a no-code platform, you do not get that. Xano is not a publisher you can subscribe to. So parallel means either a short freeze on writes while the final delta loads, or a shim that writes to both systems during cutover and a reconciliation pass afterward. Pick the freeze if you can afford twenty minutes at 4am. It is far less code, and less code is fewer ways to corrupt the thing you are trying to preserve.

Stay World Class came off Webflow and Xano onto Next.js, NestJS and Supabase this way. Lighthouse went from 55.91 to 91 out of 100 and largest contentful paint dropped 77%, but the performance numbers were the easy half. The database move was the part that could not be redone on a second attempt, which is why it got four stages and a verification step.

When staying put is the right answer

If your data model is genuinely flat, migration is not the interesting question. A few tables with no deep relationships will move whenever you want them to, so there is no reason to move them before something else forces the issue. The same goes for an app still changing shape weekly. Migrating a schema you are about to redesign means paying the ETL cost twice.

Stay if the platform bill is small relative to a rebuild and the app is not near a limit you can name. That last part matters. The good reason to move is a specific ceiling you have already hit, whether that is a query you cannot write, a page you cannot make fast, or logic you cannot put under test. Absent one of those, the four stages above are work you can schedule later, and scheduling it later is a legitimate answer.

Sources

Is your platform outgrowing its stack?

Book a call and we'll walk through how we'd approach your platform, with an estimate grounded in a working prototype.