Build a live-updating stock price badge for your web
Tags: webcomponents, tutorial, node, stocks
Published: 2022-09-06 19:47:33


[Yuzu API](https://yuzu.dev) is a free market data API that can power UIs of any size: from huge sprawling webapps to drop-in UI components. 

In this example we'll focus on powering something on the small scale: a portable web component that renders a badge with live-updating stock prices, like this:



![Live badge example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqrrndlutyg3traylpv0.gif)


> If you'd rather skip to the good part, my friend Steve created a [GitHub Repo with our demo code](https://github.com/yuzu-hq/stock-price-badge). You can drop it right into your existing page!

---

Since we want this component to be fully portable and framework-agnostic, we'll be using [Lit](https://lit.dev/) + Typescript to create a native Web Component. This guide assumes you have the following tools installed:

- Node.js
- Yarn

To get started, we'll use yarn to create a project for us.

```
$ yarn create vite yuzu-stock-price-badge --template lit-ts
$ cd yuzu-stock-price-badge
$ yarn install`
```

> The code above creates a new project folder using [Vite](https://vitejs.dev/) as the build tool, then moves into the directory and installs dependencies.

Once we're in our new project and have run a fresh `yarn install`, let's do a few things.

1. Vite has created one component for us at `src/my-element.ts`. Let's rename it to `yuzu-price-badge.ts`.

2. In `src/yuzu-price-badge.ts`, let's replace all occurences of `my-element` with `yuzu-price-badge`.

3. In `package.json`, `index.html`, and `vite.config.ts`, go ahead and do the same thing, replacing all usages of `my-element` with `yuzu-price-badge`.

4. Run `yarn build` and make sure everything looks ok!

**If that sounds like a lot of work, you can just run these commands in your terminal instead:**

```
mv src/my-element.ts src/yuzu-price-badge.ts
sed -i '' 's/my-element/yuzu-price-badge/g' vite.config.ts \
  src/yuzu-price-badge.ts \
  package.json \
  index.html
sed -i '' 's/MyElement/YuzuPriceBadge/g' src/yuzu-price-badge.ts
```

Next, let's clean out all the boilerplate from `src/yuzu-stock-price-badge.ts`

The file should look about this this when you're done:

**src/yuzu-price-badge.ts**

```
import { html, css, LitElement } from 'lit'
import { customElement, property, state } from 'lit/decorators.js'

/**
 * Yuzu stock price badge
 */
@customElement('yuzu-price-badge')
export class YuzuPriceBadge extends LitElement {
  static styles = css`
    :host {
    }
  `

  render() {
    return html``
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'yuzu-price-badge': YuzuPriceBadge
  }
}
```

Ok, now that we're at a good baseline, let's add some properties to our new component, just above our `render` method:

**src/yuzu-price-badge.ts**

```
  /**
   * Your Yuzu API key, exposed in HTML as "api-key"
   */
  @property({ type: String, attribute: "api-key" })
  apiKey = "";

  /**
   * The symbol to render
   * E.g. AAPL, MSFT, BRK.A
   */
  @property({ type: String })
  symbol = "";
  
  /**
   * Hold the last price of our stock
   */
  @state()
  private lastPrice: number = 0;
```

Our two new properties, `apiKey` and `symbol` will be exposed in HTML to change what's rendered in this badge. The third property, `lastPrice` is a bit of internal state that will hold the last known price of our stock. Let's go to `index.html ` and update our component.

**index.html**
```
 <body>
    <!-- Replace "demo" with your own API key -->
    <yuzu-price-badge api-key="demo" symbol="BYND">
    </yuzu-price-badge>
  </body>
```

Great! Now, we need to fetch the last price of our stock when the component loads. We can override the `connectedCallback `function from `LitElement` to do that. Let's put our `connectedCallback` between our new properties and the `render `method.

**src/yuzu-price-badge.ts**

```
  async connectedCallback() {
    super.connectedCallback();
  }
```

Next, we're going to do two things:

- Make a GraphQL request to grab the last price of the stock.
- Subscribe to the live price stream for our stock, and update our internal state when the price changes.

> Because Yuzu's streaming APIs only returns data on stocks during market hours, it's good practice to use the GraphQL API to fetch any data you need to render your UI, and then subscribe to subsequent updates.

Let's add the following code to our `connectedCallback` to make the GraphQL request:

```
async connectedCallback() {
    super.connectedCallback();

    // Visit https://graph.yuzu.dev/graphql to see docs on the whole graph
    const query = `query {
      securities(input: { symbols: ["${this.symbol}"] }) {
        lastTrade {
          price
        }
      }
    }`
    
    const graphQlResult = await fetch("https://graph.yuzu.dev/graphql", {
      method: "POST",
      body: JSON.stringify({
        query
      }),
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': "application/json",
      }
    });
    const { data } = await graphQlResult.json();

    this.lastPrice = parseFloat(data.securities[0].lastTrade.price);
  }
```
Great! Here's a quick breakdown of what's happening above:

1. Construct the body of our GraphQL query, requesting securities matching our symbol, and on that object, the `price` field of that security's last trade.

2. Using the Fetch API to make the request, parsing the result, and assigning the value to `this.lastPrice`.

One last step before we can see the fruits of our work: rendering `lastPrice`. Let's modify `render()` to show our symbol and the saved price.

```
  render() {
    return html`
    <p>${this.symbol}</p>
    <p>$${this.lastPrice.toFixed(2)}</p>
    `
  }
```

Now, run `yarn dev` in your terminal and open up `localhost:3000` in your browser. You should see the last price of BYND.

All we need to do now is add some liveness! Thankfully this is trivial using Yuzu streams and the `EventSource` API. Let's add the following code to the bottom of `connectedCallback()`:

```
async connectedCallback() {
    /*
    ... Our GraphQL querying code
    */

    const stream = `S:T:${this.symbol}`;
    const url = `https://sse.yuzu.dev/sse?streams=${stream}&token=${this.apiKey}`;
    const tradeStream = new EventSource(url);
    tradeStream.addEventListener("message", (message) => {
      this.lastPrice = parseFloat(JSON.parse(message.data).price);
    });
  } 
```

This is just a few lines of code, but there's a lot of power behind them. Here's what's happening:

1. We construct a URL pointing at [Yuzu's server-sent events API](https://docs.yuzu.dev/live-streaming/server-sent-events), including our API token and the stream name `S:T:BYND`, which asks for every trade of the Security `BYND`

2. Add an event listener to the `EventSource` which parses each message, and assigns the value of `price` to our internal state, causing the component to re-render.

Now, if you go back to your browser, you'll see the price updating in real-time as long as the markets are open.

Now, all that's left to do is add some styling!

```
static styles = css`
    :host {
      padding: 0.3rem 0.5rem;
      display: inline-flex;
      flex-direction: row;
      gap: 0.5rem;
      border: 2px solid black;
      border-radius: 99rem;
      background-color: gainsboro;

      font-family: sans-serif;
      font-weight: bold;
      font-size: 1rem;
    }

    p {
      margin: 0;
    }
  `
```

And there have it! [Check out our example repo](https://github.com/yuzu-hq/stock-price-badge) for a ready-made version you can drop right into your existing page.