Building a realtime cryptocurrency price tracker

Learn how to work with Aurelia's reactive binding system to work with frequent data changes.

Welcome to the Aurelia crypto price checker tutorial. In this tutorial, you will learn how to build an Aurelia 2 application that works with the Coingecko price API to get cryptocurrency prices and refresh them when they change.

Just want the code? You can find the code for this tutorial on GitHub here. Feel free to use this code as a guide or starting point for your Aurelia applications.

What we will be building

A cryptocurrency dashboard that updates in real-time whenever the price of cryptocurrencies changes. Think of it as a stock ticker of sorts, but for cryptocurrency.

We will be interacting with the very generous Coingecko API to get the price data which allows 10 calls per second. For this tutorial, we will be calling the API once per second.

This tutorial will teach you how to work with Aurelia's binding system, briefly touch on Dependency Injection, show you how to make an API request, organize your code and create a value converter to format some currency values for display.

A demo of the application you will be building can be found here.

Prerequisites

Before going any further, you should be familiar with some basic Aurelia concepts and some fundamental Javascript ones. While these are not hard prerequisites, please know that some concepts used in this tutorial out of context might be confusing or difficult to understand.

  • You have familiarized yourself with the Aurelia template syntax.

  • You are familiar with component lifecycles (which we will use binding in this tutorial).

  • You are familiar with Aurelia value converters and how they can be used to transform data.

  • You are familiar with Dependency Injection. You don't need to be a master of it, just familiar with its existence and why it matters in Aurelia.

  • You are familiar with async/await syntax. A great resource for learning can be found here.

Create the app

We will use TypeScript & Vite for this tutorial, enabling Shadow DOM to keep our styles encapsulated. Don't worry if you're unfamiliar with these concepts yet; you will learn as we go.

For this, we will use the Aurelia makes command-line tool to create a new Aurelia application and to save time, passing in the options we want.

npx makes aurelia crypto-prices -s typescript,shadow-dom

This shorthand syntax tells the Aurelia CLI to create a new project configured with TypeScript and Shadow DOM for style encapsulation.

Create our configuration file

We will now create a .json file containing the cryptocurrencies we want to monitor.

Inside the src directory, create a new file called config.json and save it. We will watch six cryptocurrencies for this tutorial, but feel free to add more if you like.

{
    "coins": [
        "bitcoin",
        "ethereum",
        "tether",
        "binancecoin",
        "dogecoin",
        "cardano",
        "ripple"
    ]
}

This is just a JSON object with an array of cryptos called coins. Feel free to add your own cryptocurrencies to this file. Make sure they conform to the coin list on Coingecko.

Create an API service

A good practice when working with APIs in Aurelia is to create a service (a singleton class) that makes the calls to the API and returns the data. This keeps the logic separate from our view models. It also allows it to be tested and is just a good habit to get into.

Create a new directory inside of src called services and then create a new file called api.ts. Let's inject the Aurelia Fetch Client and write a method to fetch the prices.

import { IHttpClient } from '@aurelia/fetch-client';
import { inject } from 'aurelia';

@inject(IHttpClient)
export class Api {
    constructor(private http: IHttpClient) {
        this.http.configure(c => {
            c.withBaseUrl('https://api.coingecko.com/api/v3/');

            return c;
        })
    }

    async getPrices(ids: string[]): Promise<unknown> {
        try {
            const request = await this.http.fetch(`simple/price?ids=${ids.toString()}&vs_currencies=usd`);

            return request.json();
        } catch (e) {
            return e;
        }
    }
}

Let's go over this line-by-line. Firstly, this is TypeScript, which looks/works very similar to Javascript, except it aims to provide some safety by alerting you to simple mistakes or errors before you build or deploy them.

  • We import the IHttpClient interface we will inject into our app on the constructor method. The benefit of using TypeScript is that you get auto-injection instead of injecting the client manually. The Aurelia Fetch Client wraps the native Fetch API and makes it more "Aureliafied".

  • We create a getPrices method, which accepts an array of strings (our cryptocurrencies). We make this method async to make working with the promise that Fetch returns a lot cleaner than chaining .then and .catch functions in our code.

  • When dealing with async/await, it is good practice to wrap your calls in a try/catch to catch any errors that might occur (timeouts, erroneous requests, missing credentials).

  • We then request the Coingecko API, passing in our cryptocurrencies. By calling the Array.toString() method, it will automatically create a comma-separated string of values as the API expects. You could also use .join to do this as well.

  • When making Fetch requests, the resulting Fetch call with allow us to get the response value, we know we are going to be getting JSON, so we return the request.json() call, which is a promise.

As for errors, if we encounter them, the catch will capture those, and we return the error value. We have everything we need now.

