Building a Sleep and Readiness Dashboard with the Oura API

by Pinta

4 min read

In today's fast-paced world, keeping an eye on our sleep and readiness scores can greatly impact our overall well-being. The Oura Ring provides valuable data on sleep quality, readiness, and more. In this guide, we'll explore how to build a Sleep and Readiness Dashboard using the Oura API, a library written primary for Deno, a next-generation JavaScript Runtime. We'll create a project that fetches Oura data and with Deno Fresh visualizes your sleep and readiness scores over the past 30 days.

An image showing site in all its glory

Prerequisites

Before we get started, this is not supposed to be a finished product, there are plenty of articles out there covering the content of creating beautiful and intuitive dashboards. This is an article about getting started with fetching and using Oura data with the Oura API.

Make sure you have the following set up:

Setting Up the Project

To keep this article short I have scaffold a Deno Fresh project, removed unwanted files and added a couple of small things to make the article easier to follow along. I have then uploaded that project to a public repository that you can easily use as a base. We want to quickly get you a working prototype that you can experiment further with. If you need further help or other questions you can find me on my Discord server.

  1. Clone the project repository:

    git clone https://github.com/Pinta365/oura_fresh.git
    

    Open the folder with your favorite editor, I use Visual Studio Code. Shows you to the commands and output of the git clone

  2. Create the following empty files:

    • /islands/chart.tsx
    • /lib/oura.ts
    • .env

    An image representing the folder structure opened in VS Code

Lets update some files

/deno.json

Add the following dependencies to the imports section of your deno.json file:

"oura": "https://deno.land/x/oura_api@0.3.1/mod.ts",
"$fresh_charts/": "https://deno.land/x/fresh_charts@0.3.1/"

.env

Replace your_token with your Oura API token:

TOKEN=<your_token>

/islands/chart.tsx

Replace the content of this file with:

export { Chart as default } from "$fresh_charts/island.tsx";

/lib/oura.ts

Replace the content of this file with:

import { Oura } from "oura";
const accessToken = Deno.env.get("TOKEN") || "";

interface OuraData {
    sleepData: {
        days: string[];
        scores: number[];
    };
    readinessData: {
        days: string[];
        scores: number[];
    };
}

/**
 * Fetch sleep and readiness data from the Oura API.
 * @param daysToGet Number of days to fetch data for.
 * @returns Promise containing OuraData.
 */
export async function getOuraData(daysToGet: number): Promise<OuraData> {
    const ouraClient = new Oura(accessToken);

    const end = new Date();
    const start = new Date();
    start.setDate(new Date().getDate() - daysToGet);

    try {
        const returnDocument: OuraData = {
            sleepData: {
                days: [],
                scores: [],
            },
            readinessData: {
                days: [],
                scores: [],
            },
        };
        //Get Sleep documents.
        const dailySleeps = await ouraClient.getDailySleepDocuments(
            start.toLocaleDateString(),
            end.toLocaleDateString(),
        );

        for (const item of dailySleeps.data) {
            const { score, day } = item;
            returnDocument.sleepData.days.push(day);
            returnDocument.sleepData.scores.push(score);            
        }

        //Get readiness documents.
        const dailyReadiness = await ouraClient.getDailyReadinessDocuments(
            start.toLocaleDateString(),
            end.toLocaleDateString(),
        );

        for (const item of dailyReadiness.data) {
            const { score, day } = item;
            returnDocument.readinessData.days.push(day);
            returnDocument.readinessData.scores.push(score); 
        }
        
        return returnDocument
    } catch (error) {
        // Just log the error in the console while developing.
        console.error(`Error fetching data: ${error}`);
        return <OuraData> {};
    }
}

/routes/index.tsx

Replace the content of this file with:

import { getOuraData } from "../lib/oura.ts";
import Chart from "../islands/chart.tsx";
import { ChartColors } from "$fresh_charts/utils.ts";

export default async function Page() {
    if (!Deno.env.get("TOKEN")) {
        return <h1>Can't find environment variable with Oura token.</h1>;
    }
    // Fetch 30 days of data.
    const daysToFetch = 30;
    const ouraData = await getOuraData(daysToFetch);
    // Check if we got data points, should probably implement better checks.
    if (ouraData.sleepData.scores.length <= 0) {
        return <h1>No sleep data.</h1>;
    }
    if (ouraData.readinessData.scores.length <= 0) {
        return <h1>No readiness data.</h1>;
    }

    return (
        <div class="container">
            <header>
                <h1>Oura Sleep and Readiness Score 🛌🏽</h1>
            </header>
            <main>
                <p>
                    This interacts with the Oura API and fetches your last
                    {daysToFetch} days of Sleep and Readiness Score.
                </p>
                <div class="grid">
                    <div>
                        <Chart
                            type="line"
                            options={{
                                interaction: {
                                    mode: "index",
                                    intersect: false,
                                },
                            }}
                            data={{
                                labels: ouraData.sleepData.days,
                                datasets: [
                                    {
                                        label: "Sleep score",
                                        data: ouraData.sleepData.scores,
                                        borderColor: ChartColors.Red,
                                        tension: 0.3,
                                    },
                                ],
                            }}
                        />
                    </div>
                    <div>
                        <Chart
                            type="line"
                            options={{
                                interaction: {
                                    mode: "index",
                                    intersect: false,
                                },
                            }}
                            data={{
                                labels: ouraData.readinessData.days,
                                datasets: [
                                    {
                                        label: "Readiness score",
                                        data: ouraData.readinessData.scores,
                                        borderColor: ChartColors.Blue,
                                        tension: 0.3,
                                    },
                                ],
                            }}
                        />
                    </div>
                </div>
            </main>
            <footer class="py-8">
                <small>
                    <p>
                        Connect with me on{" "}
                        <a href="https://discord.gg/J7QtVxAt6F">
                            this discord
                        </a>{" "}
                        for questions and discussions.
                    </p>
                </small>
            </footer>
        </div>
    );
}

Fetching and Visualizing Data

Now that our project is set up, let's fetch and visualize the data.

In the ./routes/index.tsx file, we're using the getOuraData function to fetch your sleep and readiness data for the last 30 days. This is where all the interaction with the Oura endpoints are happening. Check out the Docs for more information concerning fetching various types of Oura data. We then display this data using Deno Fresh's chart components.

Running the Project

To run the project, use the following command:

deno task start

An image showing the output when you issue the start command This command will start the Deno Fresh development server, and show you the address where you can connect to the site, default is http://localhost:8000. The fresh server will reload automatically when it detects changes to the source files.

An image showing site in all its glory

Conclusion

Congratulations! You've created a Sleep and Readiness Dashboard using the Oura API and Deno Fresh. This project allows you to monitor your sleep and readiness scores, helping you make informed decisions to improve your well-being.

Feel free to customize and expand upon this project to suit your needs. Connect with us on Discord if you have any questions or want to discuss further improvements.

Happy tracking, and sweet dreams! 🌙😴