Ditch REST: How React + SQL + WebSockets Can Power Real-Time Apps Without the Overhead
Tired of polling? Hate Redux overload? Want snappy real-time updates without diving into Firebase lock-in? Here's a bold new way to build full-duplex apps with SQL + React + WebSockets that actually scales.
π¨ Why REST Doesnβt Cut It Anymore
Weβve been building web apps the βRESTfulβ way for the last two decades: client requests -> server responds. While it works for a lot of CRUD-style apps, the internet is changing. Take a look at:
- Real-time collaboration (e.g. Google Docs)
- Multiplayer experiences (e.g. Figma, Trello)
- Dashboards that auto-update (e.g. Trading apps)
Polling every few seconds is a waste of resources and bad UX. You might consider Firebase, Hasura, or GraphQL subscriptions β but what if you could get real-time capabilities with tools you already know?
Enter React + SQL + WebSockets.
π§ The Stack
We'll build a basic chat app (yes, revolutionary π) but the idea scales:
- Frontend: React with Hooks and WebSocket connection
- Backend: Node.js with Express and WebSocket server
- Database: PostgreSQL (because SQL still rocks)
- Real-Time Sync: PostgreSQL triggers + WebSocket pushing delta
π οΈ Pre-requisites
Before diving in, make sure youβve got:
- Node.js β₯ 18
- PostgreSQL β₯ 12
- Basic knowledge of React
- Familiarity with Express
π§ͺ Step 1 β PostgreSQL Setup with Trigger
Letβs enable PostgreSQL to notify our backend when new data comes in.
-- Create messages table
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Function to notify
CREATE OR REPLACE FUNCTION notify_new_message()
RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify('new_message', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER trigger_new_message
AFTER INSERT ON messages
FOR EACH ROW EXECUTE FUNCTION notify_new_message();
Now, every time you INSERT a message, PostgreSQL sends a NOTIFY event.
π Step 2 β Node.js WebSocket Listening on PostgreSQL
Letβs write the backend that pipes SQL NOTIFY messages to WebSocket clients.
const express = require('express');
const { Pool } = require('pg');
const WebSocket = require('ws');
const http = require('http');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
const pgPool = new Pool({
connectionString: process.env.DATABASE_URL // or use config
});
(async () => {
const client = await pgPool.connect();
await client.query('LISTEN new_message');
client.on('notification', msg => {
const payload = JSON.parse(msg.payload);
console.log('π¬ New message:', payload);
wss.clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
});
});
})();
app.use(express.json());
app.post('/messages', async (req, res) => {
const { content } = req.body;
const { rows } = await pgPool.query(
'INSERT INTO messages (content) VALUES ($1) RETURNING *',
[content]
);
res.json(rows[0]);
});
server.listen(3001, () => console.log('π Server listening on port 3001'));
Now the server acts like a bridge from PostgreSQL -> WebSocket.
βοΈ Step 3 β React Hook for WebSocket Subscription
On the React side, we set up a WebSocket hook to connect to the backend and render messages.
import React, { useEffect, useRef, useState } from 'react';
function useWebSocketMessages(url) {
const [messages, setMessages] = useState([]);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket(url);
ws.current.onmessage = (e) => {
const data = JSON.parse(e.data);
setMessages((prev) => [...prev, data]);
};
return () => ws.current.close();
}, [url]);
return messages;
}
function Chat() {
const messages = useWebSocketMessages('ws://localhost:3001');
return (
<div>
<h2>Real-time Chat</h2>
<ul>
{messages.map((msg, idx) => (
<li key={idx}>{msg.content}</li>
))}
</ul>
</div>
);
}
export default Chat;
𧬠Architectural Benefits
This architecture is surprisingly powerful:
- π₯ Realtime β updates as they happen, no polling
- πͺ React hooks fit naturally with WebSocket streams
- π¦ Lightweight backend β no need for GraphQL or Firebase
- π§ Declarative updates from SQL without Redux
- π Full control over data & auth
βοΈ Where to Go From Here?
This pattern isnβt limited to a chat app. You can extend it to:
- Real-time stock tickers
- IoT sensor visualization
- Collaborative whiteboards
- Multiplayer boards (Kanban, Trello clones)
Bonus: Add authentication, debounce updates, or event sourcing with PostgreSQL logical replication.
π Conclusion
Imagine a future where:
- Your server speaks SQL + WS natively
- Your client only fetches once
- You skip Redux, polling, and stale data
With a small upgrade to your architecture, you can get real-time SQL in the browser β and you didnβt even need Firebase.
REST is slow. GraphQL is heavy. WebSockets + SQL is underrated.
Try it.
π Resources:
- WebSocket spec: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
- PostgreSQL NOTIFY: https://www.postgresql.org/docs/current/sql-notify.html
- React state management without Redux: https://recoiljs.org/
π§ If you enjoyed this article, follow the blog for deeper real-time explorations, async patterns, and minimalist web architectures that scale.
Happy hacking π
π If you need this done β we offer fullstack-development services.
Top comments (0)