How to Connect Github API with a Nodejs Server

2019-07-08

Playing around with Github API is the easiest way to get started when learning any new technology stack or a framework. If you are trying to get familiarize with Crowdbotics app building platform and build a server from scratch on it, this is the tutorial you should read.

In this tutorial, you are going to build a demo app that fetches data from the Github API and render using a template. The server is going to set up from scratch using Nodejs as a platform and Express as the framework. This demo application is also going to OAuth strategy to access client's Github credentials. There is a lot to get out of this tutorial.

Table of Contents

  • Requirements
  • Setting Github OAuth application
  • Bootstrap a Node Server
  • Setup Handlebars Template
  • Adding Github Passport for OAuth
  • Fetching Data from Github API
  • Interacting with Github API
  • Conclusion

Requirements

To follow this tutorial, you are required to have installed the following on your local machine:

  • Nodejs v8.x.x or higher installed along with npm/yarn as the package manager
  • Github Account
  • Crowdbotics App builder Platform account (preferably log in with your valid Github ID)

What are we building? Here is a short demo.

ss0

Setting Github OAuth application

To fetch a user's data from his Github account, you have to define a strategy in the Node app for the user login using their Github credentials. To write this strategy, your app needs a Github API access key and a secret key. For this, you have to register a new OAuth application at this link.

ss3

You have to fill the required fields that are marked with a red asterisk symbol in the above image. The most import thing is that make sure in the above form, where it is specified Authorization callback URL, you enter the value: http://localhost:3000/login/github/return. Since the demo application is being built on a local development environment, hence the local host URL and the port number. If you are interested in hosting this application, make sure in the callback URL, instead of using localhost and the port number, you have are specifying the domain of your hosted application.

Once the form is filled, enter the button that says Register application. On the next screen, you will get two values. Client ID and Client Secret.

ss4

In the Node server, you are going to use both of these values. So, if you have cloned the Github repo from the previous step, or create a new project, traverse inside it, create a new file called .env. This file is going to hold all the environment variables and secret data such as those two values just obtained.

Add the following content to the file and replace all Xs with actual values for Client ID and Client Secret.

1GITHUB_CLIENT_ID=XXXX
2GITHUB_CLIENT_SECRET=XXXX
3PORT=3000

The last variable, PORT in the above snippet defined is going to be used to bootstrap the server later. With this step complete, you have done all configuration part that was required, outside building the server app.

Bootstrap a Node Server

Once you are inside the server folder, make sure you run the following commands sequentially using a terminal window. The first command will initialize the server directory as a node server and generate a package.json file. The second command will let you install all the required dependencies.

1# to initialize
2npm init --yes
3
4# to install dependencies
5npm install -save express cookie-parser connect-ensure-login dotenv express-handlebars express-session flatted github-api passport passport-github underscore

Once all the dependencies are installed, let us bootstrap a small server and see if everything is working or not. Create a new file server.js and add the following snippet to it.

1const express = require('express')
2
3// import env variables
4require('dotenv').config()
5
6const app = express()
7const port = process.env.PORT
8
9app.use(express.json())
10app.use(express.urlencoded({ extended: true }))
11
12app.get('/', (req, res) => {
13 res.status(200).send('Server is working.')
14})
15
16app.listen(port, () => {
17 console.log(`🌏 Server is running at http://localhost:${port}`)
18})

The above server is simple as it can be with only one / home route. The moduledotenv enable the file to read environment variables and their values from the file .env. Go to the terminal window and execute the command node server.js. This will prompt with the message you have defined in the above snippet. Visit the URL http://localhost:3000 from the browser window, and you will get the following result.

ss5

Setup Handlebars Template

Handlebars is a popular template engine among Express applications. It is powerful, open-source, and has a large community to help you out if you ever get stuck. Since to keep things simple in the demo server app you are building, let us Handlebars. Write code to setup handlebars. Open server.js file and modify it accordingly.

1const express = require('express')
2const exphbs = require('express-handlebars')
3
4// import env variables
5require('dotenv').config()
6
7const app = express()
8const port = process.env.PORT
9const COOKIE = process.env.PROJECT_DOMAIN
10
11app.use(express.json())
12app.use(express.urlencoded({ extended: true }))
13
14const hbs = exphbs.create({
15 layoutsDir: __dirname + '/views'
16})
17app.engine('handlebars', hbs.engine)
18app.set('views', __dirname + '/views')
19app.set('view engine', 'handlebars')
20
21app.get('/', (req, res) => {
22 res.render('main')
23})
24
25app.listen(port, () => {
26 console.log(`🌏 Server is running at http://localhost:${port}`)
27})

