How I built an Instagram image gallery using Node.js and Ember.js

·

10 min read

I built an image gallery application that uses the Instagram API. The application displays posts of a specific hash tag from a user (me) with a private Instagram account. It's mostly just a bunch of photos of me drinking coffee and coding but a little insight into the life of Tina from Tinacious Design! This Instagram client was built as a proof of concept to build something in Ember utilizing the Instagram API. Ember itself does not communicate with the Instagram API but rather consumes data that a back-end service that communicates with Instagram has made available. There are both server-side and client-side parts to this application:

  1. A back-end written in Node.js that authenticates against and communicates with the Instagram API
  2. A front-end written in Ember.js that consumes the server-generated data.

You can continue reading to learn how it was done.

Exposing posts in a private Instagram account

The application exposes specific posts using a specific hash tag of a private Instagram account. These posts cannot be seen in the Instagram app unless you are one of my Instagram friends, but because I have authenticated with my account, the posts become available. It is a way to expose specific posts to the public while maintaining the privacy of the account. Essentially, I tag my posts with #tinaciousdesign and then run a script to hit the Instagram API to generate the data that my Ember app can use.

Communicating with Instagram

In order to communicate with Instagram, you need to authenticate with the Instagram API using OAuth. Setting this up can be a tricky process. Since my application was just built to display my own Instagram posts with a specific hash tag, my use case did not require the support of user authentication. My solution instead was to create an endpoint that once hit would make a request to the Instagram API with the required token and get all of the Instagram posts with that specific hash tag.

The server-side application built in Node.js

The basis of the application is very simple. It's a Node.js application that uses Hapi.js to create a server and endpoint to hit in order to spawn off the process.

Route

The route is very simple and uses Hapi.js and a custom handler. More about the photosHandlerlater, but here is how you set up a simple route in Hapi.js:

const Hapi = require('hapi');
const server = new Hapi.Server();

server.connection({ 
  port: process.env.PORT || 1337, 
  routes: { cors: true } 
});
server.route({ 
  method: 'GET', 
  path: '/photos', 
  handler: photosHandler 
});

server.start(() => {
  console.log(`Server running at ${server.info.uri}`)
});

Contacting Instagram

Initially I tried some of the Instagram wrappers out there for Node.js but I found that they were not necessary. I instead opted to just use the request module, a simple module. It's very similar to the request package in Python. In order to make requests to the Instagram API, a token is required. Setting up authentication and obtaining a token can be tricky. I'm not going to get into it. For the sake of this tutorial, the access token is available as an environment variable. Below is a Hapi.js route handler function. I'm not going to get into Hapi.js but essentially, the callback takes a request and a reply object. We just need to pass the reply object to the next function,requestUserMedia, which is the method that does all of the magic:

const photosHandler = (req, reply) => {
  const requestUri = `https://api.instagram.com/v1/users/self/media/recent?access_token=${process.env.TOKEN}`;

  requestUserMedia(requestUri, reply);
 };

The requestUserMedia method takes the request URI and the reply object, and makes a request to the Instagram API for the recent media of the specified user, in this case self, which is the authenticated user's recent media.

const requestUserMedia = (url, reply) => {
  console.log(`Requesting: ${url}`);

  request.get(url, { json: true }, (err, res, body) => {
    // More code goes here
  });
};

I used the request module to make a GET request to the provided Instagram API URL with the access token, requesting JSON format, and providing a callback. The callback is in the standard Node.js callback format, where the error is the first returned parameter, followed by the response object and the response body.

Cherry picking Instagram posts

The posts I want are the ones with the hash tag #tinaciousdesign. Conveniently, the response object has a property tags which is an array of all of the available hash tags. I can use this value to find out if my posts are tagged in the request callback. You can see all available properties in theInstagram API documentation. Node.js best practices usually involve handling the error first, e.g.:

if (error) {
  return reply({ message: 'Failure.', error }).code(res.statusCode);
}

I just relay the Instagram API error message and status code back to the client. Now let's talk about the happy path! Assuming we have no error, we should have received body, the third parameter in the request callback. Here's how we can iterate over all of the posts we received:

let tinaciousDesignPosts = [];

