How I Built an App That Syncs With Supabase Without Overwriting Data - 02/03/2026
One of the hardest parts of building a collaborative or real-time app is keeping data in sync across multiple users without one client's update overwriting another's changes. In this post I explain how I solved that problem using Supabase.
Live app: capturioo.com
One of the hardest parts of building a collaborative or real-time app is keeping data in sync across multiple users or sessions without one client’s update overwriting another’s changes. In this post, I explain how I solved that problem using Supabase as the backend.
The Problem: Sync vs. Overwrite
In a traditional app using fetch or axios, the typical flow is:
- Client fetches data from the server.
- User changes something.
- Client sends the full object back to the server.
The problem is that in step 3, if another user modified the same resource in the meantime, those changes are lost. This is called a blind overwrite.
The Solution: Granular Updates + Real-Time
Instead of sending the full object, the key is to send only the field that changed. I call this a semantic patch.
Example:
// ❌ Wrong — overwrites everything
await supabase
.from('items')
.update({ ...fullObject })
.eq('id', item.id);
// ✅ Right — only updates the changed field
await supabase
.from('items')
.update({ title: newTitle })
.eq('id', item.id);
Supabase Realtime: Listening to Changes Without Polling
With supabase.channel() I subscribe to table changes in real time. When another user modifies a record, my client receives the event and only merges that field into local state, without replacing the entire object:
supabase
.channel('items-channel')
.on(
'postgres_changes',
{ event: 'UPDATE', schema: 'public', table: 'items' },
(payload) => {
// Merge only the changed field, not replace the full object
setState((prev) =>
prev.map((item) =>
item.id === payload.new.id
? { ...item, ...payload.new }
: item
)
);
}
)
.subscribe();
Row Level Security (RLS): Protect Without Blocking
Supabase uses PostgreSQL with RLS. I configured policies so each user can only modify their own records, but can read others’ in collaborative contexts. This ensures that even with open sync, private data stays protected.
Lessons Learned
- Granular updates are the key to avoiding conflicts.
- Realtime + merged local state eliminates the need for polling.
- Well-configured RLS enables collaboration without sacrificing security.
- Supabase makes implementing all of this surprisingly accessible.
You can see this architecture in action at capturioo.com.