It is necessary to specify the path to a layoutsDir otherwise known as the layout directory. In the above snippet, the views directory is going to serve the purpose. Create a new directory at the root of the Node server app and inside this folder, create a new file called main.handlebars. Add the following code to this file. Inside the route app.get('/'), the returned file is going to be the name of the handlebars template file.

1<!DOCTYPE html>
2<html>
3
4 <head>
5 <title>GitHub API Example</title>
6 <meta name="description" content="Github API app with Nodejs Server.">
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge">
9 <meta name="viewport" content="width=device-width, initial-scale=1">
10 </head>
11
12 <body>
13 <header>
14 <h1>Github API Data</h1>
15 <p>This page uses Handlebars as the template engine.</p>
16 </header>
17
18 </body>
19
20</html>

The advantage Handlebar templates have is that they give you the power to use computable expressions using curly braces {{}} inside an HTML file. The above file does not use any expressions at the moment, but you will notice this in the future.

Execute the command node server.js again and visit the localhost URL as you did in the previous section. You will notice the following result. This means the template engine has been hooked with our Express server successfully.

ss6

Adding Github Passport for OAuth

Before you proceed in this section to define and add a strategy in order to consume Github credentials for them to login in your app using their Github account details, open server.js file, and import all required dependencies that are needed in this process.

1// ... after other imports
2const cookieParser = require('cookie-parser')
3const expressSession = require('express-session')
4const crypto = require('crypto')
5const passport = require('passport')
6const GithubStrategy = require('passport-github').Strategy
7const { stringify } = require('flatted')
8const _ = require('underscore')

Next, let us define the passport strategy for Github. Passport is authentication middleware for any Nodejs server application. This means you can use it with other Node frameworks that are not Express. Using this module, helps Node server apps to be clean and maintainable code.

OAuth is a standard protocol that allows users to authorize API access to the web, desktop, or mobile applications. Once the access is granted, the authorized application utilizes the API on behalf of the user. This is what you are trying to implement. Once the demo application is authorized with a user's Github account, the application will fetch the required data from their Github account details.

Add the following snippet after you have defined the variable port in server.js file.

1let scopes = ['notifications', 'user:email', 'read:org', 'repo']
2passport.use(
3 new GithubStrategy(
4 {
5 clientID: process.env.GITHUB_CLIENT_ID,
6 clientSecret: process.env.GITHUB_CLIENT_SECRET,
7 callbackURL: 'http://localhost:3000/login/github/return',
8 scope: scopes.join(' ')
9 },
10 function(token, tokenSecret, profile, cb) {
11 return cb(null, { profile: profile, token: token })
12 }
13 )
14)
15passport.serializeUser(function(user, done) {
16 done(null, user)
17})
18passport.deserializeUser(function(obj, done) {
19 done(null, obj)
20})
21app.use(passport.initialize())
22app.use(passport.session())

The clientID and clientSecret values are required in order to create OAuth Github client. These values are being obtained from environment variables. The callbackURL is the same as you defined when setting up a new Github application.

The scopes array holds values that are acceptable while creating a token to authorize the Node app with OAuth. The authorization of application can only happen for a user when scopes are defined and provided as above.

Once you have defined the Passport strategy, you have to create a cookie which will hold the saved authenticated user. Add the following middleware after previously defined other middleware functions.

1app.use(cookieParser())
2app.use(
3 expressSession({
4 secret: crypto.randomBytes(64).toString('hex'),
5 .randomBytes(64)
6 .toString('hex'),
7 resave: true,
8 saveUninitialized: true
9 })
10)

Using the Node's core module crypto, it is easy to create a random string of secret value for the user's session in the browser. Let us also define other routes in this section to accept the main / that you are going to complete in the next section after writing the code to fetch data from the Github API.

Other routes include

  • /logoff which will clear the cookie from the browser session as well as redirect the user to the home or initial route.
  • /auth/github to authenticate the user using passport strategy
  • /login/github/return is the callback URL. On success, it will create the cookie with user authorization data, and on failure, it will redirect to the initial route.
  • /setcookie on successful auth, this route will store the user profile detail and token.

Here is the code for the above-mentioned routes.

