Yuzu API 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:
If you'd rather skip to the good part, my friend Steve created a GitHub Repo with our demo code. 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 + 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 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.
Vite has created one component for us at
src/my-element.ts
. Let's rename it toyuzu-price-badge.ts
.In
src/yuzu-price-badge.ts
, let's replace all occurences ofmy-element
withyuzu-price-badge
.In
package.json
,index.html
, andvite.config.ts
, go ahead and do the same thing, replacing all usages ofmy-element
withyuzu-price-badge
.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:
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.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:
We construct a URL pointing at Yuzu's server-sent events API, including our API token and the stream name
S:T:BYND
, which asks for every trade of the SecurityBYND
Add an event listener to the
EventSource
which parses each message, and assigns the value ofprice
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 for a ready-made version you can drop right into your existing page.
Top comments (0)