Wouldn't it be nice to get a text message if one of your websites is down? In this tutorial, I'll show you how to build a minimum viable uptime monitoring service.

Quick Start without Tutorial

git clone https://github.com/focuswish/uptime-monitoring.git
cd uptime-monitoring
npm install

Running Locally

Edit .env:

TWILIO_ACCOUNT_SID={YOUR TWILIO ACCOUNT SID}
TWILIO_AUTH_TOKEN={YOUR TWILIO AUTH TOKEN}
TWILIO_NUMBER={YOUR TWILIO REGISTERED NUMBER}
RECIPIENT_NUMBER={YOUR NUMBER}

Deploying With Heroku

Create A New Heroku App

cd uptime-monitoring
heroku create
heroku buildpacks:set https://github.com/heroku/heroku-buildpack-nodejs#v107

Set Environmental Vars

heroku config:set TWILIO_ACCOUNT_SID={YOUR TWILIO ACCOUNT SID}
heroku config:set TWILIO_AUTH_TOKEN={YOUR TWILIO AUTH TOKEN}
heroku config:set TWILIO_NUMBER={YOUR TWILIO REGISTERED NUMBER}
heroku config:set RECIPIENT_NUMBER={YOUR NUMBER}

Deploy to Heroku

git remote -v ## check to make sure you've got the right heroku remote 
git push heroku master

Uptime Monitoring Tutorial

We'll use Node. I'll assume that node and npm are installed on your system.

Let's start from scratch.

mkdir uptime-monitoring && cd uptime-monitoring
npm init
touch index.js

First, let's install isomorphic-fetch, twilio, and node-schedule:

npm i twilio isomorphic-fetch node-schedule --save

Why these modules?

We'll use:

  • twilio to send an SMS alert when a site is down

  • isomorphic-fetch to make a simple get request to some websites

  • node-schedule to run our uptime-monitoring microservice at regular intervals

require('isomorphic-fetch')

const sites = [
  'http://www.example.com'
]

const run = async () => {
  for(let i in sites) {
    var resp = await fetch(url).catch(err => { return err })  
    if(resp && resp.status >= 200 && resp.status < 300) {
      // Site is OK
    } else {
      // Site is down; send an SMS
    }
  }
}

run() // call run() to test things out

The above snippet makes a fetch request to the URIs in sites.

We'll use async/await syntax, but you could just as well use then.

If you don't want to usea for() loop, you could use this syntax:

var responses = await Promise.all(sites.map(site => {
  // Map needs to be wrapped in Promise.all()
  // Don't forget that await can only be used if the enclosing function is async
  return fetch(site)
}))

Sending An SMS With Twilio

First, we need to require the twilio node module. Sending an SMS is easy.

var twilio = require('twilio');

const accountSid = ''; // from your twilio dashboard
const authToken = '';  // from your twilio dashboard
var twilioClient = new twilio(accountSid, authToken);

const sendMessage = async () => {
  // to is the recipient phone number.
  // from is the number you registered with twilio to send text messages.
  // body is the actual message to send.
  // Note: to and from must be formatted with the country code: +12345678910.
  return await twilioClient.messages.create({body, to, from}) 
}

If You Don't Have Twilio Account

Create an account at twilio.com. Once you register, you'll see your ACCOUNT SID and AUTH TOKEN in your dashboard. Lastly, you'll need to buy a phone number (the first one is free) to dispatch the text messages.

twilio-buy-a-number.png

What we have so far:

  • given a list of sites, make a GET request to each site.

  • If the site is down (e.g., resp.status === 404), then send a text message via twilio.

Using Node-Schedule

We can use node-schedule to execute our function at regular intervals. Node schedule accepts a crontab and a callback as follows:

const schedule = require('node-schedule')
var j = schedule.scheduleJob('*/5 * * *', () => {
  // function to execute every 5 minutes
})

crontab.png

Putting everything together, we have:

const schedule = require('node-schedule')
const twilio = require('twilio')
require('isomorphic-fetch')

const sites = ['http://www.example.com']

const config = {
  accountSid: '',
  authToken: '',
  twilioNumber: '',
  recipientNumber: ''
}

var twilioClient = new twilio(config.accountSid, config.authToken);

const sendMessage = async (site) => {
  return await twilioClient.messages.create({
    body: `${site} is down!`,
    to: config.recipientNumber, 
    from: config.twilioNumber
  })
}    


const run = async () => {
  for(let i in sites) {
    var resp = await fetch(sites[i]).catch(err => { return err })
    if(resp && resp.status >= 200 && resp.status < 300) {
      // Site is OK
      console.log(`${sites[i]} is OK`)
    } else {
      // Site is down; send an SMS
      var msgResp = await sendMessage(sites[i])
        console.log(`${sites[i]} is Down. SMS status: ${msgResp.status}`)
    }
  }
}

if(process.env.NODE_ENV !== 'production') {
  run()
} else {
  schedule.scheduleJob('*/5 * * * *', () => { run() })
}

Testing things out:

NODE_ENV=dev node index.js

Here's a link to the github repo.