Instagram is about things that people love and want to share with others (it has selfies, yolo, swag and food pics too) but if there is one thing that the Internet loves and is ... CATS. Everybody loves cats, even the dog people out there love cats secretly. With this in mind, what if we could create a infinity cat photo experience ?! <insert evil laugh here>

The thing is: we can. At the end of this post you will be seated at your chair for days without eat or drink having your soul being drained by an infinity ammount of cats in real time. Let's hack it !

Dependencies

Our package.json will look like this:

...
"dependencies": {
  "body-parser": "^1.13.3",
  "express": "^4.13.3",
  "instagram-node-lib": "^0.1.1",
  "react": "^0.13.3",
  "socket.io": "^1.3.6",
  "socket.io-client": "^1.3.6",
  "vinyl-source-stream": "^1.1.0"
},
"devDependencies": {
  "babel": "^5.8.21",
  "babel-core": "^5.8.22",
  "babelify": "^6.1.3",
  "browserify": "^10.2.4",
  "dotenv": "^1.2.0",
  "gulp": "^3.9.0",
  "gulp-uglify": "^1.2.0",
  "reactify": "^1.1.1"
},
"scripts": {
  "gulp:build": "node node_modules/gulp/bin/gulp build",
  "gulp": "node node_modules/gulp/bin/gulp",
  "start": "node server.js"
}
...

The most relevant libraries for this project:

  • instagram-node-lib - Access the Instagram API.
  • socket.io - Server side web sockets.
  • socket.io-client - Client side web sockets.
  • express - Minimal web framework.

Gulp and Browserify

This time let's run our project with Gulp and Browserify. I tried to use Webpack but sounds like the dev server have some problems with Socket.IO. Maybe by the time you are reading this the problem could have been already solved, so feel free to use what you think is best.

Gulp is a task manager/runner which is very easy to use and setup. Browserify will allow us to execute server side code in the browser. I will get into the setup this time because this post can turn into a TLDR and it's not a requirement to create the app. But if you wanna get more into this libraries just take a look at the links I highlighted above.

To keep things simple you can use this script. Just create a file named gulpfile.babel.js in the project root with:

import gulp from 'gulp'
import browserify from 'browserify'
import reactify from 'reactify'
import source from 'vinyl-source-stream'
import babelify from 'babelify'
import uglify from 'gulp-uglify'

gulp.task('browserify', () => {
  return browserify('./src/main.js')
    .transform(babelify)
    .transform(reactify)
    .bundle()
    .pipe(source('main.js'))
    .pipe(gulp.dest('dist/js'))
})

gulp.task('copy', () => {
  gulp.src('src/index.html').pipe(gulp.dest('dist'))
  gulp.src('src/assets/**/*.*').pipe(gulp.dest('dist/assets'))
})

gulp.task('default', ['browserify', 'copy'], () => {
  return gulp.watch('src/**/*.*', ['browserify', 'copy'])
})

gulp.task('compress', ['browserify'], () => {
  return gulp.src('dist/js/main.js')
    .pipe(uglify())
    .pipe(gulp.dest('dist/js'))
})

gulp.task('build', ['copy', 'browserify', 'compress'])

It is required that you use the file name with .babel.js in the end.

Localhost tunnel

When we are developing in the localhost eveything is simple but in this case we will need a public URL to give it to Instagram. Through this address we will receive the real time notifications. I used for this app the ngrok. You just execute one command with your server up and it generates a random public url with a tunnel to the port you passed as parameter. You can see the ngrok running down below:

Environment variables

To use the Instagram API you need to create a client in their developer site here. They will ask you the Website URL, use the url that ngrok generated for you like the gif above. But remember to not restart the tunnel during you tests or you will have to change the Website URL in your Instagram client profile. After the client registration you will receive a CLIENT_ID and a CLIENT_SECRET. In the root of the project create a file named .env with:

CLIENT_ID=YOUR_CLIENT_ID
CLIENT_SECRET=YOUR_CLIENT_SECRET
CALLBACK_URL=YOUR_HTTPS_GROK_URL/gimmecats

* Don't push your .env to a public repo, remember to add it in the .gitignore.

* The dotenv library don't work on production, don't forget to create the env vars at your production if you wanna deploy it.

* Use your https url as callback url, it's a requirement for this Instagram API now

The CALLBACK_URL is the url where we will receive the Instagram notifications.

Server

Now create a file named server.js, the code of this file will be this:

const express = require('express')
const http = require('http')
const socketio = require('socket.io')
const bodyParser = require('body-parser')
const Instagram = require('instagram-node-lib')

const port = process.env.PORT || 8080
const static_path = './dist'
const app = express()
const server = http.Server(app)
const io = socketio(server)

if (process.env.NODE_ENV !== 'production') {
  require('dotenv').load()
}

Instagram.set('client_id', process.env.CLIENT_ID)
Instagram.set('client_secret', process.env.CLIENT_SECRET)

