Twitter GeoTrending app using node.js and AngularJS

As a recent and keen user of Twitter, I thought I would give their REST API a go and see what I can come up with using it. After some thinking I have decided to create an app that displays the trends around a particular user by doing various geo lookups.

The app is available on GitHub - please follow the setup instructions to try it out on your system.

Let's discuss the code behind this project - there are a lot of cool things going on in the background.

The app has two parts - although it is invisible to the user - a backend that communicates with Twitter's REST endpoints, and a frontend that uses the data returned from the backend to produce some output.

To get things started, familiarise yourself with the Twitter APIs, most importantly with the REST API. Once you have done this, you can create your first Twitter application which will be crucial if you'd like to authenticate against their services. (Note that there has been a major change between version 1 and 1.1 of the API, namely, the latest API only accepts authenticated calls.)

The application that I put together has the following process flow:

  • Determine the user's location using HTML5 Geolocation (written as an AngularJS Factory)
  • Make a request to Yahoo's Geo API lookup service. This step is needed as the call to retrieve the latest trends accepts a location ID which is a Yahoo! Where On Earth ID.
  • It is possible that a particular city exist in multiple locations - e.g. the city of Herndon exists in Virgina, Pennsylvania, Kansas, Kentucky and West Virgina states. If there are multiple results, users are prompted to select one city.
  • Once the ID is retrieved, a call is made to the aforementioned Twitter service.
  • If there are trends available, they will be listed. If there are no trends for a given location, the user is allowed to re-enter a new city

First, let's discuss the frontend component - written in HTML5 and AngularJS. All the action is happening in AngularJS so let's have a look at that first:

'use strict';

angular.module('geolocation',[]).constant('geolocation_msgs', {
        'errors.location.unsupportedBrowser':'Browser does not support location services',
        'errors.location.notFound':'Unable to determine your location',
});

angular.module('geolocation')
  .factory('geolocation', ['$q','$rootScope','$window','geolocation_msgs',function ($q,$rootScope,$window,geolocation_msgs) {
    return {
      getLocation: function () {
        var deferred = $q.defer();
        if ($window.navigator && $window.navigator.geolocation) {
          $window.navigator.geolocation.getCurrentPosition(function(position){
            $rootScope.$apply(function(){deferred.resolve(position);});
          }, function(error) {
            $rootScope.$broadcast('error',geolocation_msgs['errors.location.notFound']);
            $rootScope.$apply(function(){deferred.reject(geolocation_msgs['errors.location.notFound']);});
          });
        }
        else {
          $rootScope.$broadcast('error',geolocation_msgs['errors.location.unsupportedBrowser']);
          $rootScope.$apply(function(){deferred.reject(geolocation_msgs['errors.location.unsupportedBrowser']);});
        }
        return deferred.promise;
      }
    };
}]);

(The original version of the code above has been forked from GitHub. Please note however that the code there is not compatible with AngularJS v1.2.4. I have updated it, and it's available here).

This wonderful factory returns the latitude and longitude information for a particular user (providing the fact that of course their browser supports HTML5's Geolocation and they have enabled the Location Services).

This factory is then injected into the main app.js:

angular.module('twitter-geotrend',['geolocation'])

Once the location has been detected, it can be utilised to make the first call to the Yahoo service:

geolocation.getLocation().then(function(data) {
      $http.get('http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.placefinder%20where%20text%3D%22'+data.coords.latitude+'%2C'+data.coords.longitude+'%22%20and%20gflags%3D%22R%22&format=json').success(function(data) {
            $scope.city = data.query.results.Result.city
            $scope.detected = true;
        })

This will in turn update the city model with the right value and enable the appropriate form conrols.

To retrieve the trends the getTrends() function is called which does two things.

Firstly, it looks up the right "Where On Earth ID" using Yahoo's service. If it finds more than one result, it displays it to the user:

$http.get('http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.placefinder%20where%20text=%22'+$scope.city+'%22&format=json')
  .success(function(data) {
   if (Object.prototype.toString.call(data.query.results.Result) === '[object Array]') {
   $scope.detected = false;
   $scope.multicity = true;
   $scope.cities = data.query.results.Result;
  }

Secondly, if there's only one result for the geocoding, it makes a call to the backend service of the app and passes the retrieved ID as an argument:

else {
  $scope.woeid = data.query.results.Result.woeid;
  $scope.detected = true;
  woeid = $scope.woeid;
  $http.get('/api/trends/' + woeid)
  .success(function(data, status, headers, config) {
    if (data.status) {
      $scope.trends = data.trends[0].trends;
    } else {
      $scope.errorMsg = "Can't find trends around " + $scope.city + ". Try another search.";
      $scope.error = 1;
    }
  });
  }
});

It's about time to talk about the backend service.

My initial idea was to develop the whole application using AngularJS but of course I realised that I would have to store my consumer and access tokens as well, and doing that using AngularJS probably wouldn't have been the best idea. So instead, I have created a separate node.js service that stores these details and also communicates with Twitter's services.

As most of my projects, this one is running using ExpressJS as well and I have defined the following routes for my app:

app.get('/', routes.index);
app.get('/api/trends/:woeid', api.trends);

The first one is solely responsible for displaying the HTML page and the second one is where the API calls are happening to Twitter - and I am utilising an amazing npm library called 'twit'.

var Twit = require('twit'),
config = require('../config'),
T = new Twit(config);

exports.trends = function(req, res) {
  var woeid = req.params.woeid;
  T.get('trends/place', {id: woeid}, function(err, data) {
    if (typeof data === "undefined") {
      res.json({status: false});
    } else {
      res.json({trends: data, status: true});
    }
  });
};

As per the setup instructions, please don't forget to add a config.js file to the project's root folder where you have to enter your consumer and access tokens:

module.exports = {
  consumer_key: 'xxx',
  consumer_secret: 'xxx',
  access_token: 'xxx',
  access_token_secret: 'xxx'
};

The rest of the code is pretty simple and straight forward. If the REST call to Twitter returns data (i.e. it's not "undefined") then I pass that data onto AngularJS and it consumes it via it's $http.get() call and assignes it to the appropriate $scope variable.

I, then, use that $scope variable in index.html to display the results:

<ul class="list-unstyled">
  <li ng-repeat="trend in trends"><a target="_blank" href="{{ trend.url }}">{{trend.name}}</li>
</ul>

Finally, here are a few screenshots of the app in action:

Thank you for reading. I hope you've found some useful information in this article. Until next time.

Show Comments