# Twitter GeoTrending app using Node.js and AngularJS

Source: https://tpiros.dev/blog/twitter-geotrending-app-using-node-js-and-angularjs

I've been using Twitter more and more recently, so I figured I'd poke at their REST API and see what falls out. After some thinking, I built an app that pulls trending topics near a user's location through a series of geo lookups.

The app lives on [GitHub](https://github.com/tpiros/twitter-geotrend). Follow the [setup instructions](https://github.com/tpiros/twitter-geotrend#setup-instructions) to run it yourself.

Let's walk through the code. There's plenty happening under the hood.

The app has two parts (invisible to the user): a backend that talks to Twitter's REST endpoints, and a frontend that takes the returned data and renders it.

First things first: familiarise yourself with the [Twitter APIs](https://dev.twitter.com/start), especially the [REST API](https://dev.twitter.com/docs/api/1.1). Once you've got your bearings, [create your first Twitter application](https://dev.twitter.com/apps/new). You'll need it for authentication. (Version 1.1 of the API only accepts authenticated calls, which was a big shift from v1.)

Here's the process flow:

- Grab the user's location via HTML5 Geolocation (wrapped in an AngularJS Factory)
- Hit Yahoo's Geo API lookup service. We need this because the [trends endpoint](https://dev.twitter.com/docs/api/1.1/get/trends/place) expects a Yahoo! Where On Earth ID (Yahoo's GeoPlanet API has since been deprecated)
- If a city name maps to multiple locations (Herndon exists in Virginia, Pennsylvania, Kansas, Kentucky, and West Virginia), the user picks the right one
- Once we've got the ID, we call Twitter's trends service
- If trends exist, they get listed. If not, the user can try a different city

Let's start with the frontend: HTML5 and AngularJS. All the interesting work happens in Angular, so here's the geolocation factory:

```js
'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 code was forked from [GitHub](https://github.com/arunisrael/angularjs-geolocation). That version doesn't work with AngularJS v1.2.4. I've updated it and it's [available here](https://github.com/tpiros/angularjs-geolocation).)

This factory hands back latitude and longitude for the user (assuming their [browser supports](http://caniuse.com/#feat=geolocation) HTML5 Geolocation and they've enabled Location Services).

The factory gets injected into the main `app.js`:

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

Once the location's been detected, it feeds into the first Yahoo API call:

```js
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 populates the `city` model and enables the right form controls.

To fetch the trends, the `getTrends()` function fires. It does two things.

First, it looks up the "Where On Earth ID" through Yahoo's service. Multiple results get shown to the user:

```js
$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;
}
```

Second, if only one result comes back, it calls the backend service with the retrieved ID:

```js
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;
    }
  });
  }
});
```

Now for the backend.

I originally wanted to build the whole thing in AngularJS alone, but then I realised I'd have to store my consumer and access tokens somewhere. Stuffing them into client-side code would've been a terrible idea. So I spun up a separate Node.js service to hold those details and talk to Twitter.

Like most of my projects, it runs on ExpressJS with these routes:

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

The first route serves the HTML page. The second handles the Twitter API calls using a brilliant npm library called '[twit](https://npmjs.org/package/twit)':

```js
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 });
    }
  });
};
```

Per the setup instructions, don't forget to add a `config.js` file to the project root with your consumer and access tokens:

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

The rest is fairly straightforward. If the Twitter REST call returns data (not `undefined`), it gets passed to AngularJS, which picks it up via `$http.get()` and assigns it to the right `$scope` variable.

That `$scope` variable then gets rendered in `index.html`:

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

Here are a few screenshots of the app in action:

Thanks for reading. Hope you found something useful here.
