Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Linux Tipps & HardeningSecurity: Ausführen beliebiger Kommandos in perl-Dancer2 (Fedora)(29.09.2026 um 07:43 Uhr)
•
Linux Tipps & HardeningSecurity: Denial of Service in perl-HTML-FormFu (Fedora)(29.09.2026 um 07:46 Uhr)
••
Linux Tipps & HardeningSecurity: Zwei Probleme in NetworkManager-l2tp (Fedora)(29.09.2026 um 07:46 Uhr)
••
Sicherheitslücken (CVE)CVE-2026-77144 | TYPO3 Events 2 Plugin up to 10.2.11 permission(29.09.2026 um 06:21 Uhr)
•
Sicherheitslücken (CVE)CVE-2026-21753 | HCL Hive 1.0 unmaintained third party components(29.09.2026 um 06:21 Uhr)
••
Sicherheitslücken (CVE)CVE-2026-75038 | ilya-zlobintsev LACT up to 0.10.0 symlink(29.09.2026 um 06:21 Uhr)
••
Linux Tipps & HardeningSecurity: Ausführen beliebiger Kommandos in perl-Dancer2 (Fedora)(29.09.2026 um 07:43 Uhr)
•
Linux Tipps & HardeningSecurity: Denial of Service in perl-HTML-FormFu (Fedora)(29.09.2026 um 07:46 Uhr)
••
Linux Tipps & HardeningSecurity: Zwei Probleme in NetworkManager-l2tp (Fedora)(29.09.2026 um 07:46 Uhr)
••
Sicherheitslücken (CVE)CVE-2026-77144 | TYPO3 Events 2 Plugin up to 10.2.11 permission(29.09.2026 um 06:21 Uhr)
•
Sicherheitslücken (CVE)CVE-2026-21753 | HCL Hive 1.0 unmaintained third party components(29.09.2026 um 06:21 Uhr)
••
Sicherheitslücken (CVE)CVE-2026-75038 | ilya-zlobintsev LACT up to 0.10.0 symlink(29.09.2026 um 06:21 Uhr)
••
Intelligence View
⚡ tsecurity.de Intelligence

Offline Sync in the Browser Without a Framework

I've been building apps with IndexedDB for years. The local part works fine — store data, query it, show it on screen. The hard part is keeping that data in sync with a server when the network comes and goes. Most tutorials show you how t…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

I've been building apps with IndexedDB for years. The local part works fine — store data, query it, show it on screen. The hard part is keeping that data in sync with a server when the network comes and goes.



Most tutorials show you how to build an offline app with a framework. Firebase, RxDB, WatermelonDB. Those work, but they bring their own abstractions, their own sync protocols, their own opinions.



I wanted something simpler. A database with a sync API that doesn't dictate how my backend works.



Here's the setup I landed on.



npm: npm install ctrodb

Docs: ctrodb.vercel.app/docs/sync/overview





What We're Building



A notes app that works offline. Create and edit notes on the train, in a tunnel, on a plane. When the network comes back, everything syncs automatically.



The database is ctrodb (zero-dependency, browser-based). The backend is anything that speaks HTTP.





Step 1: Database Setup





import { Database, syncPlugin, HttpTransport } from "ctrodb"

const db = new Database({
name: "notes-app",
schema: {
version: 1,
collections: {
notes: {
fields: {
title: { type: "string", required: true },
body: { type: "string" },
updatedAt: { type: "string", default: () => new Date().toISOString() },
},
indexes: [{ field: "updatedAt" }],
},
},
},
})

await db.connect()





Every collection you want to sync needs a timestamp field. The sync engine uses it to order changes and detect conflicts.



Plugins are passed in the Database constructor via plugins array:




const transport = new HttpTransport({
url: "https://api.myapp.com/sync",
})

const db = new Database({
name: "notes-app",
schema: { ... },
plugins: [syncPlugin({ transport })],
})

await db.connect()






