DEV Community

Cover image for Ditch REST: How React + SQL + WebSockets Can Power Real-Time Apps Without the Overhead
Yevhen Kozachenko ๐Ÿ‡บ๐Ÿ‡ฆ
Yevhen Kozachenko ๐Ÿ‡บ๐Ÿ‡ฆ

Posted on • Originally published at ekwoster.dev

Ditch REST: How React + SQL + WebSockets Can Power Real-Time Apps Without the Overhead

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(); 
Enter fullscreen mode Exit fullscreen mode

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')); 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

๐Ÿงฌ 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:


๐Ÿง  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)