Migration eines AppSheet CRM zur Express + relationalen Datenbank
Ersatz eines an Grenzen stoßenden AppSheet-CRMs durch ein typisiertes Express-Backend, relationale Datenbank und React-UI — mit 100% verifiziertem Datensatzabgleich.
Kundenprojekt · anonymisiert
Ein kleines Vertriebsteam ist über AppSheet hinausgewachsen: langsame Synchronisierungen, keine wirkliche relationale Integrität, keine Integration in die Abrechnung und schlechte Preisskalierung pro Benutzer. Sie brauchten den Besitz ihrer Daten und eine API, die andere Tools aufrufen konnten.
TL;DR
| Aspekt | Vorher (AppSheet) | Nachher (Express + Datenbank) |
|---|---|---|
| Datenmodell | Flache Google Sheets, Kundenname auf 5 Registerkarten dupliziert | Normalisierte relationale Tabellen (customers ↔ contacts ↔ deals) |
| Integrität | Keine – drei Schreibweisen desselben Kunden | Fremdschlüssel, UNIQUE-, CHECK-Einschränkungen |
| Integration | Google Apps Script-Hacks, kopieren und in die Abrechnung einfügen | REST-API + ausgehende Webhooks |
| Latenz synchronisieren | 20–40 Sekunden Hin- und Rückfahrt, schlimmer unter Last | Lokale Abfragen, <100 ms |
| Kostenskalierung | Pro AppSheet-Benutzer (~5–10 $/Sitzplatz/Monat, gestiegen) | Flat-Hosting (~20 $/Monat) |
| Audit-Trail | „Zuletzt bearbeitet von“ in einer Zelle | Nur anhängendes Ereignisprotokoll |
Sie lernen die konkreten Anzeichen dafür kennen, dass ein Team dem Ziel AppSheet entwachsen ist Architektur, auf die wir migrieren, und das fünfstufige Muster für die sichere Migration (relationale Neumodellierung → Validierung → idempotentes ETL → Parallellauf → Umstellung) das verschiebt ein Unternehmen aus AppSheet, ohne einen Datensatz zu verlieren oder das Team einzufrieren.
Das Problem
AppSheet ist ein hervorragendes Tool für die erste Version einer Felddaten-App: point it Ziehen Sie in einem Google Sheet einige Formulare zusammen und senden Sie sie an einem Nachmittag an Mobiltelefone. Es verbirgt die Datenbank. Das ist seine Stärke und letztendlich seine Obergrenze.
Das Team in diesem Fall – acht Vertriebs- und Betriebsmitarbeiter in zwei Zeitzonen – hatte lebte zwei Jahre lang in einem AppSheet CRM. Als sie mich anriefen, war das Werkzeug da Habe sie von 0→1 bekommen, habe sie jede Woche aktiv gekostet:
- Synchronisierungslatenz dominierte den Arbeitstag. Jede Bearbeitung führte zu einem Roundtrip zum zugrunde liegendes Google Sheet. Bei ca. 50.000 Zeilen dauerte die Synchronisierung 20–40 Sekunden und länger unter gleichzeitigen Bearbeitungen. Das Team hatte gelernt, Änderungen stapelweise durchzuführen, um Verzögerungen zu vermeiden.
- Es gab keine relationale Integrität. Unter der App war es immer noch eine Wohnung Blatt, sodass ein Kundenname, der auf drei Arten eingegeben wurde, drei „Kunden“ ergab. Die Berichterstattung war dauerhaft verdächtig.
- Es konnte nicht mit der Abrechnung kommuniziert werden. Finance hat die Geschäftsdaten in ein separates Verzeichnis kopiert System jede Woche, da AppSheet keine API hatte, die ihre Tools aufrufen konnten.
- Preise skalieren nach Mitarbeiterzahl. Das Pro-Benutzer-Modell von AppSheet bedeutete jeden neuen Hire hat eine Werbebuchung hinzugefügt, liefert aber keine weiteren Funktionen.
Sie brauchten den Besitz ihrer Daten und eine API, die andere Systeme aufrufen konnten – ohne Verlust von Aufzeichnungen aus zwei Jahren oder Einfrieren des Geschäfts für einen Monat. Das ist das Form fast jedes AppSheet-Exit-Projekts: kein Umschreiben um seiner selbst willen, aber Eine durch eine Mauer erzwungene Migration, für deren Bewältigung das Tool nie gedacht war.
Die allgemeine Version dieser Geschichte (im Großen und Ganzen Tabellenkalkulationen) finden Sie unter Wenn Google Sheets die Skalierung stoppt.
Anzeichen dafür, dass Sie AppSheet entwachsen sind
Wenn Sie überlegen, ob Sie bleiben oder migrieren sollen, sind dies die Signale, nach denen ich suche. Drei oder mehr bedeuten, dass die Kosten für den Aufenthalt bereits die Kosten für die Migration übersteigen.
1. Die Synchronisierungslatenz ist der Rhythmus des Arbeitstages
AppSheet synchronisiert jede Änderung zurück mit dem Sicherungsspeicher. Wenn der Laden wächst und Gleichzeitige Benutzer nehmen zu, Synchronisierungen reichen von „sofort“ bis „warten darauf“. Wenn die Wenn das Team rund um die Synchronisierungszeit mit der Planung seiner Änderungen beginnt, bekämpft das Tool sie.
2. Du täuschst Beziehungen durch Dereferenzierung vor
Die [_thisrow]/Dereferenzierungsausdrücke von AppSheet simulieren Beziehungen darüber
flache blätter. Sie funktionieren, bis sie es nicht mehr tun – verwaiste Zeilen, kaputte Referenzen danach
Eine Umbenennung führt zu Berichten, die stillschweigend die falschen Zeilen zurückgeben. Wenn Sie eine 15-zeilige geschrieben haben
Wenn ein Ausdruck einen JOIN emuliert, verlangt die Datenbankschicht, dass er real ist.
3. Die Preisgestaltung pro Sitzplatz ist die Werbebuchung, die den Leuten auffällt
Der No-Code-Preis pro Benutzer ist bei fünf Benutzern fair und bei zwanzig Benutzern schmerzhaft. Wenn die Wenn die monatliche Rechnung ein wiederkehrendes Gespräch ist, sind Sie an der wirtschaftlichen Grenze angelangt.
4. Ein anderes System muss Ihre Daten lesen oder schreibenDer Moment, in dem die Abrechnung, ein ERP, ein Data Warehouse oder eine Automatisierung miteinander in Berührung kommen müssen
Wenn Sie Ihre CRM-Daten nicht verwalten, wird das Fehlen einer stabilen externen API bei AppSheet zum Hindernis. Sie sind zurück zum Kopieren und Einfügen oder zum fragilen Apps Script-Kleber.
5. Sie benötigen einen Audit-Trail, den AppSheet nicht bieten kann
Compliance, Finanzen oder ein Kundenstreit fragen schließlich: „Wer hat das geändert?“ wann.“ Die Angabe „Zuletzt bearbeitet von“ einer Zelle ist kein Prüfprotokoll.
Architektur
Eine saubere Aufteilung: Express API + relationale Datenbank als Quelle der Wahrheit, ein React SPA für das Team und eine einmalige ETL-Brücke, die die Blätter von AppSheet einzog normalisierte relationale Tabellen.

