DEV Community

Cover image for Realtime Chart with Supabase and Tremor
György Márk Varga for Shiwaforce

Posted on

Realtime Chart with Supabase and Tremor

In our fast-paced world, users of our application want to get information as soon as possible. What could be faster than "real time"? Let's see how we can build a "real-time dashboard" component. First, we should check out how the implementation of a line chart looks like, and then it will be easy to do anything else with this stack, even putting together a complete real-time dashboard.

What will we build?

So, as I mentioned, we will start by constructing a line chart that will be updated in real time. This line chart represents prices in specific time (for example a stock prices in the market):

Image description

The technologies that we use

If you haven't heard of Supabase yet, I'll describe it in a few sentences so that you can get to know it better:

Supabase is an open-source backend-as-a-service platform that provides developers with a real-time database, authentication, and storage solutions, built on top of PostgreSQL. It offers a seamless integration with modern frameworks, allowing developers to easily create, manage, and deploy full-stack applications without needing to manage server infrastructure. Supabase is often compared to Firebase, but with the advantage of being open-source and SQL-based.

Implementation

Let's give ourselves a big advantage by using Supabase's Next.js starter project, which we can access HERE. This saves us a lot of settings. We can already create the project with this command:

npx create-next-app -e with-supabase

Enter fullscreen mode Exit fullscreen mode

After this has been issued, we can continue with the default settings. I named my project "realtime-chart".
There are some settings we need to make. An example of this is rewriting the .env.local.example file to .env and replacing the two Supabase API key values:

NEXT_PUBLIC_SUPABASE_URL=[INSERT SUPABASE PROJECT URL]
NEXT_PUBLIC_SUPABASE_ANON_KEY=[INSERT SUPABASE PROJECT API ANON KEY]
Enter fullscreen mode Exit fullscreen mode

We will be able to get this by creating a Supabase project.
We can easily do this HERE. I also named my Supabase project "realtime-chart" for the sake of simplicity.

We will access our API keys through the "Project settings" and within it the "API" point. We will find these two keys here:

Image description

If we have copied them, we can start the application with the dev command in package.json.

When we start and look at the project for the first time, we can see a lot of extra things (e.g. authentication) that we won't necessarily need, at least for this small example project (of course, we can put them in easily enough later). As you can see in the source code on Github, I stripped everything down enough to focus only on the real time component.

If we return to the Supabase admin interface, in the left menu bar, if we click on Table Editor, the interface will appear where we can add a new database table to our project. Click on the green "Create a new table" button, then fill out the form as follows:

Image description

What will be interesting for us here is the "Enable Realtime" checkbox, which we need to tick.

This “Realtime” function works as follows:

Supabase’s real-time functionality with PostgreSQL works by listening to changes in the database, such as when new data is added or existing data is updated. It uses PostgreSQL’s built-in capabilities to capture these changes and instantly sends updates to connected clients (like your web or mobile app). This means that whenever something in the database changes, your app can immediately react and display the latest information without needing to refresh or manually check for updates.

Now that we understand how it works, let's take a look at how we will implement it. Let's go back to our project and install the appropriate packages.

Let's start with Tremor first. We will use Tremor to display the data in the form of a chart. It is described in great detail in the docs that in order to be able to upload it to a Nextjs project, we should follow this description.

We need to install Tremor first:

npm install @tremor/react
Enter fullscreen mode Exit fullscreen mode

After that, we need to update Tailwind (this step is probably not even necessary, because we installed the latest Tailwind version by default, but we can do it anyway):

npm install tailwindcss@latest
Enter fullscreen mode Exit fullscreen mode

Then we upload the headless ui:

npm install @headlessui/react
Enter fullscreen mode Exit fullscreen mode

Then the Tailwind css headless ui plugin:

npm install @headlessui/tailwindcss
Enter fullscreen mode Exit fullscreen mode

The tailwind css form:

npm install -D @tailwindcss/forms
Enter fullscreen mode Exit fullscreen mode

Remix icon is also required:

npm install @remixicon/react
Enter fullscreen mode Exit fullscreen mode

And the last and most important step is to modify our tailwind.config.ts file based on the docs.

If we are satisfied with this, we are ready to code and to implement the real time feature in our chart.

First, let's go to the app/page.tsx file, where our page will look like this:

import { createClient } from "@/utils/supabase/server";
import RealtimeChart from "@/components/realtime-chart";

