DEV Community

Cover image for How I built a Stock Suggestion app using React + node.js + free APIs ?
Aashutosh Bairagi
Aashutosh Bairagi

Posted on

How I built a Stock Suggestion app using React + node.js + free APIs ?

Application Link

This is How I Built a Real-Time Stock Suggestion App with the MERN Stack, WebSockets, and Algorithmic Trading Logic!

Finance and tech are converging faster than ever. As developers, we have the tools to build incredibly powerful applications that demystify market data. I set out to do just that: build a full-stack web application that not only tracks stock data but also provides actionable, AI-driven suggestions in real-time.

This article is a technical deep-dive into the architecture and key features of my Stock Suggestion App. We'll cover the "AI" backend, the real-time WebSocket layer, and how it all connects to a resilient React frontend.

The Tech Stack 🥞

1.Frontend: React 18, Vite, React Router 7, Tailwind CSS, Framer Motion.

2.State Management: @tanstack/react-query (for server state) & React Context (for auth/theme).

3.Backend: Node.js, Express, Mongoose (MongoDB).

4.Real-time Layer: ws (WebSocketServer).

5.Authentication: Passport.js.

6.Charting: lightweight-charts & recharts.

The Architecture: A Three-Layered Approach

The app is broken down into three core components: the "AI" Signal Service, the Real-time Data Hub, and the Reactive Frontend.

1. The "Brain": Algorithmic Signal Generation

The term "AI" gets thrown around a lot. In this project, it refers to a dedicated backend service (signalService.js) that runs technical analysis on historical stock data to generate actionable insights.

It calculates several key Technical Indicators (TIs):

  • Simple Moving Average (SMA): I calculate both the 50-day and 200-day SMAs to identify long-term trends.
  • Relative Strength Index (RSI): A proxy calculation to determine if a stock is overbought or oversold.
  • Moving Average Convergence Divergence (MACD): A proxy to gauge momentum by comparing short-term and long-term averages.

These indicators are fed into two key functions:

  • determineSignal(latestData): This function applies a rules-based logic (e.g., "if RSI < 30 and MACD > 0, signal 'Strong Buy'") to provide a clear BUY, SELL, or HOLD recommendation.

  • calculateTrendingScore(latestData): This is the secret sauce for the "Top Picks" page. It generates a proprietary score from 0-100 based on a weighted average of momentum, RSI strength, and long-term trend confirmation (e.g., is the price above its 200-day SMA?).

2. The "Heartbeat": Real-Time with WebSockets

A stock app without real-time data feels static. I used the ws library to build a lightweight WebSocket server that integrates directly with the main Express API.

On the Backend (server.js): Instead of running a separate server, I attach the WebSocketServer to the same HTTP server instance that Express uses. This is efficient and simplifies deployment.

// In server.js...
import { WebSocketServer } from 'ws';
import { handleConnection } from './core/websocket.js';

// ... after mongoose connects ...
const server = app.listen(process.env.PORT || 5000, /* ... */);

// --- 2. INITIALIZE THE *NEW* WEBSOCKET SERVER ---
const wss = new WebSocketServer({ server }); // Attach to the Express server

wss.on('connection', (ws) => {
    handleConnection(ws); // Delegate logic to a separate module
});

console.log("✅ Real-time WebSocket service initialized...");
Enter fullscreen mode Exit fullscreen mode

This handleConnection module is responsible for managing connected clients and, crucially, broadcasting real-time price and signal updates as they happen.

3. The "Face": A Resilient React Frontend

The frontend's main challenge is managing data from two sources: a traditional REST API (for historical data, watchlist) and a WebSocket (for live updates).

The Hybrid Data-Fetching Pattern: I landed on a powerful hybrid pattern using Tanstack Query (React Query) and a WebSocketContext. The TopPicks.jsx page is the best example of this.

  1. Initial Load (HTTP): The page first uses useQuery to fetch the pre-calculated list of top picks from the /api/stocks/top-picks REST endpoint. This gives the user instant, cached data.

  2. Live Updates (WebSocket): Simultaneously, useWebSocket() (from my custom context) is connected. It listens for live "tick" messages from the server.

3.Merging Data: The component intelligently decides which data to display. If the WebSocket is connected and has sent data, it uses that live data. If not, it falls back to the useQuery batch data.

// In TopPicks.jsx...
import { useWebSocket } from '../context/WebSocketContext';
import { useQuery } from '@tanstack/react-query';

// ...

const { topPicks: wsTopPicks, realtimeData } = useWebSocket();
const { data: batchData, isLoading } = useQuery(
    'topPicksBatch', 
    () => fetchTopPicksBatch(token),
    { enabled: wsTopPicks.length === 0 } // Only run if WS isn't ready
);

// Source of Truth: Prefer live data, fall back to batch API data
const sourceData = wsTopPicks.length > 0 ? wsTopPicks : (batchData?.picks || []);

// Combine with real-time price ticks
const rankedStocks = sourceData.map(stock => {
    const realtime = realtimeData[stock.symbol] || {};
    return {
        ...stock,
        latestPrice: realtime.price || stock.latestPrice,
        signal: realtime.signal || stock.signal, // Live signal!
    };
}).sort((a, b) => b.trendingScore - a.trendingScore);
Enter fullscreen mode Exit fullscreen mode

This pattern makes the app feel incredibly fast and resilient. It loads instantly and provides live-tick updates without complex state management. For simpler data, like the WatchlistPage.jsx, I just use useQuery for a standard, cached fetch.

Polishing the UI: To make the app feel modern, I used Tailwind CSS for all styling and Framer Motion to add subtle, satisfying animations, like the card load-in effect on the Top Picks page.

Conclusion

Building this app was a fantastic journey into combining data-driven backend logic with a high-performance, real-time frontend. The core challenge—merging historical analysis with live data—was solved by the hybrid useQuery/WebSocket pattern. The signalService.js was my first real attempt at codifying trading logic, and the ws library proved to be a simple yet powerful tool for the real-time layer.

This architecture provides a scalable and responsive foundation, and I'm excited to continue refining the signal algorithms.

Thanks for reading! I hope this deep-dive was useful. What real-time data challenges are you tackling?

Top comments (0)