Since I started working with Trails, I have come to like its approach to a lot of things. I feel its what I have always been looking for in a Node web framework. At the moment the documentation is not completed but there is enough information to got you started with Trails.

From my last post you can see I am trying to build something with the Instagram API, I had to change from using the bell for social authentication to the actual [Instagram node library][], the reason for this was that both were not compatible with each other (this however may be due to my lack of experience, if you know how to get them to work together reach out to me please). When I changed the code I started by writing the social auth in InstagramController, auth method. For authentication I was following an article about building a an instagram postcard app and they suggested using Bluebird to convert callbacks into promises. This is what my InstagramController looked like after I was finished:

'use strict'

const instagramApi = require('instagram-node').instagram()
const Bluebird = require('bluebird')
const Controller = require('trails/controller')

/**
 * Promisify the instagram API library
 */
Bluebird.promisifyAll(instagramApi)

/**
 * @module InstagramAuthController
 * @description Social authentication with Instagram.
 */
module.exports = class InstagramController extends Controller {

  auth(request, reply) {
    const instagramRedirectUri = this.app.config.get('instagram_redirect_uri')
    const clientId = this.app.config.get('instagram_client_id')
    const clientSecret = this.app.config.get('instagram_client_secret')

    instagramApi.use({
      client_id: clientId,
      client_secret: clientSecret
    })

    reply.redirect(instagramApi.get_authorization_url(redirectUri))
  }

  handleAuth(request, reply) {
    const instagramRedirectUri = this.app.config.get('instagram_redirect_uri')
    
    instagramApi.authorize_userAsync(request.query.code, redirectUri)
      .then(function (result) {
        request.cookieAuth.set({
          accessToken: result.access_token
        })

        return reply.redirect('/')
      })
      .catch(function (errors) {
        console.log('Blowing up: ', errors)
      })
  }
}

This was looking good, It was doing the social authentication just as I wanted it to, I could now move on to getting actual data from Instagram and start building the rest of my app. I decided to just test this out by using the default ViewController that is created when you first run the Trails generator. I wrote some code in it that look like the code below:

'use strict'

const instagramApi = require('instagram-node').instagram()
const Bluebird = require('bluebird')
const Controller = require('trails/controller')

/**
 * Promisify the instagram API library
 */
Bluebird.promisifyAll(instagramApi)

module.exports = class ViewController extends Controller {

  helloWorld (request, reply) {
    const { accessToken } = request.auth.credentials

    if (accessToken) {
      instagramApi.use({ access_token: accessToken })
      instagramApi.user_self_media_recentAsync(10)
        .then(function (medias, pagination, remaining, limit) {
          reply(medias.map(function (image) {
            return image.images.standard_resolution.url
          }))
        })
        .catch(function (errors) {
          console.log(errors)
        })
    } else {
      reply('Hello Trails.js')
    }
  }
}

I won’t be going through what each part of the code is doing, but have you noticed something common amongst the two controllers? They both seem to be repeating code.

Let try and keep it DRY

So you can see in both controllers we are requiring the Instagram api library and the Bluebird library. But not just that alone, we are also Promisifying (not even sure this is a word) the Instagram in both controllers, we can reduce this by moving our code into what Trails call a Service (this is not specific to Trails as you will find service class in other programming languages and frameworks). Before we start doing this, we can also start to think of how we want our dependencies to work, wether we should pass down an entire object or just pass down what we need. I am from a [PHP][] background where we advocate dependency injection heavily. I made the decision that my Service should be as agnostic to the framework I am using as much as it can. This is what my InstagramService looks like:

'use strict'

const instagramApi = require('instagram-node').instagram()
const Bluebird = require('bluebird')
const Service = require('trails/service')

/**
 * Promisify the instagram API library
 */
Bluebird.promisifyAll(instagramApi)

/**
 * @module InstagramService
 * @description Deals with all interaction with instagram
 */
module.exports = class InstagramService extends Service {

  authenticate(clientId, clientSecret, redirectUri) {
    instagramApi.use({
      client_id: clientId,
      client_secret: clientSecret
    })

    return instagramApi.get_authorization_url(redirectUri)
  }

  authorize(code, redirectUri) {
    return instagramApi.authorize_userAsync(code, redirectUri);
  }

  getRecent(accesToken, limit=10) {
    return this._getApi(accesToken).user_self_media_recentAsync({count: limit})
  }

  _getApi(accessToken) {
    instagramApi.use({ access_token: accessToken })
    return instagramApi
  }
}

Now with my new InstagramService class in place, I can start using it in places where I was repeating code before, so now I can revisit my InstagramController and my ViewController and make the changes necessary. We can remove the code from the InstagramController auth method to:

auth(request, reply) {
  const instagramRedirectUri = this.app.config.get('instagram_redirect_uri')
  const clientId = this.app.config.get('instagram_client_id')
  const clientSecret = this.app.config.get('instagram_client_secret')

  const instagramAuthUri = this.app.services.InstagramService.authenticate(
    clientId,
    clientSecret,
    instagramRedirectUri
  )

  reply.redirect(instagramAuthUri)
}

I know what you are probably thinking, that didn’t change much of the code. It doesn’t even look like it saved that many lines of code either, but we did move logic that’s relating to Instagram api to somewhere else, this also made sure that in the InstagramController we no longer need:

const instagramApi = require('instagram-node').instagram()
const Bluebird = require('bluebird')

/**
 * Promisify the instagram API library
 */
Bluebird.promisifyAll(instagramApi)

This is also true for the ViewController, but lets move on to the handleAuth method:

handleAuth(request, reply) {
  const instagramRedirectUri = this.app.config.get('instagram_redirect_uri')

  this.app.services.InstagramService.authorize(
    request.query.code,
    instagramRedirectUri
  )
  .then(function (result) {
    request.cookieAuth.set({
      accessToken: result.access_token
    })

    return reply.redirect('/')
  })
  .catch(function (errors) {
    console.log('Blowing up: ', errors)
  })
}

Oh man, this still seem like we haven’t done that much changes. But we have hidden a bit of implementation detail.

Hiding implementation details isn’t a bad thing, it allows us to have a simple API to interact with our code whose details are hidden

Now let us move on to the ViewController to see how this has helped. I can now refer to the same service again which will have my promisified version of the Instagram api ready to use.

helloWorld (request, reply) {
  const { accessToken } = request.auth.credentials

  if (accessToken) {
    this.app.services.InstagramService.getRecent(accessToken, 5)
      .then(function (medias, pagination, remaining, limit) {
        reply(medias.map(function (image) {
          return image.images.standard_resolution.url
        }))
      })
      .catch(function (errors) {
        console.log(errors)
      })
  } else {
    reply('Hello Trails.js')
  }
}

Do note I don’t have any databse setup in place as yet, currently I am just storing the Instagram access token in session cookie.