Log Strava Activity in Notion

This developer documentation provides a step-by-step guide to create an integration between the Notion API and the Strava API using JavaScript. The integration fetches data from a Strava athlete's activities and populates a Notion database with their activity data.

🔑

This integration requires following Strava's OAuth flow. Please refer to Strava's OAuth documentation for more information.

Prerequisites

  • Node.js installed on your machine
  • A Strava Developer account
  • A Notion account

Step 1: Get Your API Keys

Strava API Key

  1. Log in to your Strava account.
  2. Go to Strava API.
  3. Click on Create An App.
  4. Fill in the required details and click Create.
  5. Copy the Client ID and Client Secret.

Notion API Key

  1. Log in to your Notion account.
  2. Go to Notion Integrations.
  3. Click on New Integration.
  4. Fill in the required details and click Submit.
  5. Copy the generated Internal Integration Token.

Step 2: Set Up Your Notion Database

  1. Create a new database in Notion with the following properties:
  • Name (Title(
  • Distance (Number)
  • Pace (Text)
  • Duration (Text)
  • Elevation Gain (Number)
  • Type (Text)
  • Date (Date)
  • Calories (Number)
  1. Share the database with your integration:
  • Click on the three dots in the top-right corner of the page.
  • Under Connections, click on Connect to and search for your integration and invite it.

Step 3: Project Structure

This step is not necessary but we suggest projects to be structured like so:

project-root/
├── src/
│   ├── config/
│   │   └── spotify.js
│   │   └── notion.js
│   ├── integrations/
│   │   ├── spotifyIntegration.js
│   │   └── notionIntegration.js
│   ├── main.js
│   ├── server.js
├── .env
├── package.json

Step 4: Create .env file

Create a .env file in your project directory with the following with the following information:

NOTION_TOKEN='your_notion_integration_token'
NOTION_DATABASE_ID='your_notion_database_id'
STRAVA_CLIENT_ID='your_strava_client_id'
STRAVA_CLIENT_SECRET='your_strava_client_secret'
STRAVA_ACCESS_TOKEN='your_strava_access_token'
STRAVA_REFRESH_TOKEN='your_strava_refresh_token'

🚨

Please note that you will have to update your .env file with your STRAVA_ACCESS_TOKEN and STRAVA_REFRESH_TOKEN after completing Strava’s OAuth flow.

Step 5: Packages

Install the required Node.js packages using the following command:

npm install @notionhq/client dotenv express https

Additionally, create a package.json file and add the following:

{
    "name": "strava_integration",
    "version": "1.0.0",
    "main": "src/main.js",
    "scripts": {
        "start": "node src/main.js"
    },
    "dependencies": {
        "@notionhq/client": "^1.0.4",
        "dotenv": "^8.6.0",
        "express": "^4.19.2",
        "https": "^1.0.0"
    }
}

Step 6: OAuth Flow

Create server.js to handle the OAuth flow:

const express = require('express');
const https = require('https');
const querystring = require('querystring');
require('./utils/dotenv');

const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.send('<h1>Strava OAuth Integration</h1><p><a href="/auth">Click here to authorize with Strava</a></p>');
});

app.get('/auth', (req, res) => {
  const authUrl = `https://www.strava.com/oauth/authorize?client_id=${process.env.STRAVA_CLIENT_ID}&response_type=code&redirect_uri=http://localhost:${PORT}/exchange_token&approval_prompt=force&scope=activity:read_all`;
  res.redirect(authUrl);
});

app.get('/exchange_token', (req, res) => {
  const { code } = req.query;

  const postData = querystring.stringify({
    client_id: process.env.STRAVA_CLIENT_ID,
    client_secret: process.env.STRAVA_CLIENT_SECRET,
    code: code,
    grant_type: 'authorization_code'
  });

  const options = {
    hostname: 'www.strava.com',
    path: '/oauth/token',
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  };

  const request = https.request(options, (response) => {
    let data = '';

    response.on('data', (chunk) => {
      data += chunk;
    });

    response.on('end', () => {
      const parsedData = JSON.parse(data);
      if (parsedData.errors) {
        res.status(400).send(`Error: ${parsedData.message}`);
      } else {
        res.send(`Access Token: ${parsedData.access_token}<br>Refresh Token: ${parsedData.refresh_token}`);
      }
    });
  });

  request.on('error', (e) => {
    res.status(500).send(`Error: ${e.message}`);
  });

  request.write(postData);
  request.end();
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Step 7: Set Up Strava API Client

Create strava.js to set up the Strava API client:

const https = require('https');
const querystring = require('querystring');

// Function to get Strava access token
async function getStravaAccessToken() {
  const postData = querystring.stringify({
    client_id: process.env.STRAVA_CLIENT_ID,
    client_secret: process.env.STRAVA_CLIENT_SECRET,
    refresh_token: process.env.STRAVA_REFRESH_TOKEN,
    grant_type: 'refresh_token'
  });

  const options = {
    hostname: 'www.strava.com',
    path: '/oauth/token',
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';

      res.on('data', (chunk) => {
        data += chunk;
      });

      res.on('end', () => {
        const parsedData = JSON.parse(data);
        if (parsedData.errors) {
          reject(new Error(`Error fetching access token: ${parsedData.message}`));
        } else {
          resolve(parsedData.access_token);
        }
      });
    });

    req.on('error', (e) => {
      reject(e);
    });

    req.write(postData);
    req.end();
  });
}

module.exports = { getStravaAccessToken };

Step 8: Set Up Notion API Client

