Build a React pub-sub stock ticker using STOMP and WebSockets
Asynchronous APIs and event driven design are part of the future of applications.

Watch the video


This is quick tutorial to help you understand the basic mechanics of how Pub-Sub works and how you can call an asynchronous API from a browser using JavaScript, STOMP, and WebSockets. We’re going to build a simple

If you’re not familiar with STOMP, it stands for (S)imple (T)ext (O)riented (M)essaging (P)rotocol. It’s a straightforward but stable protocol that makes interacting with a message or event-based APIs a breeze.


We will use a library encapsulating the protocol. We’re going to use Transport for this tutorial. It’s an asynchronous API framework implemented in multiple languages that supports STOMP out of the box.

Our application is going to be a simple stock ticker. We will type in a stock symbol and request it from a StockTicker Service running for real over at transport-bus.io via WebSocket.

The service will respond with details about the latest closing price for that stock symbol, and our UI will render it out.


1. Create our new react application using create-react-app

Let’s keep it simple and create a new react application using npx create-react-app stock-ticker

Change directory into the ‘stock-ticker’ directory, and now you can install Transport (JavaScript).

Transport only has one significant dependency, RxJS, so let’s install it via yarn add rxjs

Now install Transport using yarn add @vmw/transport.

To stop any TypeScript warnings, add Typescript (but it’s not required) yarn add typescript


2. Create a custom hook to access event bus, and boot it.

Create a new file named ‘transport.js’. Here we will create a custom hook called useTransport. It’s a simple way for components to access the singleton event bus.

import { BusUtil } from "@vmw/transport/util/bus.util"

export function useTransport() {
    return BusUtil.getBusInstance();
}

Next, we import BusUtil and LogLevel from Transport into our “index.js” file. We will use them to boot the event bus properly and set the logging level correctly.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BusUtil } from "@vmw/transport/util/bus.util"
import { LogLevel } from "@vmw/transport/log/logger.model"

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// boot bus and set logging levels.
const bus = BusUtil.bootBus();
bus.api.setLogLevel(LogLevel.Verbose);
bus.log.useDarkTheme(true); // disable if you're not using dark mode in your browser.

3. Connect to message broker in App.js

create-react-app” will generate some example code here that we don’t need, so let’s clear out the generated markup.

Let’s import useState and useEffect hooks from react, and let’s use our custom hook useTransport to get a reference to our event bus.

import { useState, useEffect } from "react"
import { StockTicker } from "./StockTicker";
import { useTransport } from "./transport";
import './App.css';

function App() {
  const bus = useTransport();

We want to track if we’re connected, so let’s create a new state variable called connected and set it to false.

  const [connected, setConnected] = useState(false);

Next, we hook into the component lifecycle with useEffect. Let’s use bus.fabric.connect and provide two closures.

  // connect to broker if we're not connected
  useEffect(
    () => {
      if (!connected) {
        bus.fabric.connect(
          () => {
            // handle success
            console.log("application has connected to the broker");
            setConnected(true);
          },
          () => {
            // handle failure
            console.log("application has disconnected from the broker");
            setConnected(false);
          },
          "transport-bus.io",
          443,
          "/ws",
          true,
          "/topic",
          "/queue"
        );
      }
    }
  );

Now we can put in a new page title and then get cracking on our StockTicker component.

  return (
    <div className="App">
        <h1>Stock Lookup</h1>
        <StockTicker />
    </div>
  );
}

export default App;


4. Create StockTicker component

Create a new file named “StockTicker.js” and then import what we need:

import { useState, useEffect } from "react";
import { useTransport } from "./transport";
import { GeneralUtil } from "@vmw/transport/util/util";

Now we can create our StockTicker component.

