During the scope of this project, we will be working with several files across different frameworks. Our project structure will look like the following:
For those interesting in viewing the final product right away, you may do so here.
Over the course of this tutorial, we will use several front-end and helper libraries. We first look at
bower.json, which we use in conjunction with
bower to install various libraries.
We will also add a
.bowerrc file to configure where our libraries will be installed to:
With that all set up, go ahead and run
bower install, which will download all the packages listed within the
We will also need some packages for our server-side application. Set up a
package.json file as shown below:
With this, we now run
npm install to install the packages listed within our
Tip: For OS X users, you will have to run sudo npm install.
Setting up your Github Developer Account
In order to remove data limitations from the Github API (50 queries per hour), it is well worth your time to register an application as a developer, which will produce a
CLIENT_SECRET for you. Once you have these in your possession, create a file
config.js and set it up as shown below:
Here we export this object in order to save ourselves a lot of typing in the near future. We will be supplying the
CLIENT_SECRET in each of our API calls, which will get repetitive quickly.
For this project, we will be using an admin bootstrap template provided by Start Bootstrap. Within the
public folder, your directory structure should look like this:
You may grab the files listed above from <a style=”color:#FC645F” href=https://github.com/DrkSephy/git-technetium/tree/master/git-technetium/public>the project repository located here</a>.
With all of that preparation out of the way, we now finally begin to build out our application!
NodeJS and ExpressJS
In this application, we will be grabbing data from the Github API and creating routes within ExpressJS which will consume various API endpoints from Github, parse and return the JSON to the AngularJS client, which will render it out. We begin by building a minimal working version of the application located here, starting with gathering issue data from a repository.
We begin by writing our NodeJS Server, which will serve up the application and register all API routes for our ExpressJS application.
We begin by requiring all of the modules we will need. Note how we include our
config.js file which we wrote earlier - this will contain the
CLIENT_SECRET which will be used in our
routes and `server.js.
Here we register our
issue route, which we will write shortly.
Above we will prefix all our routes with
/api. This means that on the client-side application, we can make an HTTP request to
/api/issues (our ExpressJS API route) which will return JSON containing issue data. This is simply a convention to differentiate between client-side and server-side routes for our application.
Lastly, we direct our application to know where to find our static files (HTML, CSS) and start the server using
app.listen. By running:
Putting it all together, our
server.js looks like the following:
server.js finished, we now move onto writing our first Express API endpoint.
routes folder, we will create our first route:
For now, we start with a minimal endpoint, where we are exporting a
router.get which contains an endpoint
/issues, which in turn is a function of
req, res. We will be using the node.js
request library to retrieve data. We now move onto forming our request for data:
Here we create an array
issueData which will contain all of our parsed issue data. We then define and execute a function
getData which is a function of
pageCounter refers to which page of data we are looking at that is returned by Github’s API. All data from the Github API is paginated to 30 results per page, which will require making several requests to bundle up all of the data. If you have not registered your application with Github and received a
CLIENT_SECRET, I strongly advise you do so now - otherwise you may quickly run out of hourly requests.
getData function makes a
request, which is a function of:
url: The URL from which we want to get data from. Notice that it is a function of two paramaters:
req.query.repo- these query parameters will be passed in by the AngularJS client. The URL is also a function of the current page,
headers: Any request to the Github API requires sending a header, otherwise the HTTP request will fail.
json: We set this to true to denote we are receiving JSON data.
If our request is successful, the
body parameter of our callback function will contain the JSON from that specific page. We now write a short parser to extract the data we are interested in sending to the client:
If we received no error from our HTTP request and our request returned a status code of 200, we will iterate over each issue. It is important to note that pull requests are treated as issues on Github, so we check if the
pull_request key exists in that specific issue data in our iteration. If that specific issue entry is not generated by a pull request, we will push an object into our
issueData array containing the following data:
number: The issue number (descending order).
title: The title of the issue.
state: The state (opened, closed, etc) of the issue.
creator: The creator of the issue.
assignee: The person who is assigned the issue.
Lastly, we need to check if more data exists for the
issues endpoint of this specific repository.
In our first HTTP request, we are currently looking at
pageCounter = 1. If the length of this content is
< 30, we know there are no more pages of data, so we use
res.send to send the
issueData to the client. Otherwise, we call
getData and pass in the next page URL to gather the rest of the data.
Putting it all together, our
issues.js route is looking like the following:
With that done, we move onto our AngularJS client.
Before we begin our AngularJS application, we first put together an
The comments should be self-sufficient in explaining what each section is doing. The most important sections to note are the
Controllers and the
Factories, which we will get into now.
One aspect of AngularJS which developers may find confusing is the difference between
Services. I’ll try to explain the difference as I see it.
Factories allow us to add logic before creating the object that we will require (often required by a
Controller - which we will define shortly after.) We can create an object, add properties to it, and then return the same object. By passing this object to the
Controller will have access to these same properties. Other than that,
- Can use other dependencies
- Useful for non-configurable services
- Singleton object
On the other hand, a
Service (which is not used in this application) creates a service object through use of the
new keyword. We can then add properties to it and return the object using
- Gives us an instance of a function/object which we can augment with new properties and return through
- Singleton object, will only be created once.
- Dependencies will be injected as arguments to the constructor
- Used for simple object creation logic
With the terminology out of the way, we will create a factory which is in charge of returning a function to retrieve issue data from our ExpressJS API endpoint.
Here we create a factory which is a function of
$http - an AngularJS service for reading data from remote servers. We return a
get method which is a function of
repo, which will be passed in through a form object inside of one of our
partials (which we have not implemented yet). Remember those query parameters
repo.query.repo from our
issues.js route in ExpressJS? Those parameters will be sent through this HTTP request.
issue factory implemented, we move onto implementing our first
Controllers to add logic to our view (our
partials, which we implement in the next section). Simply put,
Controllers bind data to our views and act as the glue between our model (data) and the view.
In more technical terms,
Controllers allow us to augment the
$scope object with data which we will make available in the view.
Before writing our first functional controller, we must first get some initialization out of the way. We need to define a controller which will handle our routing based on the current page we are on.
With that out of the way, here is our first controller,
Here we create a
Controller which is a function of the
$scope object and our
issuesFactory factory. When we submit our form, we will augment
pageData - which will contain the result of retrieving data from our ExpressJS issues API endpoint through our
get method inside of
issuesFactory.js. Once the data returns successfully, we augment
$scope.pageData with the data returned in our callback function.
Controller implemented, we will define the routes of our application. Using AngularJS’s
ui.router library, we can define
states which dictate which template, controller and data to map to a specific URL in our application. Let us go ahead and define a URL with which to view issues at:
By navigating to
localhost/#/issues, we will be able to view a form which will allow us to query a repository for all issues. Before we can do so, let us finally define our first
Partials are HTML fragments which are similar to AngularJS templates. Due to our data from the controllers binding the data to our
$scope object - we can use the AngularJS directive
ng-repeat to iterate over an array of elements in our User Interface. As we mentioned before, we will use a form to pass in the repository owner name (
owner) and the repository name (
repo). Below is the HTML for our
Most of the above is simply Bootstrap code, but let us break down the important AngularJS specific parts.
Here we create a form which contains
ng-model - used to bind form properties to the
$scope object. It provides two-way data binding between the form and the Controller, which in turn will pass it’s values to our Factory method and subsequently down to our ExpressJS API issues endpoint.
Here we simply use the AngularJS directive
ng-repeat to loop over each entry within
pageData - which comes from our Controller. We display the issue number, issue title, issue state, issue creator and issue assignee.
Go ahead and re-run your server if you aren’t running it already. Input a repository owner name and repository name, and you should see the User Interface update once the data is returned.
If you’ve made it this far, congratulations! You’ve successfully developed an application in which you’ve built your own RESTful API using ExpressJS, NodeJS and a client in AngularJS to consume our API. While we have only covered a single API endpoint and only use GET requests, you should now have some intuition on how to extend this application.
For those interested in a broader application which covers more endpoints of the Github API, feel free to check out Git-Techetium, which covers endpoints such as commits, issues opened, issues closed, issues assigned, pull requests, lines of code and more.
Thanks for reading!