1app.get('/logoff', function(req, res) {
2 res.clearCookie(COOKIE)
3 res.redirect('/')
4})
5
6app.get('/auth/github', passport.authenticate('github'))
7
8app.get(
9 '/login/github/return',
10 passport.authenticate('github', {
11 successRedirect: '/setcookie',
12 failureRedirect: '/'
13 })
14)
15
16app.get('/setcookie', function(req, res) {
17 let data = {
18 user: req.session.passport.user.profile._json,
19 token: req.session.passport.user.token
20 }
21 res.cookie(COOKIE, JSON.stringify(data))
22 res.redirect('/')
23})

Fetching Data from Github API

For this demo, you are going to fetch all the repositories of an authorized user. Create a new file called api.js and import the following dependencies.

1const GitHub = require('github-api')

The github-api is the dedicated package to traverse the Github API. Next, define an asynchronous function called getGithubData that will be responsible for executing a business logic to fetch the data from the API. It accepts a parameter called token. Also, create an empty object called data that will hold the actual data from the API. Add the following snippet of code to the file.

1async function getGitHubData(token) {
2 let gh = new GitHub({
3 token: token
4 })
5
6 let data = {}
7 let me = gh.getUser()
8 let repos = await me.listRepos()
9 data.repos = repos.data
10 return data
11}
12
13module.exports = getGitHubData

The getUser() allows fetching the user details from the API such as notifications, repositories, and so on. In the above snippet, listRepos is the function that fetches the name of the repository that this Node application is going to display. Now, go back to server.js file and import the function getGitHubData.

1// ... after other imports
2const getGitHubData = require('./api')

Interacting with Github API

In this section, you are going to modify the index route and the handlebar template associated with it. Index route is where the complete interaction between the application and API is going to happen.

First, there is going to be a check whether the session and the token exists or not. This confirms that the user is logged in or not. Using async/await approach is the best way to make asynchronous calls. Next, using the _ underscore utility library, create a shallow copy of the returned object from the Github API.

Then, use stringify method from the flatten library that helps to un-nest the JSON object. Lastly, send the data object to the template. Open the file server.js and modify the index route.

1app.get('/', async (req, res) => {
2 let data = {
3 session: req.cookies[COOKIE] && JSON.parse(req.cookies[COOKIE])
4 }
5
6 if (data.session && data.session.token) {
7 let githubData
8 try {
9 githubData = await getGitHubData(data.session.token)
10 } catch (error) {
11 githubData = { error: error }
12 }
13 _.extend(data, githubData)
14 }
15
16 if (data.session) {
17 data.session.token = 'mildly obfuscated.'
18 }
19 data.json = stringify(data, null, 2)
20
21 res.render('main', data)
22})

Now you have to edit the main.handlebars template to show the data from the API.

1<!DOCTYPE html>
2<html>
3
4 <head>
5 <title>GitHub API Example</title>
6 <meta name="description" content="Github API app with Nodejs Server.">
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge">
9 <meta name="viewport" content="width=device-width, initial-scale=1">
10 </head>
11
12 <body>
13 <header>
14 <h1>Github API Data</h1>
15 <p>This page uses Handlebars as the template engine.</p>
16 </header>
17
18 <main>
19
20 {{#if session}}
21 <p>
22 You are now logged in.
23 </p>
24 <p>
25 <a href="/logoff">Sign out</a>
26 </p>
27 {{else}}
28 <a class="container" href="/auth/github">Log-in with GitHub</a>
29 {{/if}}
30
31
32 {{#if repos}}
33 <h2>{{session.user.name}}'s Repositories</h2>
34 <ul class="collapsible">
35 {{#each repos}}
36 <li>{{full_name}} -- owned by {{owner.login}}</li>
37 {{/each}}
38 </ul>
39 {{/if}}
40 </main>
41
42 </body>
43
44</html>

In the above template, start by adding a button for logging into the application using Github credentials. Start the server by executing the command npm start and then visit the browser window.

ss7

If the user is logging in for the first time, they will be asked to authorize the app, as shown below.

ss8

Once the app is authorized, the user will be welcomed by the following screen.

ss10

This screen will only exist if the user is available in the session. In the template above, you are using an if/else statement to check for incoming data to the template from the index route and whether it consists of the session or not.

The session contains the user and its details, hence when displaying user's name along with repository data, session.user.name is being used.

Conclusion

This completes this demo of integrating Github API with a Nodejs server application, fetching data from the API, implementing OAuth in the simplest way manner and using Crowdbotics platform to host the app.

Originally published at Crowdbotics

I'm Aman working as an independent fullstack developer with technologies such as Node.js, ReactJS, and React Native. I try to document and write tutorials to help JavaScript, Web and Mobile developers.