// stock ticker component
export function StockTicker() {

Let’s define the state we’re going to need for this component.

    // define hooks
    const [stockLookup, setStockLookup] = useState("GOOG");
    const [closePrice, setClosePrice] = useState(null);
    const [lastRefreshed, setLastRefreshed] = useState(null);
    const [symbol, setSymbol] = useState(null);
    const [stockError, setStockError] = useState(null);
    const [subscribed, setSubscribed] = useState(false);

Next, we get a reference to our bus, define the channel we’re subscribing to and then generate a connection string.

    // grab a reference to our event bus
    const bus = useTransport();
    
    // define the channel our stock service is operating on.
    const stockChannel = "stock-ticker-service";

    // create a connection string to identify our broker
    const connectionString = GeneralUtil.getFabricConnectionString(
        "transport-bus.io",
        443,
        "/ws"
    );


4.1 Implement useEffect and subscribe to the stock ticker channel

Implement useEffect and subscribe to our broker destination.

    // if we are subscribed, then we do nothing, if not... we subscribe!
    useEffect(
        () => {
            if (!subscribed) {
                // subscribe to service by marking the channel as galactic.

If we haven’t yet subscribed, we use the markChannelAsGalactic method, and it automatically subscribes the client to that destination on the broker. We’re marking this channel as private, which means it will use a queue and be a one-to-one conversation.

                bus.markChannelAsGalactic(stockChannel, connectionString, true);
                setSubscribed(true);
            }

Then, we can return a function that will use the markChannelAsLocal method to un-subscribe from the destination when the component is unmounted.

            return () => {
                // all done, unsubscribe
                bus.markChannelAsLocal(stockChannel);
            };
        }, [subscribed, bus, connectionString]
    );

4.2 Implement handleSymbolChange function

Next, let’s implement the handleSymbolChange function that will fire when the component form input changes. We set the stockLookup state using our event’s target.value property.

    function handleSymbolChange(e) {
        setStockLookup(e.target.value);
    }

4.3 Implement requestStock function

If we look at the AsyncAPI contract for this service and look at the publish command and have a look at the example, we can see that the payload of our message is an object that contains a single property named symbol.

We should note that the operation ID is ticker_price_update_stream. Operation IDs are what Transport calls commands.

Let’s create a new request via the generateFabricRequest method.

 function requestStock() {
        let request = bus.fabric.generateFabricRequest(
            "ticker_price_update_stream",
            { symbol: stockLookup }
        );

Now we can define the logic that will send our request over the bus to our stock ticker service that is listening at transport-bus.io on channel “stock-ticker-service”.

        // make the request over the bus and over to our broker at transport-bus.io
        bus.requestOnce(stockChannel, request).handle(
            (response) => {
                // success
                if (response.payload) {
                    setClosePrice(response.payload.closePrice);
                    setLastRefreshed(response.payload.lastRefreshed);
                    setSymbol(response.payload.symbol);
                } else {
                    setStockError("nothing returned by service");
                }
            },
            (error) => {
                // error
                setStockError(error.errorMessage);
            }
        );


4.4 Return StockTicker JSX

    // return our component JSX
    return (
        <div>
            <label>Ticker Symbol: </label>
            <input type="text" value={stockLookup} onChange={handleSymbolChange} />
            <button onClick={requestStock}>Get Price!</button>

            <StockError errorMessage={stockError} />
            <StockResult
                closePrice={closePrice}
                symbol={symbol}
                lastRefreshed={lastRefreshed}
            />
        </div>
    );

5. Create StockResult and StockError Components

The StockError component only renders if the props contain an errorMessage property.

function StockError(props) {
    if (props.errorMessage) {
        return (
            <div>
                <hr />
                <h3>Sorry! the service issued an error</h3>
                <p> {props.errorMessage} </p>
            </div>
        );
    }
    return null;
}

Lastly, we define our StockResult component. Let’s limit the price value to two decimal points to make it simpler to read. Then we can also extract the lastRefreshed and symbol properties.

function StockResult(props) {
    let price = props.closePrice?.toFixed(2);
    let lastRefreshed = props.lastRefreshed;
    let symbol = props.symbol;

    if (price && lastRefreshed && symbol) {
        return (
            <div>
                <hr/>
                Symbol: <strong>{symbol}</strong>
                <br/>
                Price: <strong>{price}</strong>
                <br/>
                Last Refreshed: <strong>{lastRefreshed}</strong>
            </div>
        );
    }
    return null;
}

6. Run the application!

Let’s run our application. In our terminal window, use npm start and our application should appear.

Image of our Stock Ticker UI application running in a browser, showing a form input saying 'GOOG' and a button saying 'Get Price'.
It looks crap yes, but it’s the mechanics we care about in this tutorial

7. Take a look at the code.

You can find all the code in this tutorial on GitHub

  1. App.js
  2. StockTicker.js
  3. transport.js
  4. index.js

Also, you can Try out the stock ticker online.