Instagram.tags.subscribe({
  object: 'tag',
  object_id: 'catsofinstagram',
  aspect: 'media',
  callback_url: process.env.CALLBACK_URL,
  type: 'subscription'
})

app.use(express.static(static_path))
app.use(bodyParser.json())

app.get('/', function (req, res) {
  res.sendFile('index.html', {
    root: static_path
  })
})

app.get('/gimmecats', function (req, res) {
  Instagram.subscriptions.handshake(req, res)
})

app.post('/gimmecats', function (req, res) {
  res.send()
  Instagram.tags.recent({
    name: req.body[0].object_id,
    complete: (data) => {
      io.sockets.emit('cats', { cat: data })
    }
  })
})

server.listen(port)
console.log('Listening ...')

Initializing the Socket.IO and the express server:

const app = express()
const server = http.Server(app)
const io = socketio(server)

Initializing the dotenv library to load our .env file and setting up the Instagram API credentials:

if (process.env.NODE_ENV !== 'production') {
  require('dotenv').load()
}

Instagram.set('client_id', process.env.CLIENT_ID)
Instagram.set('client_secret', process.env.CLIENT_SECRET)

At this point we are subscribing to Instagram notifications. In this example we want to receive a notification everytime someone posts a photo with the tag catsofinstagram. Notice that we are sending our CALLBACK_URL:

Instagram.tags.subscribe({
  object: 'tag',
  object_id: 'catsofinstagram',
  aspect: 'media',
  callback_url: process.env.CALLBACK_URL,
  type: 'subscription'
})

This part confuses a lot of people. You will declare a GET and a POST routes to the /gimmecats path. Instagram needs the GET route to confirm your subscription, so we will do a handshake to confirm that everything is ok to begin the transfer:

app.get('/gimmecats', function (req, res) {
  Instagram.subscriptions.handshake(req, res)
})

Here is where we will receive the notifications through the POST route. The real time api send to us the notification but not the actual media/photo url, so we need to send a request asking for the recent posted photos.
You noticed that we are sending the response to Instagram before make anything with the answer ? It's because if your reponse take too much time to respond your subscription will be dropped and this cat train is leaving with no stops !
When we receive the recent medias let's emit an event through Socket.IO with them:

app.post('/gimmecats', function (req, res) {
  res.send()
  Instagram.tags.recent({
    name: req.body[0].object_id,
    complete: (data) => {
      io.sockets.emit('cats', { cat: data } )
    }
  })
})

React

Now you will create a file named main.js in your /src path to load our component called App:

import App from './components/app'
import React from 'react'

React.render(<App />, document.body)

We will need in the /src path a file named index.html to load our main.js:

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>Cat train with no stops</title>
</head>
<body>
  <script src="js/main.js"></script>
</body>
</html>

And finally our React component app.js in the /src/components path:

import React from 'react'
import io from 'socket.io-client'

export default class App extends React.Component {
  constructor () {
    super()

    const host = location.origin.replace(/^http/, 'ws')
    const socket = io.connect(host)

    this.state = {
      socket,
      content: null
    }

    this.fetchRecents()
  }

  fetchRecents () {
    this.state.socket.on('cats', (data) => {
      this.setState({ cat: data.cat[0].images.standard_resolution.url })
    })
  }

  render () {
    const message = 'Waiting Instagram ... You catta be kitten me, purr.'
    let { content, cat } = this.state

    cat ? content = <img src={this.state.cat} /> : content = message

    const style = {
      marginLeft: '35px',
      marginBottom: '30px'
    }

    return (
      <div>
        <center>
          <div style={style}>
            <img src='/assets/cat.png' />
          </div>
          {content}
        </center>
      </div>
    )
  }
}

Connecting to the web socket host and setting it in the state to be used by the component render:

const host = location.origin.replace(/^http/, 'ws')
const socket = io.connect(host)

this.state = {
  socket,
  content: null
}

When the socket emits the event which our server is sending we will set in the component state the data received. The tag we choosed is very popular, so the Instagram will send a lot of photos to us, but we will pick only one for each notification:

fetchRecents() {
  this.state.socket.on('cats', function (data) {
    this.setState({ cat: data.cat[0].images.standard_resolution.url })
})

While we are not receiving any update let's show a lovely cat themed message to our users and as soon we got something the img tag will render synchronized with the state:

render () {
  const message = 'Waiting Instagram ... You catta be kitten me, purr.'
  let { content, cat } = this.state

  cat ? content = <img src={this.state.cat} /> : content = message

  const style = {
    marginLeft: '35px',
    marginBottom: '30px'
  }

  return (
    <div>
      <center>
        <div style={style}>
          <img src='/assets/cat.png' />
        </div>
        {content}
      </center>
    </div>
  )
}

Running

Now we can start our application with:

$ cd my_app_directory
$ npm run gulp:build
$ npm start

I drawned a super professional logo to our app on paper because we need some style. Here's the result:

Got lost ? Take a look at the repo that I pushed to Github as always ! See you soon :)