body.data.forEach(media, index => {
  if (media.tags.indexOf('tinaciousdesign') >= 0) {
    tinaciousDesignPosts.push(media);
  }
});

Doing this should return about 10 items. Wait, we don't want 10, we want all of them! One of the reasons I opted to use the Node request module instead of an Instagram wrapper is lack of support (or documentation) for pagination. Thankfully, the Instagram API documentation has the information I need in order to figure out how to get the next items. There is a property on the body called pagination which has a property called next_url. This URL is the next one we need to call in order to get more images. Thankfully, since the logic to make requests for Instagram posts was put in its own function, this is as easy as calling the method with our new URL:

if (body.pagination.next_url) {
  requestUserMedia(body.pagination.next_url, reply);
  return;
}

We want to return early from the next step and recursively call the requestUserMedia function until we have all of the posts with the specified hash tag. Once we have reached the last page of our posts and body.pagination.next_url returns false, we can prepare the data in a nice consumable format we can use and return it to the client:

let data = tinaciousDesignPosts.map(post => {
  return {
    id: index,
    thumbnail: post.images.thumbnail.url,
    image: post.images.standard_resolution.url,
    caption: post.caption.text,
    date_utc: parseInt(post.created_time, 10)
  };
});
return reply({ data });

The fields I need:

  • id for Ember
  • thumbnail for the gallery view
  • full-sized image for the single image view
  • caption to display with the full-sized image
  • date in UTC

Once it's all in a nice package called data I can do a Hapi reply with it. The provided data looks like this:

{
  "data": [
    {
      "id": 1,
      "thumbnail": "https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/s150x150/e35/10012553_435208453335078_714900051_n.jpg",
      "image": "https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/s640x640/sh0.08/e35/10012553_435208453335078_714900051_n.jpg",
      "caption": "JavaScript app development in React. A nice hot cuppa tea and some fancy chocolate truffles. ☕️ #tinaciousdesign",
      "date_utc": 1451420323
    },
    {
      "id": 3,
      "thumbnail": "https://scontent.cdninstagram.com/hphotos-xpf1/t51.2885-15/s150x150/e35/12256593_1661803950744133_1845805885_n.jpg",
      "image": "https://scontent.cdninstagram.com/hphotos-xpf1/t51.2885-15/s640x640/sh0.08/e35/12256593_1661803950744133_1845805885_n.jpg",
      "caption": "Coffee and code ☕️ #tinaciousdesign",
      "date_utc": 1448749779
    }
    // ... More posts here ...
  ]
}

And that's it for the server-side portion!

The client-side application built in Ember.js

Using the data in Ember

The Ember app is quite basic and utilizes the generated JSON as a fixture. I create a model with the fields caption, image and thumbnail, and paste the resulting JSON as the fixture. The rest includes some controller methods to help with paginating the single image post.

The next trick is to create square thumbnails that take up all of the space available while only spanning to their full width, i.e. 100 pixels. When visiting the page, resize the window and see how the thumbnails behave. With the help of a CSS preprocessor, I was able to create a for loop that iterated over a range of numbers and generated a bunch of media queries, calculating the required breakpoint and thumbnail width for each media query. I chose Stylus, but you can do the same with Sass. The resulting output of looks like this:

@media screen and (min-width:300px) {
  .thumbnail {
    width: 33.333333333333336%;
  }
}

@media screen and (min-width:450px) {
  .thumbnail {
    width: 25%;
  }
}

@media screen and (min-width:600px) {
  .thumbnail {
    width: 20%;
  }
}

@media screen and (min-width:750px) {
  .thumbnail {
    width: 16.666666666666668%;
  }
}
/* ... etc., all the way to (min-width: 4500px) ... */

Yay, math! That goes up to @media screen and (min-width:4500px). Imagine writing that out all by hand! With a couple of lines of Stylus and a for loop, we can let the preprocessor do the math and grunt work for us. There was somewhere around 28 to 30 media queries generated with about 4 lines of Stylus.

Check it out!

Now that you had a quick peek at how it was built, check it out! If you've built something with the Instagram API or a different API and would like to share, feel free to leave a comment with links to some code samples!