DEV Community

Simplr
Simplr

Posted on • Originally published at blog.simplr.sh

Real‑Time Binance OHLCV Streaming with Node.js Native WebSocket & TypeScript

Binance offers several WebSocket endpoints that let you stream market data in real time. In this article, we’ll focus on retrieving 24‑hour mini ticker data—which includes open, high, low, close, and volume details (i.e. OHLCV) for selected trading pairs—using Binance’s combined stream endpoint. We’ll build a robust TypeScript solution that handles errors, reconnections, and graceful shutdowns using Node’s native WebSocket (available in Node.js v20 or later).

Note: Binance provides two primary mini ticker endpoints:

• The Combined Stream (via query parameters) returns individual messages in the format:

{
  "stream": "<streamName>",
  "data": { "e": "24hrMiniTicker", "E": 123456789, "s": "BTCUSDT", "c": "xxxx", ... }
}

• The All Market Mini Tickers Stream (wss://stream.binance.com:9443/ws/!miniTicker@arr) sends a single message that is an array of mini ticker objects.

In this tutorial we work with a combined stream for a fixed set of tickers, which provides the “stream” and “data” keys.


1. Overview

The Binance 24hr Mini Ticker Stream delivers a brief update about market statistics. Each mini ticker message includes:

  • e: Event type (e.g. "24hrMiniTicker")
  • E: Event time
  • s: Symbol (e.g. "BTCUSDT")
  • c: Close price
  • o: Open price
  • h: High price
  • l: Low price
  • v: Total traded base asset volume
  • q: Total traded quote asset volume

When using Binance’s combined stream, you can subscribe to multiple symbols by constructing a URL query parameter with each stream—for example, for BTCUSDT, ETHUSDT, and BNBUSDT:

wss://stream.binance.com:9443/stream?streams=btcusdt@miniTicker/ethusdt@miniTicker/bnbusdt@miniTicker
Enter fullscreen mode Exit fullscreen mode

Each message from this combined stream will have the structure shown above.


2. Environment Setup

We will build our solution in TypeScript, leveraging Node.js’ native WebSocket. (Node.js v20 or later will have a stable built-in WebSocket API; if you are on Node v18, you may need to enable experimental features.)

  1. Initialize your project:
   npm init -y
   npm install --save-dev typescript @types/node
   npx tsc --init
Enter fullscreen mode Exit fullscreen mode
  1. No external WebSocket library is required since we’re using the native WebSocket API.

3. Implementation in TypeScript

Below is the full code sample. This example:

  • Connects to a combined mini ticker stream for three symbols.
  • Uses the native WebSocket API (without importing an external module).
  • Parses incoming messages that adhere to the { stream, data } structure.
  • Implements error handling and reconnection logic.
  • Cleans up the WebSocket connection on process termination.
// List of tickers to subscribe to – note: Binance commonly uses USDT pairs.
const tickers = ["BTCUSDT", "ETHUSDT", "BNBUSDT"];

// Build the stream query string by making the tickers lowercase and appending the mini ticker event.
const streams = tickers.map((ticker) => `${ticker.toLowerCase()}@miniTicker`).join("/");
const WS_URL = `wss://stream.binance.com:9443/stream?streams=${streams}`;

// Global variables to manage our connection and reconnection timeout.
let ws: WebSocket | null = null;
let reconnectTimeout: NodeJS.Timeout | null = null;

/**
 * Establish (or re-establish) a connection to Binance’s combined stream.
 */
function connect() {
  // Create a new WebSocket connection using the native API.
  ws = new WebSocket(WS_URL);

  ws.onopen = () => {
    console.log("Connected to Binance WebSocket API");
  };

  ws.onmessage = (event: MessageEvent) => {
    try {
      // For combined streams, Binance sends messages as an object with:
      // - "stream": the stream name (e.g., "btcusdt@miniTicker")
      // - "data": the mini ticker payload with OHLCV information.
      const message = JSON.parse(event.data.toString());
      const { stream, data: payload } = message;
      console.log(`Received update from ${stream}:`, payload);

      // You can process the payload here:
      // For example: using payload.o (open), payload.h (high), payload.l (low), payload.c (close),
      // payload.v (volume), payload.q (quote volume), etc.
    } catch (err) {
      console.error("Error parsing incoming message:", err);
    }
  };

  ws.onerror = (event: Event) => {
    console.error("WebSocket error occurred:", event);
    cleanupAndReconnect();
  };

  ws.onclose = (event: CloseEvent) => {
    console.warn(`WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`);
    // If not a normal closure (code 1000), attempt to reconnect.
    if (event.code !== 1000) {
      console.log("Attempting to reconnect in 5 seconds...");
      reconnectTimeout = setTimeout(connect, 5000);
    }
  };
}

/**
 * Clean up the connection and schedule a reconnection.
 */
function cleanupAndReconnect() {
  if (ws) {
    // Nullify callbacks to avoid duplicate handling.
    ws.onopen = null;
    ws.onmessage = null;
    ws.onerror = null;
    ws.onclose = null;

    // Close the connection if it is still active.
    if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
      ws.close(1001, "Reconnecting"); // 1001 means "Going Away".
    }
  }
  if (reconnectTimeout) {
    clearTimeout(reconnectTimeout);
  }
  reconnectTimeout = setTimeout(connect, 5000);
}

