Archive for January 14, 2019

Replace tedious coding with MongoDB Stitch and public APIs

Most apps and websites contain much functionality that’s tedious to implement and has been done thousands of times before. MongoDB Stitch takes care of a lot of these chores – for example, making it simple to interact with third-party APIs. This post explains how I used Stitch to do just that that this week.

I recently described how I ported my legacy website to Stitch. After reading that post, one of our Paris Solution Architects spotted that there was a bug in how I was validating phone numbers in my forms (specific to France’s dialling plan). I was about to go into my code to tweak the regular expressions to cope with French phone numbers. However, what if there was another country with odd rules? What if the rules changed?

It then struck me that this is precisely the kind of chore that I should use Stitch to replace – being an expert in regular expressions and country-specific dialling plans wasn’t going to make the experience any better for my users, and so I should contract it out to “someone” else.

A quick Google later, and I’d settled on the NumVerify service which provides an API to validate phone numbers and add some extra information such as the type of line and the same number in full international format.

To access the NumVerify API, I needed to create a Stitch HTTP service. I named the service apiplayer.net and created a rule to allow the service to access the required domain (apiplayer.net) using GET requests:

Creating an HTTP Service rule in MongoDB Stitch to access REST APIs

Then I can call use that service from a new Stitch Function (verifyPhoneNumber):

exports = function(phoneNumber){
  const APIKey = context.values.get("numverifyAPIKey");
  const http = context.services.get("numverify");
  const url = "http://apilayer.net/api/validate?access_key=" + APIKey + "&number=" + phoneNumber + "&country_code=&format=1";
  var validationResults;

  return http.get({"url": url})
  .then((resp) => {
      const result = EJSON.parse(resp.body.text());
      if (result.valid) {
        console.log(result.international_format);
        return result;
      } else {
        console.log(result.number + " is not a valid phone number");
        return {valid: false};
      }
    },
    (error) => {
      console.log("Verification failed: " + error);
      return {valid: false};
    }
  );
};

Note that numverifyAPIKey is a Stitch Value that I’ve set to the API key I received when registering with NumVerify.

A typical (successful) result from this function looks like:

{
  "valid": true,
  "number": "448449808001",
  "local_format": "08449808001",
  "international_format": "+448449808001",
  "country_prefix": "+44",
  "country_code": "GB",
  "country_name": "United Kingdom of Great Britain and North",
  "location": "",
  "carrier": "",
  "line_type": "special_services"
}

The JavaScript running in the browser now executes the Stitch Function:

function validatePhone (inputField, helpText) {
  return new Promise(function(resolve, reject) {
    if (!validateNonEmpty (inputField, helpText)) {resolve(false)} else {
      const client = stitch.Stitch.defaultAppClient;
      client.callFunction("verifyPhoneNumber", [inputField.value]).then(
      result => {
        if (result.valid) {
          inputField.value = result.international_format;
          resolve(true);
        } else {
          helpText.innerHTML = 
            '<span class="english_material" lang="en">Phone number is invalid."</span>\
             <span class="french_material" lang="fr">Le numéro de téléphone est invalide.</span>';
             resolve(false);
        }
      }),(
      error => {
        helpText.innerHTML = 
          '<span class="english_material" lang="en">Phone number validation failed, but you can submit anyway."</span>\
           <span class="french_material" lang="fr">La validation du numéro de téléphone a échoué, mais vous pouvez quand même soumettre.</span>';
        // Not going to reject the form request just because the cloud service
        // is unavailable
        resolve(true);
      });
    }
  })   
}

NumVerify is as flexible as possible in accepting phone numbers in different formats. It also sets the resulting international_format field to a fully formatted international number – I use that to replace the user-provided number in the form, ensuring that all numbers stored in Atlas are in the same format. In one swoop, I’ve ripped out my spaghetti regular expression code, made the validations more robust, and added a new feature (normalizing the phone numbers before I store them) – result!

See the results at stitch.oleronvilla.com, but please don’t judge my code, I still need to delegate a lot more to Stitch and other services!

Creating your first Stitch app? Start with one of the Stitch tutorials.

Want to learn more about MongoDB Stitch? Read the white paper.





From Legacy Website to Serverless with MongoDB Stitch Hosting

From the start, Stitch has been great at serving up dynamic content and interacting with backend services. There was just one piece missing that meant that you still needed an app server or hosting service – storing and serving up your static assets (index.html, style.css, logo.png, etc.). The recent addition of Stitch Hosting, currently in beta, fixes that!

Following the dogfooding principle, I decided to try porting one of my existing websites to Stitch (the site is for renting out our holiday home). Now, this isn’t some newfangled website with all the latest bells and whistles. I’d built the original site before the serverless and mobile-first movements took off; using PHP in the backend and JavaScript/JQuery in the frontend. The vast majority of the porting effort went into replacing the backend PHP code with frontend JavaScript. Once that was done, porting it to Stitch was a breeze.

Step 1 was to enable Hosting through the Stitch UI and upload the (907) static files. You can upload all the files manually through the UI, but here’s a pro-tip, you can perform a bulk upload using the Stitch CLI:

stitch-cli import --include-hosting --strategy=replace

When I made further edits, I used the Stitch UI to upload the modified files:

Upload modified static files through the MongoDB Stitch UI

When users submitted forms on the original site, I was sent an email via GoDaddy’s temperamental gdform.php script (which writes the data to a local file, which GoDaddy’s cron job may eventually process to send the email), and I was looking forward to ripping that out and using Stitch QueryAnywhere instead. The frontend code to store the data in MongoDB is straightforward:

function addBooking (booking) {
    const client = stitch.Stitch.initializeDefaultAppClient('oleronvilla-xxxxx');
    const db = client.getServiceClient(stitch.RemoteMongoClient.factory, 
        'mongodb-atlas').db('oleron');
    const collection = db.collection('bookings');

    return client.auth.loginWithCredential(
            new stitch.AnonymousCredential()).then(user => {
        return collection.insertOne({owner_id: client.auth.user.id, 
                                    bookingRequest: booking})
            .catch(err => console.error(err.message))});
}

So that safely stores the user-submitted data in MongoDB. The next step was to bring the site into the 1990s by having it send me a text message with the submitted data. I could have gone back to the frontend code to send the data to a Stitch Function (in addition to writing it to Atlas), but, as my legacy frontend code is somewhat ‘fragile’, I prefer to make as few changes there as possible. Stitch Triggers to the rescue! I created a new Stitch Function (textReservation) to send the form data to Twilio:

exports = function(event) {
    const twilio = context.services.get("TwilioText");
    const request = event.fullDocument.bookingRequest;
    const body = "New booking from "
      + request.name.first + " " + request.name.last
      + " for these weeks: " + request.requestedDates.toString() + ". "
      + "Call on " + request.contact.phone
      + " or email " + request.contact.email + ". "
      + "Comment: " + request.comments;
    twilio.send({
        to: context.values.get("phoneNumber"),
        from: context.values.get("twilioNumber"),
        body: body
    }); 
};

The final step was to link this function to a Stitch Trigger which fires whenever Stitch QueryAnywhere adds a new document to the oleron.bookings collection:

Create a MongoDB Stitch Trigger

Note that I used the Stitch option to bring my own domain name. See the results at stitch.oleronvilla.com, but please don’t judge my code!

Creating your first Stitch app? Start with one of the Stitch tutorials.

Want to learn more about MongoDB Stitch? Read the white paper.