Link to home
Start Free TrialLog in
Avatar of Bruce Gust
Bruce GustFlag for United States of America

asked on

If you've worked with the Fitbit API, you probably have run into this...

I'm working my way through a couple of tutorials that walk you through how to create an API that retrieves Fitbit info. The content is great and every educational, but I'm stuck and I need some help.

First of all, I'm using  "fitbit-node" (https://github.com/lukasolson/fitbit-node). With that, I've been able to successfully retrieve "profile.json" which seems to be the starting point for any API.

The "app.js" that's included as part of the example, looks like this:

// initialize the express application
const express = require("express");
const app = express();

// initialize the Fitbit API client
const FitbitApiClient = require("fitbit-node");
const client = new FitbitApiClient({
	clientId: "22BD5D", //client_id
	clientSecret: "9677a6cdf63836e26d0617e9ac2c38f8", //client secret
	apiVersion: '1.2' // 1.2 is the default
});

// redirect the user to the Fitbit authorization page
app.get("/authorize", (req, res) => {
	// request access to the user's activity, heartrate, location, nutrion, profile, settings, sleep, social, and weight scopes
	res.redirect(client.getAuthorizeUrl('activity heartrate location nutrition profile settings sleep social weight', 'http://localhost:3000/callback')); //callback url
});

// handle the callback from the Fitbit authorization flow
app.get("/callback", (req, res) => {
	// exchange the authorization code we just received for an access token
	client.getAccessToken(req.query.code, 'http://localhost:3000/callback').then(result => { //callback url
		// use the access token to fetch the user's profile information
		client.get("/profile.json", result.access_token)
		.then(results => {
			res.send(results[0]);
			console.log(results[0].user.age);
		}).catch(err => {
			res.status(err.status).send(err);
		});
	}).catch(err => {
		res.status(err.status).send(err);
	});
});

// launch the server
app.listen(3000);

Open in new window


When you run this code after having successfully negotiated the Fitbit authorization screen, you get a screen full of JSON data that looks similar to this:

{ 
   "user":{ 
      "age":22,
      "ambassador":false,
      "autoStrideEnabled":true,
      "avatar":"https://static0.fitbit.com/images/profile/defaultProfile_100.png",
      "avatar150":"https://static0.fitbit.com/images/profile/defaultProfile_150.png",
      "avatar640":"https://static0.fitbit.com/images/profile/defaultProfile_640.png",
      "averageDailySteps":5296,
      "clockTimeDisplayFormat":"12hour",
      "corporate":false,
      "corporateAdmin":false,
      "dateOfBirth":"1983-08-26",
      "displayName":"Bruce G.",
      "displayNameSetting":"name",
      "distanceUnit":"en_US",
      "encodedId":"7V335V",
      "familyGuidanceEnabled":false,
      "features":{ 
         "exerciseGoal":true
      },
      "firstName":"Bruce",
      "foodsLocale":"en_US",
      "fullName":"Bruce Gust",
      "gender":"MALE",
      "glucoseUnit":"en_US",
      "height":175.20000000000002,
      "heightUnit":"en_US",
      "isChild":false,
      "isCoach":false,
      "lastName":"Gust",
      "locale":"en_US",
      "memberSince":"2019-10-22",
      "mfaEnabled":false,
      "offsetFromUTCMillis":-21600000,
      "startDayOfWeek":"SUNDAY",
      "strideLengthRunning":95.60000000000001,
      "strideLengthRunningType":"default",
      "strideLengthWalking":72.7,
      "strideLengthWalkingType":"default",
      "swimUnit":"en_US",
      "timezone":"America/Chicago",
      "topBadges":[ 
         { 
            "badgeGradientEndColor":"00D3D6",
            "badgeGradientStartColor":"007273",
            "badgeType":"DAILY_STEPS",
            "category":"Daily Steps",
            "cheers":[ 

            ],
            "dateTime":"2019-11-04",
            "description":"15,000 steps in a day",
            "earnedMessage":"Congrats on earning your first Urban Boot badge!",
            "encodedId":"228TMK",
            "image100px":"https://static0.fitbit.com/images/badges_new/100px/badge_daily_steps15k.png",
            "image125px":"https://static0.fitbit.com/images/badges_new/125px/badge_daily_steps15k.png",
            "image300px":"https://static0.fitbit.com/images/badges_new/300px/badge_daily_steps15k.png",
            "image50px":"https://static0.fitbit.com/images/badges_new/badge_daily_steps15k.png",
            "image75px":"https://static0.fitbit.com/images/badges_new/75px/badge_daily_steps15k.png",
            "marketingDescription":"You've walked 15,000 steps  And earned the Urban Boot badge!",
            "mobileDescription":"With a number that's almost three times more than the national average, your step count is really heating up.",
            "name":"Urban Boot (15,000 steps in a day)",
            "shareImage640px":"https://static0.fitbit.com/images/badges_new/386px/shareLocalized/en_US/badge_daily_steps15k.png",
            "shareText":"I took 15,000 steps and earned the Urban Boot badge! #Fitbit",
            "shortDescription":"15,000 steps",
            "shortName":"Urban Boot",
            "timesAchieved":1,
            "value":15000
         },
         { 
            "badgeGradientEndColor":"38D7FF",
            "badgeGradientStartColor":"2DB4D7",
            "badgeType":"DAILY_FLOORS",
            "category":"Daily Climb",
            "cheers":[ 

            ],
            "dateTime":"2019-11-04",
            "description":"50 floors in a day",
            "earnedMessage":"Congrats on earning your first Lighthouse badge!",
            "encodedId":"FITBITID",
            "image100px":"https://static0.fitbit.com/images/badges_new/100px/badge_daily_floors50.png",
            "image125px":"https://static0.fitbit.com/images/badges_new/125px/badge_daily_floors50.png",
            "image300px":"https://static0.fitbit.com/images/badges_new/300px/badge_daily_floors50.png",
            "image50px":"https://static0.fitbit.com/images/badges_new/badge_daily_floors50.png",
            "image75px":"https://static0.fitbit.com/images/badges_new/75px/badge_daily_floors50.png",
            "marketingDescription":"You've climbed 50 floors to earn the Lighthouse badge!",
            "mobileDescription":"With a floor count this high, you're a beacon of inspiration to us all!",
            "name":"Lighthouse (50 floors in a day)",
            "shareImage640px":"https://static0.fitbit.com/images/badges_new/386px/shareLocalized/en_US/badge_daily_floors50.png",
            "shareText":"I climbed 50 flights of stairs and earned the Lighthouse badge! #Fitbit",
            "shortDescription":"50 floors",
            "shortName":"Lighthouse",
            "timesAchieved":1,
            "value":50
         }
      ],
      "waterUnit":"en_US",
      "waterUnitName":"fl oz",
      "weight":81.6,
      "weightUnit":"en_US"
   }
}