Create a notion.js file to set up the Notion API client using the @notionhq/client library. Use your Notion integration token and database ID.

// notion.js
const { Client } = require('@notionhq/client');
const dotenv = require('dotenv');

dotenv.config();

const notion = new Client({ auth: process.env.NOTION_TOKEN });
const databaseId = process.env.NOTION_DATABASE_ID;

module.exports = { notion, databaseId };

Step 9: Fetch Activity Data From Strava

Create a stravaIntegration.js file to fetch activity data from your Strava account:

const https = require('https');
const { getStravaAccessToken } = require('../config/strava');

// Helper function to make GET requests
async function fetchJson(url, headers) {
  return new Promise((resolve, reject) => {
    const options = {
      headers: headers
    };

    https.get(url, options, (res) => {
      let data = '';

      res.on('data', (chunk) => {
        data += chunk;
      });

      res.on('end', () => {
        resolve(JSON.parse(data));
      });
    }).on('error', (e) => {
      reject(e);
    });
  });
}

// Convert pace from min/km to min/mile
function convertPaceToMinPerMile(paceInMinPerKm) {
  const minPerMile = paceInMinPerKm * 1.60934;
  const minutes = Math.floor(minPerMile);
  const seconds = Math.round((minPerMile - minutes) * 60);
  return `${minutes}:${seconds < 10 ? '0' : ''}${seconds} min/mile`;
}

// Convert distance from meters to miles
function convertMetersToMiles(meters) {
  return meters * 0.000621371;
}

// Fetch activities from Strava
async function getStravaActivities() {
  const accessToken = await getStravaAccessToken();
  const headers = {
    'Authorization': `Bearer ${accessToken}`
  };

  const activitiesResponse = await fetchJson('https://www.strava.com/api/v3/athlete/activities', headers);

  // Log the response to understand its structure
  console.log('Activities Response:', activitiesResponse);

  if (!Array.isArray(activitiesResponse)) {
    throw new Error('Invalid response from Strava API');
  }

  return activitiesResponse.map(activity => {
    const paceInMinPerKm = (activity.moving_time / 60) / (activity.distance / 1000);
    const paceInMinPerMile = convertPaceToMinPerMile(paceInMinPerKm);
    const distanceInMiles = convertMetersToMiles(activity.distance);

    return {
      name: activity.name,
      distance: distanceInMiles,
      pace: paceInMinPerMile,
      duration: new Date(activity.moving_time * 1000).toISOString().substr(11, 8), // Duration in HH:MM:SS
      elevation_gain: activity.total_elevation_gain,
      type: activity.type,
      date: activity.start_date,
      calories: activity.calories
    };
  });
}

module.exports = { getStravaActivities };

Step 10: Populate Notion Database with Strava Activity Data

Create a notionIntegration.js file to handle populating the Notion database with all Strava activities.

const { Client } = require('@notionhq/client');

// Initialize the Notion client
const notion = new Client({ auth: process.env.NOTION_TOKEN});

// Add data to the Notion database
async function addToNotionDatabase(databaseId, activity) {
  try {
    await notion.pages.create({
      parent: { database_id: databaseId },
      properties: {
        'Name': {
          title: [
            {
              text: {
                content: activity.name
              }
            }
          ]
        },
        'Distance': {
          number: activity.distance
        },
        'Pace': {
          rich_text: [
            {
              text: {
                content: activity.pace
              }
            }
          ]
        },
        'Duration': {
          rich_text: [
            {
              text: {
                content: activity.duration
              }
            }
          ]
        },
        'Elevation Gain': {
          number: activity.elevation_gain
        },
        'Type': {
          rich_text: [
            {
              text: {
                content: activity.type
              }
            }
          ]
        },
        'Date': {
          date: {
            start: activity.date
          }
        },
        'Calories': {
          number: activity.calories || 0 // Default to 0 if undefined
        }
      }
    });
  } catch (error) {
    console.error('Error adding to Notion database:', error);
  }
}

module.exports = { addToNotionDatabase };

Step 11: Main Script

Createa main.js file that uses the modules created above to execute the integration logic.

require('dotenv').config();

const { getStravaActivities } = require('./integrations/stravaIntegration');
const { addToNotionDatabase } = require('./integrations/notionIntegration');

const NOTION_DATABASE_ID = process.env.NOTION_DATABASE_ID;

// Main function to fill the Notion database with Strava activities
async function fillNotionDatabase() {
  try {
    const activities = await getStravaActivities();
    
    for (const activity of activities) {
      await addToNotionDatabase(NOTION_DATABASE_ID, activity);
      console.log(`Added to Notion: Name: ${activity.name}, Distance: ${activity.distance}, Pace: ${activity.pace}`);
    }
  } catch (error) {
    console.error('Error filling Notion database:', error);
  }
}

fillNotionDatabase();

Step 12: Run the OAuth Flow

  1. Start the OAuth Server:
node src/server.js
  1. Open your browser and navigate to http://localhost:3000:
    • You should see a page with a link to authorize with Strava.
  2. Click the link to authorize with Strava:
    • This will redirect you to Strava's authorization page.
  3. Authorize the application:
    • Strava will redirect you to a page with an authorization code.
  4. The server will exchange the authorization code for an access token and refresh token:
    • The tokens will be displayed on the page.
  5. Copy the refresh token and update your .env file with it.

Step 13: Run the Integration

Execute the integration script by running the following command:

npm start 

🔑

Customize the code according to your Notion database structure and the properties you want to include for each Strava activity.