export default async function Index() {
    const supabase = createClient();

    const { data, error } = await supabase.from("chart_data").select();

    if (error) {
        console.error("Error fetching data:", error);
        return (
            <div className="min-h-screen flex items-center justify-center bg-gray-100">
                <p className="text-red-600 text-lg font-semibold">Error loading data</p>
            </div>
        );
    }

    return (
        <div className="min-h-screen flex flex-col w-full items-center bg-gray-50">
            <main className="flex-1 w-full flex flex-col items-center py-10">
                <div className="max-w-4xl w-full px-6">
                    <h2 className="text-3xl p-4 font-bold text-gray-800 text-center">Real Time Chart</h2>
                    <div className="bg-white rounded-lg shadow p-6">
                        <RealtimeChart serverPrices={data} />
                    </div>
                </div>
            </main>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Here we need the Supabase client. This will allow us to start our database query. And the data is passed to a component that we haven't implemented yet. This will be a client component and will be responsible for displaying our data visually.

So let's create a components folder, and then the realtime-chart inside it. The code of this component is also quite simple, let's look at it:

'use client';

import { useEffect, useState } from "react";
import { createClient } from "@/utils/supabase/client";
import { LineChart } from '@tremor/react';

interface PriceItem {
    id: number;
    created_at: string;
    price: number;
}

interface RealtimeChartProps {
    serverPrices: PriceItem[];
}

export default function RealtimeChart({ serverPrices }: RealtimeChartProps) {
    const supabase = createClient();
    const [prices, setPrices] = useState<PriceItem[]>(serverPrices);

    useEffect(() => {
        const channel = supabase.channel("realtime chart data").on('postgres_changes', {
            event: 'INSERT', schema: 'public', table: 'chart_data'
        }, (payload) => {
            console.log(payload);
            setPrices((prevPrices: PriceItem[]) => [...prevPrices, payload.new as PriceItem]);
        }).subscribe();

        return () => {
            supabase.removeChannel(channel);
        };
    }, [supabase]);

    const formatTime = (dateString: string) => {
        const date = new Date(dateString);
        return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
    };

    const chartData = prices.map((item: PriceItem, index: number) => ({
        date: formatTime(item.created_at) || `Point ${index + 1}`,
        Price: item.price,
    }));

    return (
        <LineChart
            className="h-80 w-full"
            data={chartData}
            index="date"
            categories={["Price"]}
            showAnimation={true}
            valueFormatter={(number) =>
                `$${Intl.NumberFormat("us").format(number)}`
            }
        />
    );
}
Enter fullscreen mode Exit fullscreen mode

Let's see exactly what's going on here. First, let's define the type. By the way, we can do this automatically with Supabase.
For now, the manual solution will be good for us for this small project.

The soul of the entire component is the code section in useEffect. Here we subscribe to the INSERT event and add the new price to the existing prices, which we insert into the database, so every time a new price (or exchange rate) is entered into the data table, it will also be visible on the graph in real time, without reloading the browser window.
In the following sections, we will perform different formatting to make the values ​​appear aesthetically on the graph.
And part of the template will be a LineChart component. We will pass the appropriate data to it.

Before we try it out, let's set another thing: in the Supabase interface, under Authentication, Policies, we set "public can read chart data".

Image description

If we are done with this, then we fill the database with data.
We can do this easily in the Table editor. Add 6-8 records so that the data looks good on the graph.

We should see something like this in our browser:

Image description

Now we also test whether this chart really provides real-time data display. Place the browser tab containing the chart and the tab for editing the Supabase table next to each other. If we insert a current price into the table, we can also see that our chart is updated in real time.

We can subscribe not only to INSERT, but also to other database events, such as UPDATE or DELETE. So the possibilities are endless.

I hope this helped to better understand the fact that there are not only static charts that can be accessed after an browser refresh, but also that we can display data here dynamically with the help of Supabase and Tremor.

How can we improve it?

Of course we can improve it in a lot of ways. For example Tremor offers you the raw components of the charts. We can get those raw components and modify it that it behaves in the way that only the end of the charts getting the animation, not the whole chart when a new price point is added. Feel free to do this and open a Merge request on Github!

If you have any questions, please head to the comment section or find me on X!

And of course (as always) the source code is available on my Github, feel free to try it out and use it! :)

Top comments (0)