DEV Community

Cover image for ⚡ Designing Real-Time Trading Apps for Scale — REST, NATS.js, and Smart State Management
Vishwark
Vishwark

Posted on

⚡ Designing Real-Time Trading Apps for Scale — REST, NATS.js, and Smart State Management

Modern trading platforms like Groww or Kite handle thousands of price updates per second.
To make the UI smooth, fast, and scalable, they combine real-time streaming (via NATS) and REST APIs (for static data).

Let’s walk through how this architecture works, why NATS.js shines here, and how to handle market hours vs post-market hours seamlessly on the frontend.


🧠 1. Where REST and NATS Are Used

A trading app needs both static and live data. Each page uses them differently.

Page Data via NATS (Live) Data via REST (Static / Snapshot)
Nifty 50 / Top Gainers / Losers Real-time prices, percentage change Company names, symbols, sectors
Holdings / Positions LTP (Last Traded Price), live PnL Stock metadata, historical cost
Stock Detail Page Price ticks, volume, OHLC updates Company info, news, fundamentals
Dashboard Portfolio value refresh (live PnL) Profile info, investment summary
User Profile Purely REST

So —
🟢 NATS → live market data
🔵 REST → static or slowly changing data


⚙️ 2. Using NATS.js in the Frontend

NATS.js is an official JavaScript client for the NATS messaging system — a high-performance publish/subscribe platform built for cloud apps.

✨ Why NATS.js Shines Here

  1. Subject-based Pub/Sub: You can subscribe to fine-grained topics like stocks.NIFTY.NS or orders.user123.status.
  2. Lightweight: Binary encoding, minimal overhead — ideal for streaming prices at millisecond frequency.
  3. Automatic Reconnects: Recovers gracefully from disconnections.
  4. Load balancing & clustering: Distributes updates efficiently across thousands of clients.
  5. Streaming + Queue Groups: Handles fan-out for large-scale market tick distribution.

📦 Installing NATS.js

npm install nats.ws
Enter fullscreen mode Exit fullscreen mode

🔌 Basic Connection Example

import { connect } from 'nats.ws';

const nc = await connect({ servers: 'wss://pulse.tradingapp.com' });

const sub = nc.subscribe('stocks.*');

for await (const m of sub) {
  const { symbol, price } = JSON.parse(m.data);
  updateStock(symbol, price);
}
Enter fullscreen mode Exit fullscreen mode

🏗️ 3. State Management — A Generic Store Design

Instead of keeping fixed slices like nifty50, topGainers, or holdings, a generic symbol-based store is more scalable.

Example (Zustand-like):

import { create } from "zustand";

const useStockStore = create((set, get) => ({
  stocks: {}, // symbol → details

  updateStocks: (updates) =>
    set((state) => {
      const newStocks = { ...state.stocks };
      updates.forEach((u) => {
        newStocks[u.symbol] = { ...newStocks[u.symbol], ...u };
      });
      return { stocks: newStocks };
    }),
}));
Enter fullscreen mode Exit fullscreen mode

Then whenever new data arrives:

pulse.on("priceUpdate", (data) => {
  useStockStore.getState().updateStocks(data);
});
Enter fullscreen mode Exit fullscreen mode

This design allows bulk updates — even if 100 stocks are subscribed (like on a holdings page), updates are applied efficiently with a single re-render.


🔄 4. How Data Flows on the UI (Static + Live)

Let’s see how different pages use both REST and NATS in harmony 👇

🔹 Nifty50 / Top Gainers Page

  • REST → Initial stock list + metadata
  • NATS → Continuous tick updates (LTP, change%)
  • Bulk updates applied to store
// REST snapshot
const snapshot = await fetch('/api/stocks/nifty50').then(r => r.json());
useStockStore.setState({ stocks: snapshot });

// Live updates
pulse.subscribe('stocks.nifty50.*', handleUpdates);
Enter fullscreen mode Exit fullscreen mode

🔹 Stock Details Page

  • REST → Company name, description, news, financials
  • NATS → Real-time price chart and volume updates Both layers merge at the UI level — static info from REST, live info from NATS.

🔹 Holdings / Portfolio Page

  • REST → Your bought quantities, avg cost
  • NATS → Live LTP → Recalculate PnL dynamically

Example:

const pnl = (ltp - avgCost) * qty;
Enter fullscreen mode Exit fullscreen mode

🔹 Dashboard

  • REST → Total invested value
  • NATS → Current value → Instant portfolio refresh

🕓 5. After Market Hours — Switching to REST

After 3:30 PM, the exchanges stop sending live ticks.
The app automatically switches from NATS to REST mode.

Time Data Source Behavior
During Market Hours NATS Live ticks, streaming updates
After Market Close REST EOD snapshots, no new subscriptions

Implementation idea:

if (marketStatus === "OPEN") {
  pulse.subscribe("stocks.*");
} else {
  pulse.unsubscribe("stocks.*");
  fetch("/api/eod-snapshot").then(updateStocks);
}
Enter fullscreen mode Exit fullscreen mode

This hybrid design saves bandwidth and keeps the UI consistent — users still see final market prices even when data feed stops.


⚡ Why Not Just WebSockets?

Feature WebSocket NATS.js
Pub/Sub Routing Manual Built-in subjects
Clustering Custom infra Native
Reconnect Handling Manual Automatic
Binary Support Yes Yes (optimized)
Fan-out Efficiency Poor at scale Excellent
Server-to-Server bridge Not native Built-in JetStream

In short, NATS is WebSocket++ — faster, scalable, and designed for real-time microservices, not just peer connections.


🧩 6. Bringing It All Together

Here’s what happens under the hood:

           ┌──────────────┐
           │ Exchange Feed│
           └──────┬───────┘
                  │
              (via NATS)
                  │
        ┌────────────────────┐
        │ Trading App Backend│
        └────────────────────┘
                  │
           ┌─────────────┐
           │ NATS.js     │  <— Subscribes to live ticks
           └─────────────┘
                  │
           ┌──────────────┐
           │ Zustand Store│  <— Bulk updates by symbol
           └──────────────┘
                  │
           ┌──────────────┐
           │ React UI     │  <— Auto re-renders only affected stocks
           └──────────────┘
Enter fullscreen mode Exit fullscreen mode

💡 Key Takeaways

  • REST + NATS = perfect hybrid for trading apps
  • NATS.js gives reliable real-time streaming with lightweight reconnects
  • Generic key–value store (symbol → data) handles high-frequency updates efficiently
  • Bulk update pattern ensures smooth UI even with 100+ stocks
  • Fallback to REST keeps app stable after market close

🔚 Final Thoughts

Frontend performance in trading apps isn’t just about fancy charts —
it’s about data orchestration: balancing live vs static updates, avoiding over-renders, and using protocols designed for scale.

So the next time you build a dashboard or live feed —
think beyond WebSocket, think NATS 🚀


Top comments (0)