How I built a real-time database layer using MySQL binlogs, WebSockets, and React hooks to create automatic UI updates without polling.

Modern applications are no longer request-response driven. Users expect dashboards to update instantly, chats to sync automatically, and admin panels to reflect database changes in real time — without refreshing the page.
While exploring real-time systems like Convex, I became curious about how such systems actually work under the hood. Instead of just reading about it, I decided to build my own lightweight real-time data layer on top of MySQL and Next.js.
The result is Realtime DB — a simple but powerful architecture that listens to database changes and pushes updates to the frontend instantly.
GitHub: https://github.com/dev-kraken/realtime-db
This article explains the architecture, technical decisions, and how the system works internally.
Traditionally, frontend apps fetch data like this:
useEffect(() => {
fetch('/api/products')
}, [])If you want updates, you:
setInterval)This creates:
I wanted something cleaner:
When the database changes → the UI updates automatically.
No polling. No manual refetching.
The architecture is built around three principles:
Listen to database changes
Detect what changed
Push updates to subscribed clients
Instead of making the frontend ask repeatedly:
“Did something change?”
The backend says:
“Something changed. Here’s the update.”
The system has four main parts:
MySQL maintains a binary log that records every:
By subscribing to the binlog, the server can detect changes the moment they happen — without modifying existing queries or adding triggers.
This is the key that makes everything reactive.
When a table change is detected:
This ensures:
For real-time transport, I used WebSockets (via Socket.IO).
Why WebSockets?
Because unlike HTTP:
When a table changes:
On the frontend, I designed a simple hook:
const { data, loading, error } = useRealtimeTable("products")This abstracts away:
From the component’s perspective, it behaves like a normal data hook — but it’s fully reactive.
A record in MySQL changes.
The binlog listener detects the event.
The backend determines which table changed.
The system fetches the updated rows.
WebSocket broadcasts to all subscribers.
React hook updates local state.
UI re-renders instantly.
No polling.
No manual refresh.
No explicit refetch logic.
Instead of replacing the database, this approach enhances it.
You keep:
You just add a reactive layer on top.
The system reacts to actual database events, not timers.
This makes it:
From the UI perspective, nothing special is required.
Just:
const { data } = useRealtimeTable("customers")And the UI always stays in sync.
Because WebSockets require persistent connections:
This is an architectural trade-off — but necessary for real-time push communication.
Security was an important design decision.
The system:
The client only receives broadcasted data — nothing more.
Building this system clarified how modern real-time backends work internally:
It also reinforced that “real-time” doesn’t require a completely new database — it requires listening intelligently to changes.
Some ideas for evolution:
This project started as curiosity.
I wanted to understand how real-time platforms work — not just use them.
So I built my own reactive data layer on top of MySQL.
It may not be perfect.
It may not be the final architecture.
But it works — and more importantly, I understand every part of it.
That’s the real win.