AppSheet (Google Sheets) ──ETL──▶ Relational DB (normalized)
│
▼
Express API (REST)
│
┌─────────────┴─────────────┐
▼ ▼
React SPA Billing / webhooks
- Typisierte Abfrageebene für sichere Abfragen und Migrationsskripts (Schemaänderungen werden als überprüfte Migrationsdateien geliefert).
- Zod-Validierung an jeder Routengrenze und erneut an der ETL-Grenze.
- Eine schreibgeschützte Schattenperiode: Beide Systeme liefen zwei Wochen lang parallel Wir haben jede Nacht die Anzahl der Reihen abgeglichen.
Das sichere Migrationsmuster
Dies ist das Verfahren, das ich für alle Tabellenkalkulations-zu-Datenbank-Verschiebungen verwende, hier speziell für AppSheet mit Live-Parallellauf-Umstellung.
Schritt 1 – Modellieren Sie die Daten relational, nicht als 1:1 der Blätter
Die flachen Registerkarten von AppSheet enthielten eine Spalte „Kunde“, die auf fünf Blätter dupliziert war. Die Die erste Aufgabe besteht darin, Fremdschlüssel zu normalisieren und aufzufüllen. Alles stromabwärts — Abrechnung, Berichterstattung, Deduplizierung – hängt davon ab.
CREATE TABLE customers (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
email citext UNIQUE NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE contacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id uuid NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
name text NOT NULL,
email citext,
phone text
);
CREATE TABLE deals (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id uuid NOT NULL REFERENCES customers(id),
stage text NOT NULL CHECK (stage IN ('lead','qualified','won','lost')),
amount_cents integer NOT NULL DEFAULT 0,
closed_at timestamptz,
-- the ETL cursor anchor: every migrated row keeps its AppSheet "updatedAt"
appsheet_row_updated_at timestamptz NOT NULL
);
CREATE INDEX deals_customer_idx ON deals(customer_id);
Eine CHECK-Einschränkung für stage und eine UNIQUE für email sind der Unterschied
zwischen sauberen Daten und drei Wochen Bereinigung, bevor Sie einem Bericht vertrauen können.
Schritt 2 – Validieren Sie vor der Migration
AppSheet exportiert Zahlen als Text, lässt E-Mails fehlerhaft formatieren und überträgt verwaiste Nachrichten Referenzen. Ein Zod-Durchlauf über die exportierten Zeilen bringt den Müll zum Vorschein, bevor er ihn erreicht die Datenbank – in diesem Projekt haben etwa 3 % der Zeilen die Validierung nicht bestanden und wurden weitergeleitet in eine Überprüfungswarteschlange statt in die Datenbank verschoben.
const AppSheetDeal = z.object({
DealID: z.string().min(1),
Customer: z.string().min(1),
Stage: z.enum(["lead", "qualified", "won", "lost"]),
Amount: z.string().regex(/^\d+(\.\d{1,2})?$/), // exported as text, not a number
UpdatedAt: z.string(),
});
export type AppSheetDeal = z.infer<typeof AppSheetDeal>;
Zeilen, die fehlschlagen, landen in einer migration_rejections-Tabelle mit dem Grund, also nichts
wird lautlos fallen gelassen und nichts Schmutziges gelangt in das saubere System.
Schritt 3 – Idempotentes ETL mit einem Cursor
Jede AppSheet-Zeile trägt ein updatedAt. Der Importeur verfolgt das zuletzt Gesehene
Zeitstempel pro Tabelle, daher führt die erneute Ausführung des ETL nie und immer zu doppelten Einfügungen
holt auf. Idempotenz macht den Parallellauf in Schritt 4 sicher.
async function importDealsSince(cursor: Date): Promise<number> {
const rows = await appSheet.list("Deals", {
// AppSheet filters client-side on the export; the cursor narrows the set
filter: (row) => new Date(row.UpdatedAt) > cursor,
});
for (const row of rows) {
const parsed = AppSheetDeal.parse(row); // Step 2 validation
const customer = await upsertCustomerByName(parsed.Customer);
await db
.insert(deals)
.values({
customerId: customer.id,
stage: parsed.Stage,
amountCents: Math.round(parseFloat(parsed.Amount) * 100),
appsheetRowUpdatedAt: new Date(parsed.UpdatedAt),
})
.onConflictDoUpdate({ // idempotent re-runs
target: deals.id,
set: {
stage: parsed.Stage,
amountCents: sql`excluded.amount_cents`,
appsheetRowUpdatedAt: new Date(parsed.UpdatedAt),
},
});
}
return rows.length;
}
Schritt 4 – Parallellauf mit nächtlichem Abgleich
Zwei Wochen lang liefen beide Systeme live. Die Schreibvorgänge gingen immer noch an AppSheet (wo das Team war bequem); Die ETL hat sie jede Nacht in die Datenbank gespiegelt. Jeden Morgen ein Der automatisierte Job verglich die beiden und meldete eine Abweichung.
-- Per-table count drift between the AppSheet snapshot and database
SELECT 'deals' AS table_name,
a.row_count AS appsheet,
d.row_count AS db_count,
a.row_count - d.row_count AS drift
FROM appsheet_snapshot a
JOIN db_counts d USING (table_name);
Eine Übereinstimmung der Anzahl ist notwendig, aber nicht ausreichend, daher wurde der Job auch auf Zeilenebene ausgeführt Hash-Prüfung einer 1 %-Stichprobe, um Änderungen zu erkennen, die die Zählungen stabil hielten, sich aber änderten Inhalt. Beim Parallellauf wurden drei Mapping-Fehler festgestellt, die ein Probelauf-Skript aufwies verfehlt – genau seinen Zweck.
Schritt 5 – Umstellung, mit einem Lesespiegel für Holdouts
Bei der Umstellung wurden die Schreibvorgänge auf die neue App umgestellt. AppSheet blieb als schreibgeschützte Version am Leben Spiegel für zwei Wochen über einen geplanten Exportjob, damit niemand noch auf dem neuen ist Der Datenfluss behielt die Transparenz, ohne abweichende Daten zu erzeugen. Dann haben wir es ausgeschaltet.
Interessante Entscheidungen
- Relational modellieren, nicht 1:1 mit den Blättern. Die duplizierte Kundenspalte war die Ursache der Berichtsprobleme. Normalisierung der ersten freigeschalteten Abrechnung und Meldung als nahezu freie Nebenwirkungen.
- Behalten Sie AppSheet während der Umstellung als Lesespiegel bei. Es wurde das „Jeder muss“ entfernt „Switch at Once“-Risiko und gab den Verweigerern ein Sicherheitsnetz.
- Replizieren Sie die UX von AppSheet nicht Bildschirm für Bildschirm. Eine Migration kommt selten vor Chance, Arbeitsabläufe zu korrigieren, die sich an den Grenzen eines Tools angesammelt haben. Wir haben das umgebaut Drei Flows nutzten die Leute tatsächlich und ließen zwei fallen, die nur zum Zweck der Arbeit existierten rund um AppSheet.
- Machen Sie die ETL per Design wieder ausführbar. Idempotentes
onConflictDoUpdateplus das Der Zeitstempel-Cursor bedeutete, dass wir den Importer während der Schattenperiode stündlich ausführen konnten ohne Angst.
Gelernte Lektionen
- Normalisieren Sie Datums- und Uhrzeitangaben auf dem Weg dorthin auf UTC. AppSheet speichert Datums- und Uhrzeitangaben als
Ortszeit des Herausgebers, unbeschriftet. Eine einmalige Konvertierung in UTC mit einer expliziten
Die Spalte
tzverhinderte geringfügige Abweichungen in der Berichterstellung. - Validieren Sie vor der Migration. Der Zod-Pass, der etwa 3 % Junk-Daten ans Tageslicht brachte, war die einzelne Stunde mit der höchsten Hebelwirkung des Projekts.
- Parallellauf schlägt Urknall. Der zweiwöchige Schatten hat Mapping-Fehler entdeckt a dry-run konnte nicht und ließ das Team weiterarbeiten, während wir es überprüften.
- Budget für die Daten, nicht für die App. Die React-Benutzeroberfläche war der einfache Teil; Deduplizierung Kunden und die Reparatur von Referenzen dauerten länger als der Aufbau der neuen Schnittstelle.
Vor- und Nachteile
Vorteile: Vollständiger Dateneigentum, eine stabile API, die andere Dienste aufrufen können, vorhersehbar Pauschalkosten, echte Integritätsbeschränkungen und ein ordnungsgemäßes Prüfprotokoll.
Nachteile: Sie betreiben jetzt eine Datenbank – Backups, Überwachung, Migrationen liegen bei Ihnen. AppSheet hat das alles versteckt. Dem Gewerbe obliegt die betriebliche Verantwortung für das Eigentum und Fähigkeiten, und es ist der richtige Beruf, wenn Sie erst einmal an die Wand oben gestoßen sind.
FAQ
Können wir AppSheet für die Felddatenerfassung behalten? Ja. AppSheet ist immer noch ein starker Thin Client. Nach der Migration behalten einige Teams es rein für die mobile Offline-Erfassung, Schreiben in die neue API anstelle einer Sicherung Blatt. Was Sie zurücklassen, ist AppSheet als System of Record, nicht als Benutzeroberfläche.
Wie lange dauert eine solche Migration? Für ca. 50.000 Reihen und ein Team im Bereich von 8–20 müssen Sie mit einer verstrichenen Zeit von 3–6 Wochen rechnen Davon entfällt etwa die Hälfte auf die Datenbereinigung und die Parallellaufüberprüfung – nicht Codierung. Die Benutzeroberfläche ist der schnelle Teil.
Was passiert, wenn wir nicht bereit sind, AppSheet zu verlassen? Führen Sie die obige Fünf-Zeichen-Prüfung durch. Wenn Sie weniger als drei sehen, fallen die Übernachtungskosten an liegt immer noch unter den Migrationskosten, und das ist ein berechtigter Grund zu warten. Migrieren Sie, wenn sich die Wand deutlich vor Ihnen befindet, und nicht als Vorsichtsmaßnahme.
Werden wir Daten verlieren? Nicht mit diesem Muster. Die Validierung leitet fehlerhafte Zeilen an eine Überprüfungswarteschlange weiter, die ETL idempotent, und der Parallellauf beweist die Parität vor der Umstellung. „Ohne einen zu verlieren Single Record” ist der Standard, nicht die Hoffnung.
Möchten Sie dies für Ihre AppSheet-App?
Wenn Ihr Team an die AppSheet-Grenze stößt – langsame Synchronisierungen, keine API, Kosten pro Arbeitsplatz Klettern – Ich ordne den Ausgang in einem einzigen Discovery-Aufruf zu: was migriert werden soll, was wie die Zielarchitektur aussieht, und einen realistischen Zeitplan. Sie behalten Ihre Daten und Dein Schwung.Einen Entdeckungsanruf buchen →
Interaktiver Risiko- und ROI-Assessor für Google Sheets
Bewerten Sie Tabellenzustand und geschätzte wöchentlich verlorene Zeit.