connect();

/**
 * Gracefully shut down the connection on process termination.
 */
function shutdown() {
  console.log("Shutting down gracefully...");
  if (reconnectTimeout) {
    clearTimeout(reconnectTimeout);
  }
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.close(1000, "Process terminated"); // 1000: Normal Closure.
  }
  process.exit(0);
}

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

/**
 * Optional: Dynamic Subscription Management
 *
 * If you wish to subscribe or unsubscribe dynamically using a single WebSocket connection 
 * (instead of using a combined URL), Binance supports sending these JSON messages:
 *
 * // To subscribe to a new ticker (e.g., BNBUSDT mini ticker):
 * ws.send(JSON.stringify({
 *   method: "SUBSCRIBE",
 *   params: ["bnbusdt@miniTicker"],
 *   id: 1
 * }));
 *
 * // To unsubscribe:
 * ws.send(JSON.stringify({
 *   method: "UNSUBSCRIBE",
 *   params: ["bnbusdt@miniTicker"],
 *   id: 2
 * }));
 *
 * Note that when you rely on a combined stream URL, the subscription is fixed by the URL.
 */
Enter fullscreen mode Exit fullscreen mode

4. Explanation

  • Connection Setup:

    We construct the WebSocket URL from our chosen symbols by converting them to lowercase and appending the @miniTicker suffix. This URL returns individual messages containing the keys "stream" and "data".

  • Event Handlers:

    • onopen: Logs successful connection establishment.
    • onmessage: Parses incoming JSON data. For the combined stream, each message is an object with two keys:
    • "stream": indicating which stream (ticker) the update is for.
    • "data": the mini ticker payload containing OHLCV data.
    • onerror & onclose: Handle any errors or disconnections. The code attempts to reconnect if the closure isn’t a normal termination (status code 1000).
  • Graceful Shutdown:


    We hook into SIGINT and SIGTERM to ensure that the WebSocket is properly closed (using status code 1000) before the process exits.

  • Dynamic Subscription (Optional):


    While the combined stream URL fixes your subscriptions at connection time, Binance also supports JSON‑based commands to subscribe or unsubscribe from streams using a single connection. This can be useful for dynamic subscription management.


5. Summary

In this tutorial, we demonstrated how to:

  • Use Binance’s combined mini ticker WebSocket stream to receive real‑time OHLCV data for selected symbols.
  • Leverage Node.js’ native WebSocket API, available in Node.js v20 or later, together with TypeScript.
  • Handle reconnections and errors robustly.
  • Clean up resources gracefully when your script terminates.

Using this setup, you can build applications that rely on accurate, live market data streamed directly from Binance. Happy coding!


Feel free to extend or modify this example—for instance, by adding exponential backoff for reconnections or further processing of the mini ticker data—to suit your project’s requirements.

Top comments (0)