Inject our service into the app

Because we used the makes tool by default, my-app.ts and my-app.html will be the view model and view that are displayed. We will use these files to add in our API calls and other pieces of logic.

Inside of my-app.ts replace the contents with the following:

import { Api } from './services/api';

import config from './config.json';
import { inject } from 'aurelia';

@inject(Api)
export class MyApp {
  private prices = {};

  constructor(private api: Api) {

  }

  async binding(): Promise<void> {
    this.prices = await this.api.getPrices(config.coins);

    this.priceUpdate();
  }

  priceUpdate(): void {
    setInterval(async () => {
      this.prices = await this.api.getPrices(config.coins);
    }, 1000);
  }
}
  • We import our newly created API service as well as the config.json file we created in the first step as config

  • We initialize an empty object in our class called prices which will store our price data

  • We inject the API and define it on our constructor

  • We specify a component lifecycle hook called binding which we make asynchronous

  • Inside of binding we make a call to our getPrices method and store the value in our class property prices

  • The returned value is an object

Displaying the data

We now have our crypto prices. Let's display them. Inside of my-app.html (our view for the app) we'll reference these price values.

<div class="panels">
    <div class="panel"><h4>Bitcoin</h4> ${prices.bitcoin.usd}</div>
    <div class="panel"><h4>Ethereum</h4> ${prices.ethereum.usd}</div>
    <div class="panel"><h4>Tether</h4> ${prices.tether.usd}</div>
    <div class="panel"><h4>Binance Coin</h4> ${prices.binancecoin.usd}</div>
    <div class="panel"><h4>Dogecoin</h4> ${prices.dogecoin.usd}</div>
    <div class="panel"><h4>Cardano</h4> ${prices.cardano.usd}</div>
    <div class="panel"><h4>Ripple</h4> ${prices.ripple.usd}</div>
</div>

This will look familiar if you have brushed up on Aurelia's template syntax. We are using interpolation to display values, but do you notice something? Prices are the object we defined inside of our view model. We are then accessing each property of this object inside of the view.

We know that the value inside of ${} is evaluated by Aurelia. In this instance, we are referencing our prices object and we know for each cryptocurrency we are watching that it returns a property followed by another child property for the price called usd.

Updating the data

We have a working application of sorts. It's not pretty, but it's functional. We have a problem, though. Any price updates after the page loads won't be rendered. Because these are price values, we want to ensure the user always has the most up-to-date information. We need to tell our view model to update the prices for us regularly.

priceUpdate() {
    setInterval(async () => {
        this.prices = await this.api.getPrices(config.coins);
    }, 1000);
}

And add the priceUpdate() call to binding function.

async binding() {
    this.prices = await this.api.getPrices(config.coins);

    this.priceUpdate();
}

We poll the Coingecko API every second to update prices and store them in the prices object. If these prices change, they are updated in the view because everything is reactively bound. Aurelia is watching this object and the properties we've bound to in our view. We use a basic setInterval to poll the server continuously. Feel free to adjust the frequency.

Seeing what we have so far

We have the basis of a functional web application that we can now run. Open up a Command Prompt/Terminal window and navigate to your project directory, then run the start command:

npm run start

This will run the Aurelia development server, and a browser window/tab should open with your application running on the default port 9000. Provided the Coingecko API is working, you should see unformatted prices in the view.

Making currencies look pretty using a value converter

You might have noticed our prices are displayed, but they're not formatted. At the time of this tutorial, the price of 1 Bitcoin was USD 34,354.00. However, it is being displayed without currency formatting as 34354.

So, now we will create a value converter using the Internationalization API to format our currency values as properly formed values in our view.

Inside of src create a new folder called resources and then inside of resources another folder called value-converters. It is best practice to have your resources inside a resources/components folder like this. Create a new file called currency-value-converter.ts.

You can read about value converters here to understand what this value converter is doing.

import { valueConverter } from 'aurelia';

@valueConverter('currency')
export class CurrencyValueConverter {
    toView(value) {
        if (!value) {
            return value;
        }

        const formatter = new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: 'USD'
        });

        return formatter.format(value);
    }
}

We will not be going into detail about the Internationalization API. Still, it is highly recommended you read up on it as this is intended to be the standard way to work with dates, times, currencies and other types of localization data on the web.

Import and use our new value converter

To use this, we have to register it with Dependency Injection. This tells Aurelia our value converter exists, and we have several options. In this tutorial, we will be including it from within our view using the import element, but you can also register it inside of the view model or globally inside of main.ts.

If you want to learn other ways to inject dependencies, consult the documentation on components.

Open up my-app.html again and add in the following:

<import from="./resources/value-converters/currency-value-converter"></import>