Open in new window


Now, head out to https://www.rdluna.info/fitbit_api.html. I realize that this is a different piece of code that's being used to access the Fitbit API, but there's enough in the way of similarities that justified believing that this was a viable resource that I could use to access what ultimately goes beyond mere profile info.

And that's the dilemma! What I have above is necessary and helpful, but there's nothing about the user's calorie intake, heartbeat or exercise regiment, and that's the info that I need.

According the rdluna tutorial, while my starting point is "profile.json" (below is the fourth block of code on the https://www.rdluna.info/fitbit_api.html tutorial, if you want to look at it))...

User generated image
...this is only the user's information. If you want heartbeat, calories and exercise routine, then you have to access some of the other Fitbit endpoints which are listed here: https://dev.fitbit.com/build/reference/web-api/

Here's where I get lost...

You've got the "profile.json" data. But now you have to somehow segue into the other endpoints / fitness data, but I don't understand how.

In the luna tutorial, just a few blocks of code down, you have this:

User generated image
It would seem to be that there's a "jump" that's been made based on the assumption that the student who's going through these tutorials understands the logic that ties all of this together.

If I do this:

// initialize the express application
const express = require("express");
const app = express();

// initialize the Fitbit API client
const FitbitApiClient = require("fitbit-node");
const client = new FitbitApiClient({
	clientId: "22BD5D", //client_id
	clientSecret: "9677a6cdf63836e26d0617e9ac2c38f8", //client secret
	apiVersion: '1.2' // 1.2 is the default
});

// redirect the user to the Fitbit authorization page
app.get("/authorize", (req, res) => {
	// request access to the user's activity, heartrate, location, nutrion, profile, settings, sleep, social, and weight scopes
	res.redirect(client.getAuthorizeUrl('activity heartrate location nutrition profile settings sleep social weight', 'http://localhost:3000/callback')); //callback url
});

// handle the callback from the Fitbit authorization flow
app.get("/callback", (req, res) => {
	// exchange the authorization code we just received for an access token
	client.getAccessToken(req.query.code, 'http://localhost:3000/callback').then(result => { //callback url
		// use the access token to fetch the user's profile information
		client.get("/profile.json", result.access_token)
		.then(results => {
			res.send(results[0]);
			console.log(results[0].user.age);
		}).catch(err => {
			res.status(err.status).send(err);
		});
	}).catch(err => {
		res.status(err.status).send(err);
	});
});

app.get("/heartrate/:date", function(req, res) {
	[b]console.log("heart rate");[/b]
  client.get("/activities/heart/date/" + req.params.date + "/1d.json", token)
    .then(async function(results) {
      var date = results[0]['activities-heart'][0]['dateTime'];
      var data = JSON.stringify(results[0]['activities-heart-intraday']['dataset']);
      data = data.replaceAll('"time":"', '"dateTime":"' + date + "T");
      result = await insert_query_json('fitbit.heartrate', data);
      res.send(result);
	  console.log("hello");
    }).catch(err => {
      res.status(err.status).send(err);
    });
});

app.get("/activity/:activity/:date", (req, res) => {
  client.get("/activities/" + req.params.activity + "/date/" + req.params.date + "/1d.json", token)
    .then(async function(results) {
      var date = results[0]['activities-' + req.params.activity][0]['dateTime'];
      var data = JSON.stringify(results[0]['activities-' + req.params.activity + '-intraday']['dataset']);
            data = data.replaceAll('"time":"', '"dateTime":"' + date + "T");
            result = await insert_query_json('fitbit.' + req.params.activity, data);
      res.send(result);
    }).catch(err => {
      res.status(err.status).send(err);
    });
});

// launch the server
app.listen(3000);

Open in new window


I'm looking for heart rate and activities and I get nothing. Even the "console.log("heartrate") that I've got in bold in the above code doesn't show up.

What am I missing? There are no errors, but there's no info and no evidence to suggest that the "heart rate" and "activity" pieces are even firing. What am I doing wrong?

Thanks!
ASKER CERTIFIED SOLUTION
Avatar of Chris Stanyon
Chris Stanyon
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Bruce Gust

ASKER

Thanks, Chris!

I'll apply what you're stated above and see if I can't make some progress.

BTW: I'm going to have a bunch of questions before I'm through. If you're willing, I would appreciate whatever insight you can give me.

Here's another question: https://www.experts-exchange.com/questions/29163184/Why-does-this-app-js-file-not-see-my-route.html#questionAdd
BOOM!
Haha ... I like BOOM ! BOOM is good :)