The transport takes a single base URL and appends /push and /pull automatically. The sync plugin hooks into every write operation and records it in the change log.



The plugin exposes devtools that take the database instance as their first argument:




import { inspectSyncQueue, retryFailedSync, getSyncStats } from "ctrodb"

const queue = await inspectSyncQueue(db)
console.log(queue.stats)
// { total: 12, pending: 3, syncing: 0, committed: 9, failed: 0 }

const stats = await getSyncStats(db)
console.log(stats.pending) // 3

await retryFailedSync(db)









Step 3: The Server Handler



The server needs two endpoints. Pull returns changes (cursor-based). Push accepts incoming changes.




app.post("/sync/pull", async (req, res) => {
const { cursor } = req.body
const changes = await getChangesSince(cursor)
const nextCursor = changes.length > 0
? changes[changes.length - 1].timestamp
: cursor
res.json({ changes, cursor: nextCursor, hasMore: false })
})

app.post("/sync/push", async (req, res) => {
const { changes } = req.body
const accepted = []
const conflicts = []
const errors = []

for (const change of changes) {
try {
const existing = await findRecord(change.recordId)
if (existing && existing.updatedAt > change.timestamp) {
conflicts.push({ changeId: change.id, local: existing, remote: change.data })
} else {
await upsertRecord(change)
accepted.push({ id: change.id, serverTimestamp: new Date().toISOString() })
}
} catch (e) {
errors.push({ id: change.id, error: e.message })
}
}

res.json({ accepted, conflicts, errors })
})






The push response has three arrays. The sync engine handles each differently — accepted changes are marked committed, conflicts are passed to the resolver, errors are marked for retry.






Step 4: Trigger Sync



Trigger sync through the database instance. It pushes pending changes first, then pulls remote changes.




// On app start
await db.sync()

// After writing data
async function createNote(data) {
const note = await db.collection("notes").create(data)
await db.sync()
return note
}

// Background sync every 30 seconds
setInterval(async () => {
if (navigator.onLine) {
await db.sync()
}
}, 30000)






Auto-sync is available in the plugin config:




const db = new Database({
name: "notes-app",
schema: { ... },
plugins: [syncPlugin({
transport,
autoSync: { intervalMs: 30000, debounceMs: 500 },
})],
})






With auto-sync, the engine syncs on interval and after writes (debounced). You don't need to call db.sync() manually unless you want immediate sync.






Step 5: React Hooks






import { useSyncStatus } from "ctrodb/react"

function SyncIndicator() {
const { isSyncing, pendingChanges, lastSyncAt } = useSyncStatus()

if (isSyncing) return <span>Syncing...</span>
if (pendingChanges > 0) return <span>{pendingChanges} changes pending</span>
return <span>All changes saved</span>
}






For manual sync control:




import { useSync } from "ctrodb/react"

function SyncButton() {
const { sync, status } = useSync()
return (
<button onClick={sync} disabled={status.isSyncing}>
{status.isSyncing ? "Syncing..." : "Sync now"}
</button>
)
}









What This Setup Doesn't Do



This is a simple push-pull model. It works for single-user-offline — one person editing on their phone and laptop. It doesn't handle:





  • Real-time collaboration. Two users editing the same doc won't see changes until they sync. For that, you'd need WebSocket push or a CRDT layer.


  • Automatic field-level merge. The built-in strategies (lww, client-wins, server-wins) work at the record level. For field-level merge, you need a custom conflictResolver.


  • Offline auth token refresh. If your auth token expires while offline, the next sync will fail. Handle token refresh before the sync call or in the transport's headers callback.



For a notes app, todo list, or dashboard, the simple model is enough. You add complexity where you need it.





Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Offline Sync in the Browser Without a Framework

Thematisch verwandte Begriffe: Offline, Sync, Browser, Without · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-102367 | mall4j through 4.0 contains an insufficient session expiration vulnerab…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag