MongoDB Atlas: Stitch Functions

DISCLOSURE: I am an employee of MongoDB. All views expressed are my own.

I love finding out about new technologies and products. The process of learning everything from the ground up, finding documentation, watching videos, reading StackOverflow... it all becomes so rewarding the first time you actually get something working.

Over the last few weeks, I've been reading up on MongoDB Stitch, MongoDB's serverless platform on top of a hosted database backend-as-a-service platform. I've had experience with AWS Lambda and Azure Functions. So to learn about how MongoDB does data-driven serverless, lets build a simple link expander application like tinyurl.com or bit.ly.

I already had an account for Atlas using their free M0 tier. I logged in, connected to my cluster, and created a database called linkshortener with two collections called links and use. Basically I want to track a key name, url to redirect to, created date, etc for each link and every time that link is expanded by a user visiting it, I want to store some information about the user. Simple enough.

Next I pressed the button to create a linked Stitch app. Once through the wizard, I go to "services" and create a service. Then I create three incoming webhooks: link (which will expand the link from a short value to its full link), form (which will be a simple HTML form for me to create a new link), and create which takes the input from the form. All three are HTTP GETs but with create I went the extra step of requiring a SECRET which means it will only run if the correct password is provided for some added security; I don't want anyone using my utility.

Code wise, the three JavaScript functions could not be easier. Now I did this without any error handling but you see the point...

Form

// Build out a super simple html form for key, URL, and the sevret password and send it to the create webhook
exports = function(payload, response) {  
  var html = '\
  <!DOCTYPE html> \
<html lang="en">\  
  <head>\
        <meta charset="utf-8">\
        <meta http-equiv="X-UA-Compatible" content="IE=edge">\
        <meta name="viewport" content="width=device-width, initial-scale=1">\
        <title>Grabosky URL Shortener</title>\
    </head><body>\
            <div class="container">\
                <h1>New Link</h1>\
                <form method="GET" action="WEBHOOK_URL_OF_CREATE">\
                    <label for="key">Key:</label> <input type="text" id="key" name="key" class="form-control" /> <br />\
                    <label for="url">URL:</label> <input type="text" id="url" name="url" class="form-control" /> <br />\
                    <label for="pw">Password:</label> <input type="text" id="secret" name="secret" class="form-control" /> <br />\
                    <input type="submit" />
                </form>\
            </div>\
    </body></html>\
  ';
  response.setHeader("Content-Type", "text/html");
  response.setBody(html);
};

Create

// this only gets called if Stitch automatically validated the secret
// so do a simple insert
exports = async function(payload, response){  
  var rc = context.services.get("mongodb-atlas").db("linkshortener").collection("links");
  var d = new Date(Date.now());
  var html="";
  rc.insertOne({"query":payload, "key": payload.query.key, "url": payload.query.url, "when": d});
  html = "Created Record.";
  if(html.length > 1) {
    response.setHeader("Content-Type", "text/html");
    response.setBody(html);
  }

};
// get the short key the user passed in from the payload
// if it exists, do an http redirect (301, file moved)
// if it doesnt, tell the user the key was bad
// regardless, log the attempt which has info about user
exports = async function(payload, response){  
  var rc = context.services.get("mongodb-atlas").db("linkshortener").collection("links");
  var d = new Date(Date.now());
  var html="";
  var url = "";
    let doc = await rc.findOne({"key":payload.query.key});
    var wc = context.services.get("mongodb-atlas").db("linkshortener").collection("use");
    if (doc){
      wc.insertOne({"query":payload, "key":doc.key, "url":doc.url, "when":d, "valid":true});
      url = doc.url;
    }
    else {
      html = "Bad link.";
      wc.insertOne({"query":payload, "when":d, "key":payload.query.key, "valid":false});
    }

  if(html.length > 1) {
    response.setHeader("Content-Type", "text/html");
    response.setBody(html);
  }
  else {
    response.setStatusCode(301);
    response.setHeader("Location", url);
  }
};

Sample Data Screenshots

Links Collection

Use Collection

So walking through the code. You want to create the Create first so its webhook URL can be used by the form. Then you can create the form to make it easy to create links. Lastly, your link webhook function will expand it. Now to test it out, you can visit the form webhook URL in your browser and create a link shortener for that so you don't forget the URL!

Now the only part that is missing is a short domain name. I use one of my personal short URLs and via the hosting provider I can forward a specific sub domain to this link expander page. And you are done! Atlas has handled all the DB work, Stitch has done all the code execution, and when you created your webhooks Atlas also handled setting up all your IP whitelisting and security rules.