<div class="panels">
    <div class="panel"><h4>Bitcoin</h4> ${prices.bitcoin.usd | currency}</div>
    <div class="panel"><h4>Ethereum</h4> ${prices.ethereum.usd | currency}</div>
    <div class="panel"><h4>Tether</h4> ${prices.tether.usd | currency}</div>
    <div class="panel"><h4>Binance Coin</h4> ${prices.binancecoin.usd | currency}</div>
    <div class="panel"><h4>Dogecoin</h4> ${prices.dogecoin.usd | currency}</div>
    <div class="panel"><h4>Cardano</h4> ${prices.cardano.usd | currency}</div>
    <div class="panel"><h4>Ripple</h4> ${prices.ripple.usd | currency}</div>
</div>

This looks the same as earlier, except we are importing our new value converter. You might also notice we are using the value converter syntax with a pipe | followed by currency which is the name of our value converter.

The app should automatically refresh with the new changes if you still have the app running. You should now see properly formatted currency values (complete with a dollar sign and thousand separators).

Styling it with Bootstrap

We could have ended the tutorial in the previous step, but aren't you itching to make it look nicer? Using Bootstrap 5 and some simple markup, we can.

To install Bootstrap in the project directory run npm install bootstrap

Configuring Shared Styles for Shadow DOM

Unlike conventional web applications, we use Shadow DOM to keep styles encapsulated. You must make them shared styles when working with third-party global CSS libraries like Bootstrap.

Fortunately, Aurelia already helps cater for this in your apps. Inside of main.ts you will notice some commented-out code and notes talking about shared styles.

We need to import Bootstrap's CSS first at the top of the file:

import bootstrap from 'bootstrap/dist/css/bootstrap.css?inline';

Using ?inlineis only needed for the Vite bundler and ShadowDOM. This allows us to add shared styles into our application.

Now, uncomment the .register code already present in main.ts. Also, make sure you replaced shared with bootstrap and uncomment the StyleConfiguration import from the aurelia package at the top as well.

Your main.ts file should look pretty much identical to this one:

import Aurelia, { StyleConfiguration } from 'aurelia';
import { MyApp } from './my-app';

// @ts-expect-error - This is a CSS file
import bootstrap from 'bootstrap/dist/css/bootstrap.css?inline';

// Convert the CSS string to CSSStyleSheet
const sheet = new CSSStyleSheet();
sheet.replaceSync(bootstrap);

Aurelia
  .register(StyleConfiguration.shadowDOM({
    // optionally add the shared styles for all components
    sharedStyles: [sheet]
  }))
  .app(MyApp)
  .start();

This now injects Bootstrap styles into all of our Shadow DOM components. The only downside is the global styles targeting elements like html or body do not get carried over (an intentional limitation of Shadow DOM).

However, you can fix this by adding the Bootstrap CSS into the header of our index.html if you want those. Or, you can add them yourself. If you want them, add the Bootstrap CDN include in the header of index.html. You can get the most up-to-date style include here.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

Update the markup

Now we have Bootstrap added. It's time to update the markup in my-app.html to add the data into a styled table. This is all purely markup, so we are not using any new Aurelia concepts here. You can safely copy and paste this.

<import from="./resources/value-converters/currency"></import>

<div class="container">
    <h1 class="mt-5">Crypto Prices</h1>

    <table class="table table-striped mt-5">
        <thead class="table-dark">
            <tr>
              <th scope="col">Name</th>
              <th scope="col">Price (USD)</th>
            </tr>
          </thead>
        <tbody>
            <tr>
                <td><strong>Bitcoin</strong></td>
                <td>${prices.bitcoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Ethereum</strong></td>
                <td>${prices.ethereum.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Tether</strong></td>
                <td>${prices.tether.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Binance Coin</strong></td>
                <td>${prices.binancecoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Dogecoin</strong></td>
                <td>${prices.dogecoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Cardano</strong></td>
                <td>${prices.cardano.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Ripple</strong></td>
                <td>${prices.ripple.usd | currency}</td>
            </tr>
        </tbody>
    </table>
</div>

Conclusion

You just created an Aurelia application that utilized a few common concepts. All of the code for this tutorial can be found on GitHub here. Feel free to do whatever you want with this. Use it as a guide or even starting point for your applications.

  • Reactive binding using interpolation to display values

  • Aurelia's dependency injection to include a separate file that handled our API calls

  • How to transform values in your view using value converters (taking one value and making it something else)

  • How to work with Shadow DOM and, specifically, what to do in the case of global style packages like Bootstrap

  • How to work with the Fetch client to make requests as well as async/await to work with promises in a cleaner way.

A working example of this tutorial can be found here.

Last updated