MongoDB Stitch - the latest, and best way to build your app

December 2018 updates: I originally published this post in August 2017, but the development team have been busy since then and made some significant enhancements – most notably, replacing Pipelines with Functions. I've now migrated the application to functions and have updated this post to reflect the changes.

Another significant Stitch addition is the ability to import and export Stitch application backend code and configuration data. This post will show you how to import the Stitch app – there are then optional sections stepping you through creating it from the ground-up using the Stitch UI.

Finally, Stitch Static Hosting could now be used to host the frontend resources (i.e. html, css, and JavaScript files), removing the need for a web hosting service.

While the post has been updated to reflect the major product changes made in the last 18 months, it's inevitable that there will be minor inaccuracies in some of the screen captures and code, but the overall architecture and flow is correct. If you want to start with a simpler project that is kept 100% current then try one of our Stitch tutorials.

In an earlier 6 part blog series on the MEAN & MERN stacks, I stepped through how you can build modern applications on a stack of MongoDB, Node.js, Express, and Angular or React. What if there was a service that took care of everything apart from the from application frontend (Angular, React, or other technology)? MongoDB Stitch is that service, it's a new serverless platform for applications using MongoDB.

The purpose of this post is to introduce what MongoDB Stitch is and, most importantly, demonstrate exactly how you use it – both configuring your app through the Stitch backend UI, and invoking that app backend from your frontend code or other services. Note that MongoDB Stitch is currently in beta and so you should expect the UI to evolve over the coming weeks and months. The tutorials in the Stitch documentation provide always up-to-date examples for creating Stitch applications.

What is MongoDB Stitch?

MongoDB Stitch is a BaaS, giving developers a REST-like API (plus SDKs for JavaScript, iOS, and Android) to MongoDB, and composability with other services, backed by a robust permissioning system for configuring fine-grained data access controls.

Stitch allows developers to focus on building applications rather than on managing data manipulation code or service integration. As application and display logic continues to move into the frontend web and mobile tiers, the remaining backend is often dominated by code to handle storing and retrieving data from a database, enforcing security and data privacy, and integrating various services. MongoDB Stitch provides that backend functionality, so that you don't have to write that boilerplate code.

The data access rules in MongoDB stitch are entirely declarative and designed to be expressive enough to handle any application, including sensitive data such as payment details. For a given collection, you can restrict what operations are permitted and what fields can be accessed – according to user id, role, or custom criteria. Access can even be limited to specific aggregations – allowing analysts to work with the data without exposing any individual's private information.

If you already have data in MongoDB Atlas, you can start by safely exposing it to new applications via Stitch's API – perhaps allowing read access to specific fields. You can authenticate users through built-in integrations with auth providers.

In my previous blog series, I detailed how to work with the technologies that are typically used to make up a modern application backend: MongoDB for the database, Node.js to run the backend logic, and a framework such as Express to provide a REST API:

MEAN Stack and MERN Stack

Stitch, dramatically simplifies your development and ops efforts for new applications by providing the entire backend as managed service. Even your frontend application code is simplified, as Stitch offers idiomatic SDKs for JavaScript, iOS, and Android – so you don't need to code HTTP requests directly. Further, the SDK/API isn't limited to just accessing MongoDB data, it can handle authentication, use registered partner services, or call JavaScript functions you’ve hosted on Stitch.

MongoDB Stitch Serverless architecture

Importing the TrackMe MongoDB Stitch Backend code & configuration

It's now possible to import the application's backend MongoDB Stitch code and configuration (so that you don't need to follow all of the UI steps to create it by hand). These instructions step through getting the app up and running. Note that you will still need to provide your own Twilio, Google, and Facebook API credentials.

If you want to get your app up and running as quickly as possible, follow the instructions in this section and then skip to "Checking into the app using WebHooks" after this section; if you want to understand how this app was built using the UI, keep on working through the whole post.

Download and install the stitch-cli command.

If you haven't already, create a MongoDB Atlas cluster (a free tier is available).

Download the TrackMe app code and config data:

git clone git@github.com:am-MongoDB/trackme_MongoDB_Stitch.git
cd trackme_MongoDB_Stitch

Your directory and file structure under stitch_app will now look like this:

stitch_app/
├── README.md
└── trackme
    ├── auth_providers
    │   ├── anon-user.json
    │   ├── oauth2-facebook.json
    │   └── oauth2-google.json
    ├── functions
    │   ├── addFriend
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── alreadyAFriend
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── amITheirFriend
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── checkin
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── friendsCheckins
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── ownerFromEmail
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── popularCheckins
    │   │   ├── config.json
    │   │   └── source.js
    │   ├── recentCheckins
    │   │   ├── config.json
    │   │   └── source.js
    │   └── sendText
    │       ├── config.json
    │       └── source.js
    ├── secrets.json
    ├── services
    │   ├── externalCheckin
    │   │   ├── config.json
    │   │   ├── incoming_webhooks
    │   │   │   ├── appCheckin
    │   │   │   │   ├── config.json
    │   │   │   │   └── source.js
    │   │   │   └── fourSquareCheckin
    │   │   │       ├── config.json
    │   │   │       └── source.js
    │   │   └── rules
    │   │       └── 596779544fdd1f3144a73bb1.json
    │   ├── mongodb-atlas
    │   │   ├── config.json
    │   │   └── rules
    │   │       ├── app-qhlyz.items.json
    │   │       ├── trackme.checkins.json
    │   │       └── trackme.users.json
    │   └── myTwilio
    │       ├── config.json
    │       └── rules
    │           └── 597b4a220584295fb29f49b8.json
    ├── stitch.json
    └── values
        ├── GoogleMapsStaticKey.json
        ├── stitchLogo.json
        └── twilioNumber.json

Identify all of the places where you'll need to add your own API credentials:

grep -r TODO *
src/config.js:  appId: "TODO: Find this in your MongoDB Stitch console after you create or import the app"
stitch_app/trackme/auth_providers/oauth2-facebook.json:        "clientId": "TODO: Get this from your Facebook dev console"
stitch_app/trackme/auth_providers/oauth2-google.json:        "clientId": "TODO: Get this from your Google API dev console"
stitch_app/trackme/values/GoogleMapsStaticKey.json:    "value": "TODO: Get this from your Google dev console",
stitch_app/trackme/values/twilioNumber.json:    "value": "TODO: Get this from your Twilio account",
stitch_app/trackme/secrets.json:      "clientSecret": "TODO: Get this from your Facebook dev console"
stitch_app/trackme/secrets.json:      "clientSecret": "TODO: Get this from your Google API dev console"
stitch_app/trackme/secrets.json:      "auth_token": "TODO: Get this from your Twilio account"
stitch_app/trackme/services/mongodb-atlas/config.json:        "clusterName": "TODO: This should be the name of your Atlas cluster - e.g. Cluster0"
stitch_app/trackme/services/myTwilio/config.json:        "sid": "TODO: Get this from your Twilio console"

Add all of your API credentials, apart for the appId in src/config.js (you won't have that value until you import the app backend).

For the next step, you need to find your Project ID from the settings section in the Atlas UI. Under your account settings, whitelist your IP address in the Public API Access tab; while you're their, create and take note of a new API key.

You can now import the application:

cd stitch_app/trackme/
stitch-cli login --username=andrew.morgan --api-key=<your Atlas API key goes here>
stitch-cli import
this app does not exist yet: would you like to create a new app? [y/n]: y
Atlas Group ID: 574423c3e4b0ba1b3xxxxx
App name: trackme
New app created: trackme-etjzr
Successfully imported 'trackme-etjzr'

Take a note of the new App ID (trackme-xxxxx) and add it to srtc/config.js.

Check that you haven't missed adding any credentials:

cd ../..
grep -r TODO *

Install the dependencies for the application frontend and launch the frontend app:

npm install
npm start

Building an application with the MongoDB Stitch UI (OPTIONAL)

You can get started with MongoDB Stitch for free – use it with your free MongoDB Atlas cluster. If you already registered for MongoDB Atlas then you can create your MongoDB Stitch apps with your existing Atlas group.

Creating your application in MongoDB Stitch

The app that we're building will record user checkins (from FourSquare, an iOS app, or an iOS Workflow applet) in MongoDB Atlas, and then make them visible to the user and their friends through a React/JavaScript web app.

As we work through the tutorial, no previous knowledge is assumed, but at points, you may need to refer back to the earlier blog series (e.g. for details on creating a React application frontend).

If you haven't done so already, create a new MongoDB Atlas cluster, selecting the M0 instance type for the free tier (if you already have an Atlas cluster, feel free to use that):

Creating a MongoDB Atlas cluster

After the cluster has spun up, click on Stitch Apps and then Create New Application:

Create new MongoDB Stitch application

Give the application a name and ensure that the correct Atlas cluster is selected:

Name your MongoDB Stitch BaaS appName your MongoDB Stitch BaaS app

Once you've created the application, take a note of its App ID (in this example trackme-pkjif) as this will be needed by your application's frontend:

MongoDB Stitch serverless application details

Backend database and rules

Select the mongodb-atlas service, followed by the Rules tab – this is where you define who can access what data from the MongoDB database:

MongoDB Stitch BaaS data rules

Set the database name to trackme and the collection to checkins:

MongoDB Stitch – naming a collection

MongoDB Stitch - select collection

A typical record from the track.checkins collection will look like this:

db.checkins.find().sort({_id: -1}).skip(2).limit(1).pretty()
{
    "_id" : ObjectId("597f14fe4fdd1f5eb78e142f"),
    "owner_id" : "596ce3304fdd1f3e885999cb",
    "email" : "me@gmail.com",
    "venueName" : "Starbucks",
    "date" : "July 31, 2017 at 12:27PM",
    "url" : "http://4sq.com/LuzfAn",
    "locationImg" : "http://maps.google.com/maps/api/staticmap?center=51.522058,-0.722497&zoom=16&size=710x440&maptype=roadmap&sensor=false&markers=color:red%7C51.522058,-0.722497&key=AIzaSyC2e-2nWNBM0VZMERf2I6m_PLZE4R2qAoM"
}

Select the Field Rules tab and define read and write rules for the Top-Level document:

Defining a MongoDB Stitch write rule

Set the read rule to:

{
  "%or": [
    {
      "%%root.owner_id": "%%user.id"
    },
    {
      "%%true": {
        "%function": {
          "name": "amITheirFriend",
          "arguments": [
            "%%root.owner_id"
          ]
        }
      }
    }
  ]
}

With this configuration, a document can only be read from this collection if either Its owner_id field matches the id of the application user making the request (i.e. a user can read their own data.)%%user is an expansion which gives the rule access to information about the application end-user making the request – here we're interested in their unique identifier (id). Whenever a user adds a document to a collection, Stitch will set the owner_id to the ID of that user. Or it was created by a user that had added this user to their list of friends. We will see the implementation of the amITheirFriend function later in this document.

Overwrite the write rule with the following:

{
  "%or": [
    {
      "%%prevRoot.owner_id": "%%user.id"
    },
    {
      "%%prevRoot": {
        "%exists": {
          "$numberInt": "0"
        }
      }
    }
  ]
}

%%prevRoot is another expansion, representing the state of the document before the operation. You can read the above logic as: "Allow the write to succeed if either the the same user previously added the document or the document didn't exist (i.e. it's an insert)".

In addition to general rules for the document, read/write rules can be added for individual fields. Select the owner_id field and ensure that the validation rule is set to:

{
  "%or": [
    {
      "%%prev": "%%user.id"
    },
    {
      "%%prev": {
        "%exists": false
      }
    }
  ]
}

MongoDB Stitch field validation rule

Filters control which documents a user sees when querying a collection:

MongoDB Stitch collection filter

Ensure that When == {"%%true": true} and overwrite MATCH EXPRESSION with {}. Without that change, a user's query will ignore anyone else's documents – preventing from seeing their friends' check ins.

You should also add rules for the trackme.users collection, where a typical document will look like:

> db.users.findOne()
{
    "_id" : ObjectId("596e354f46224c3c723d968a"),
    "owner_id" : "596ce47c4fdd1f3e88599ac4",
    "userData" : {
        "email" : "andrew.morgan@mongodb.com",
        "name" : "Andrew Morgan",
        "picture" : "https://lh4.googleusercontent.com/-lCBSTZFxhw0/AAAAAAAAAAI/AAAAAAAAAB4/vX9Sg4dO8xE/photo.jpg"
    },
    "friends" : [
        "billy@gmail.com",
        "granny@hotmail.com"
    ]
}

You should setup trackme.users with the same write rule and filters as trackme.checkins but the read rule is different as we need any user to be able to read everyone's email address:

  • Remove the Top-Level Document read rule.
  • Set the userData.email read rule to {}.
  • Enable Allow All Other Fields, then set the read rule to {"owner_id": "%%user.id"}

Let any user read email addresses

Values/constants

Stitch provides a simple and secure way to store values associated with your application – a perfect example is your keys for public cloud services. Set up the following values:

Define MongoDB Stitch serverless values

Create new values for GoogleMapsStaticKey (you receive this when registering the app through your Google console), stitchLogo (the URL for our application logo), and twilioNumber (the phone number that you created with Twilio.

By default, your WebHooks, functions, and frontend application code can read the values. By setting the value to be private, you prevent access from your frontend code (or any other users of the Stitch API). Only our backend logic needs access to the Google key and Twilio number and so you should set the Private switch for those two values.

Authentication providers

A key feature of Stitch is authenticating your app’s end users – after which you can configure precisely what data and services they’re entitled to access (e.g., to view documents that they created through their actions in the app). The following types of authentication are all supported:

  • Anonymous (the user doesn’t need to register or log in, but they’re still assigned an ID which is used to control what they see)
  • Email/Password
  • Google
  • Facebook
  • Custom (using JSON web tokens)

From the Authentication section of the Stitch UI, turn on Google authentication, providing the Client ID and Client Secret [generated by Google](https://docs.mongodb.com/stitch/auth/google-auth/ ""MongoDB Stitch BaaS – using Google authentication credentials"). If you are running your app on your local machine then add http://localhost:3000/ as a Redirect URI; if hosting externally, add the DNS hostname. Enable Name, Picture, and Email so that your app has access to those user credentials from Google. Click Save.

MongoDB Stitch BaaS – adding Google authentication

Turn on Facebook authentication, providing the Client ID and Client Secret generated by Facebook. If you are running your app on your local machine then add http://localhost:3000/ as a Redirect URI; if hosting externally, add the DNS hostname. Enable Name, Picture, and Email so that your app has access to those user credentials from Facebook. Click Save.

MongoDB Stitch BaaS - adding Facebook authentication

MongoDB Stitch BaaS authentication providers

Adding other services (Twilio)

Stitch has some services pre-integrated, for others, you can use the HTTP Service.

From the React web app, a logged-in user has the option to send an SMS text message, containing their latest checkin, to a phone number of their choice. To enable that service, you must configure the Twilio Service through the Slack UI:

MongoDB Stitch, configuring Twilio service

The values to use for the Account SID and the Auth Token can be retrieved after registering with Twilio. The app will not accept incoming messages from Twilio, and so there is no need to define any incoming WebHooks. In the Rules tab, enable the Send action and click Save:

Configure Twilio rules in MongoDB Stitch

Stitch Functions

MongoDB Stitch Functions allow you to provide your own business logic to be executed in the Stitch backend, in addition to interacting with external services. These functions can be executed from your application frontend, from other functions, from your Stitch rules, or from your Webhooks. You write functions in JavaScript (ES5) and the can use context to access services and other contextual information.

  • Services like Twilio or GitHub can be accessed through context.services.
  • Functions can execute other functions using context.functions.
  • Information about the requesting user can be accessed through context.user.
  • Global variables can be defined in Stitch and referenced with context.values.

The benefits of Stitch Function include:

  • Code reuse: you can create the function once in the Stich backend, and then invoke it from multiple frontend locations (e.g., from multiple places in a web app, as well as from iOS and Android apps, and also from your Stitch rules and Webhooks).
  • Simpler code: keep the frontend application code clean by hiding the function’s details in the Stitch backend.
  • Enhanced security: access to secret resources, such as API keys, can be encapsulated within the Stitch backend. The alternative is to code them in the device-side code, where a user may attempt to reverse-engineer them.

When creating a function, there is a set of information you must always provide:

  • The name of the function. The name is how your frontend application code, WebHooks, Stitch rules, or other functions can execute this function.
  • Whether the function is private. If set to true, you can only invoke the function from within the Stitch backend. If set to false then you can also invoke it directly from your application’s frontend code (or from Stitch’s Debug Console).
  • You can control under what scenarios a function is allowed to run by providing a JSON document – if it evaluates to true then the pipeline can run.
  • You must provide the JavaScript code to implement the function's logic:
    • The code must always take the form: exports = function(...){// Your code;}.
    • Functions can optionally accept arguments: `exports = function(arg1, arg2){...}.
    • Functions can optionally return a result (value, document, array) to the caller.

The first function to create is recentCheckins which returns an array of the user’s most recent checkins.

Creating MongoDB Stitch function

We will need to call function through the Stitch SDK and so we must leave Private unchecked. We don't need any restrictions on when recentCheckins can be called and so we set Can Evaluate to {}.

This is the code for the recentCheckins function:

exports = function(limit){

    var atlas = context.services.get("mongodb-atlas");
    var checkinColl = atlas.db("trackme").collection("checkins");
    var checkins = checkinColl.find({"owner_id": context.user.id})
.sort({_id: -1}).limit(limit).toArray();
    return checkins;
};

When invoking the function, the caller must provide a single parameter (limit) which specifies the maximum number of checkins it should return. If you've used the MongoDB Query Language then this code should look very familiar. The database lookup uses the {"owner_id": context.user.id} query to ensure that only the data for the current user are found (Note that context is able to access resources within Stitch, including user credentials, services, defined values, and other functions). The function returns an array of the user's most recent checkins.

be empty.

The second function to define is friendsCheckins to retrieve the most recent checkins of users who have befriended the current user. As with recentCheckins, Private should be left off and *Can Evaluateshould be set to{}`.

This is the code for friendsCheckins:

exports = function(limit) {

    var atlas = context.services.get("mongodb-atlas");
    var usersColl = atlas.db("trackme").collection("users");
    var checkinsColl = atlas.db("trackme").collection("checkins");
   
    // Find all of the users for which I'm a friend
    var myEmail = usersColl.findOne({"owner_id": context.user.id})
        .userData.email;
    var friends = usersColl.find({"friends": myEmail}).toArray();
    
    // Build an array of the checkins for each friend
    var friendCheckins = [];
    friends.forEach(function(friend){
      var friendCheckin = {_id: friend.userData.email, checkins: []};
      checkinsColl.find({owner_id: friend.owner_id},
          {_id:0, venueName:1, date:1, url:1, locationImg: 1})
          .sort({_id: -1}).limit(limit).toArray()
          .forEach(function(checkin){
        friendCheckin.checkins.push(checkin);
      });
      friendCheckins.push(friendCheckin);
    });
    
    return friendCheckins;
};

Again, the caller must provide an argument (limit)indicating the maximum number of checkins the function should return for each friend. The logic of the function may seem a little inverted as it finds the other users that have declared the current user as one of their friends (and can view their checkins), rather than the list of users the current user has declared to be friends.

Before letting the user add a new email address to their array of friends, it's useful if you check that they aren't already friends. Create a new function, alreadyAFriend:

exports = function(email) {

    var atlas = context.services.get("mongodb-atlas");
    var coll = atlas.db("trackme").collection("users");
  
    var friend = coll.findOne({owner_id: context.user.id, friends: email});
    if (friend) {
      return true;
    } else {
      return false;
    }
};

Once your application has checked that the requested friend isn't already listed, you can call the addFrriend function to add their email address:

exports = function(friend) {

    var atlas = context.services.get("mongodb-atlas");
    var coll = atlas.db("trackme").collection("users");
  
    result = coll.updateOne({owner_id: context.user.id}, 
{"$push": {"friends": friend}}, {"upsert": false});
    return ({success: ((result.matchedCount == 1) ? true : false)});
};

When a user checks in through Swarm/FourSquare or our iOS Workflow app, we identify them by their email address rather than their owner_id; the ownerFromEmail function retrieves the user's owner_id using the email parameter:

exports = function(email){
    var atlas = context.services.get("mongodb-atlas");
    var usersColl = atlas.db("trackme").collection("users");
    var owner_id = usersColl.findOne({"userData.email": email}, {_id:0, owner_id:1});
    return owner_id;
};

As this function is only ever called by other Stitch functions (i.e. not the frontend app code), you can set it to private for extra security.

Stitch must add a new document to the checkins collection – that's performed by the checkin function:

exports = function(checkin){
    var atlas = context.services.get("mongodb-atlas");
    var checkinColl = atlas.db("trackme").collection("checkins");
    try {
      checkinColl.insertOne(checkin);
    } catch (e) {
      console.log("Error inserting checkin doc: " + e);
      return e.message();
    }
};

The checkin function should only be called from the application's Stitch webhooks and so you can set it to private.

In addition to displaying the most recent data, the application also presents a graph of the most frequent checkins – it fetches that data using the popularCheckins function:

exports = function(limit){
    var atlas = context.services.get("mongodb-atlas");
    var checkinColl = atlas.db("trackme").collection("checkins");
    var checkins = checkinColl.aggregate([
      {$match: {owner_id: context.user.id}},
	    {$group: {
		    _id: "$venueName",
		    count: {$sum: 1}
	    }},
	    {$sort: {count: -1}},
	    {$limit: limit},
	    {$project: {
	      venue: "$_id",
	      _id: 0,
	      count: 1
	    }}
    ]).toArray();
    return checkins;
};

Refer to the MongoDB Aggregation Framework documentation if you don't already understand MongoDB aggregation pipelines.

An application user has the option to provide the email addresses of any other users that they want to be able to view their checkin history; this is implemented in the addFriend function:

exports = function(friend) {
    var atlas = context.services.get("mongodb-atlas");
    var coll = atlas.db("trackme").collection("users");
  
    result = coll.updateOne({owner_id: context.user.id}, {"$push": {"friends": friend}}, {"upsert": false});
    return ({success: ((result.matchedCount == 1) ? true : false)});
};

If you recall, the read rule for the checkins collection called the amITheirFriend function, we can now implement that function:

exports = function(user_id){
  // Check whether the email address of the current user is in the 
  //friends list of the user passed in
  var atlas = context.services.get("mongodb-atlas");
  var usersColl = atlas.db("trackme").collection("users");
  var myEmail = usersColl.findOne({
owner_id: context.user.id}).userData.email;
  var myFriend = usersColl.findOne({
owner_id: user_id, friends: myEmail});
  
  if (myFriend) {
    return true;
  } else {
    return false;
  }
};

Finally, the application includes a feature to send a user's latest checkin via a text message. You should create the sendText function to send the message to the Twilio service:

exports = function(destinationNumber){  
  var atlas = context.services.get("mongodb-atlas");
  var checkinColl = atlas.db("trackme").collection("checkins");
  var checkin = checkinColl.find({"owner_id": context.user.id})
.sort({_id: -1}).limit(1).toArray();
  var body = "";
  if (checkin.length > 0) {
    body = "You last checked in at " + checkin[0].venueName 
+ ". " + checkin[0].url;
  } else {
    body = "You have not yet checked in anywhere";
  }
  
  var twilio = context.services.get("myTwilio");
  twilio.send({
    to: destinationNumber,
    from: context.values.get('twilioNumber'),
    body: body
  });
};

Working with other services – the HTTP service and WebHooks

The HTTP service fulfills two roles:

  • Makes outgoing HTTP calls to services (either public web services or your microservices)
  • Accepts incoming HTTP requests (through Stitch WebHooks) – allowing external services to trigger actions within your Stitch application

The TrackMe application uses WebHooks to receive notifications whenever one of our users checks in through Swarm/FourSquare or the iOS Workflow app.

Create a new HTTP service called externalCheckin:

Create externalCheckin HTTP service in MongoDB Stitch

There's no need to define any (outgoing) rules as our application doesn't use this service to send out any requests.

Create the fourSquareCheckin WebHook:

Define fourSquareCheckin WebHook in MongoDB Stitch

To prevent another application sending your application bogus checkins, enable Require Secret As Query Param and provide a secret (I've used 667, but for a production app, you'd want a stronger secret – of course, we could have defined this secret as a Stitch value so that the actual secret didn't need to be included in the webhook definition).

The code associated with the webhook extracts the checkin data (sent from FourSquare); finds the correct user (based on the email address received), and then invokes the checkin function:

var exports = function(payload) {
queryArg = payload.query.arg || '';
  var body = {};
  if (payload.body) {
  body = EJSON.parse(payload.body.text());
  }
  
  var owner_id = context.functions.execute("ownerFromEmail", body.email);
  var checkin = {
    owner_id: owner_id.owner_id,
    email: body.email,
    venueName: body.venue,
    date: body.checkinDate,
    url: body.url,
    locationImg: body.location + "&key=" + context.values.get("GoogleMapsStaticKey")
  };
  
  context.functions.execute("checkin", checkin);
};

Note that we form the locationImg field by adding our GoogleMapsStaticKey value to the end of the received URL (so that the new URL can be used by the frontend application code to retrieve the map image from Google).

Take a note of the WebHook URL (https://stitch.mongodb.com/api/client/v2.0/app/trackme-qhlyz/service/externalCheckin/incoming_webhook/fourSquareCheckin in this example) as this is where other services must send requests.

The appCheckin webhook is almost identical but must work with a slightly different set of received data:

var exports = function(payload) {
  var body = {};
  if (payload.body) {
    console.log("Payload body: " + payload.body.text());
    body = EJSON.parse(payload.body.text());
    console.log("Body: " + body);
  }
  
  var owner_id = context.functions.execute("ownerFromEmail", body.email);
  var checkin = {
    owner_id: owner_id.owner_id,
    email: body.email,
    venueName: body.venue,
    date: body.date,
    url: body.url,
    locationImg: body.location
  };
  
  context.functions.execute("checkin", checkin);
};

Take a note of the WebHook URL (https://stitch.mongodb.com/api/client/v2.0/app/trackme-qhlyz/service/externalCheckin/incoming_webhook/appCheckin in this example) as this is where other services must send requests.

Checking into the app using WebHooks

Capturing FourSquare checkins (via IFTTT)

IFTTT (If This Then That) is a free cloud service which allows you to automate tasks by combining existing services (Google Docs, Facebook, Instagram, Hue lights, Nest thermostats, GitHub, Trello, Dropbox,...). The name of the service comes from the simple pattern used for each Applet (automation rule): "IF This event occurs in service x Then trigger That action in service y".

IFTTT includes a Maker service which can handle web requests (triggers) or send web requests (actions). In this case, we create an Applet to invoke our fourSquareCheckin WebHook whenever you check in using the Swarm (Foursquare) app:

Define IFTTT applet for MongoDB Stitch app

Note that you form the URL: (https://stitch.mongodb.com/api/client/v2.0/app/trackme-qhlyz/service/externalCheckin/incoming_webhook/fourSquareCheckin?secret=667) from the WebHook URL, with the addition of the secret parameter.

The HTTP method is set to POST and the body is a JSON document formed from several variables provided by the FourSquare service:

{
    "email":"me@gmail.com",
    "venue":"{{VenueName}}",
    "checkinDate":"{{CheckinDate}}",
    "url":"{{VenueUrl}}",
    "location":"{{VenueMapImageUrl}}"
}

In this example, the email is hard-coded, and so all checkins will be registered by the same user. A production application would need a better solution.

Checking in from an iPhone (via the Workflow iOS app)

iOS Workflow has some similarities with IFTTT, but there are also some significant differences:

  • Workflow runs on your iOS device rather than in the cloud.
  • You trigger Workflows by performing actions on your iOS device (e.g. pressing a button); external events from cloud service trigger IFTTT actions.
  • Workflow allows much more involved patterns than IFTTT; it can loop, invoke multiple services, perform calculations, access local resources (e.g. camera and location information) on your device, and much more.

Implementing a Workflow involves dragging actions into the work area and then adding attributes to those actions (such as the URL for the TrackMe appCheckin WebHook). The result of one action is automatically used as the input to the next in the workflow. Results can also be stored in variables for use by later actions.

The TrackMe workflow:

  • Retrieve the current location from your device & fetch details venue details
  • If the venue details isn't a URL then fetch an Apple Maps URL
  • Take a new photo and upload it to Imgur
  • Create a URL to invoke Trackme (ending in ?secret=668)
  • Perform an HTTP POST to this URL, including checkin details in the body

This is the Check In workflow:

Create iOS Workflow for MongoDB Stitch app

You can see the Workflow applet in action here:

Trackme MongoDB Stitch iOS Workflow in action

You can check the results of the request in the *Logs * section of the Stitch Admin UI.

Building a frontend app using React

In The Modern Application Stack - Part 5: Using ReactJS, ES6 & JSX to Build a UI (the rise of MERN), I covered developing an application frontend using React. In this post, I don't rehash the introduction to React, instead, I focus on how the React application interacts with its Stitch backend.

If you read my earlier post then you may recall that it included writing a data service to handle interactions with the backend (Mongopop) REST API; this isn't required for the TrackMe frontend as the Stitch SDK provides access to the backend.

The TrackMe application frontend allows a user to:

  • Log in using Google or Facebook authentication
  • View their most recent checkins
  • View the locations that the user has checked into most often
  • View the most recent checkins of users that have added them to their list of friends
  • Add another user to their list of friends
  • Use Twilio to send an SMS text to any number, containing the user's latest checkin information

Download the full application can from the trackme_MongoDB_Stitch GitHub project.

TrackMe ReactJS Web app frontend for MongoDB Stitch

To run the TrackMe frontend:

git clone https://github.com/am-MongoDB/trackme_MongoDB_Stitch.git
cd trackme_MongoDB_Stitch
npm install

Edit the value of appId in src/config.js; replacing trackme-xxxx with the value for your Stitch app (found in the Clients tab in the Stitch console after creating your MongoDB Stitch app).

npm start

ReactJS Javascript (ES6) Client Code

The application's React frontend is made up of the Trackme component which embeds four sub-components:

React components making up the TrackMe web app

Any Stitch JavaScript application must start by importing the Stitch SDK StitchClient. The code then uses StitchClient to connect to MongoDB Stitch in the Trackme component's constructor function within App.js. After instantiating stitchClient, it's used to connect to the trackme database, followed by the checkins, and user collections:

import { StitchClient } from 'mongodb-stitch';
import config from './config';
...
class Trackme extends React.Component {
  constructor(props) {
    super(props);
        ...
    this.appId = config.appId;
        ...
    let options = {};

    this.stitchClient = new StitchClient(this.appId, options);

    this.db = this.stitchClient.service("mongodb",
        "mongodb-atlas").db("trackme");

    this.checkins = this.db.collection("checkins");
    this.users = this.db.collection("users");
    }
...
}

stitchClient is passed down to src/login.component.js, where a single line of code (this.props.stitchClient.authWithOAuth("facebook")) can be used to authenticate using Facebook:

<div
  onClick={() => 
    this.props.stitchClient.authWithOAuth("facebook")}
  className="signin-button">
  <div className="facebook-signin-logo" />
  <span className="signin-button-text">
    Sign in with Facebook
  </span>
</div>

The same component can use Google to authenticate in the same way:

<div
  onClick={() => 
    this.props.stitchClient.authWithOAuth("google")}
  className="signin-button"
>
  ...
  <span className="signin-button-text">
    Sign in with Google
  </span>
</div>

Whichever authentication method was used, common code displays the user's name and avatar (in src/login.component.js):

this.props.stitchClient.userProfile()
.then(
  userData => {
    ...
    this.setState({userData: userData.data});
    ...
  },
  error => {
    // User hasn't authenticated yet
  })
...
{this.state.userData && 
    this.state.userData.picture
  ? <img src={this.state.userData.picture}
    className="profile-pic" alt="mug shot"/>
  : null}
<span className="login-text">
  <span className="username">
    {this.state.userData && this.state.userData.name 
      ? this.state.userData.name
      : "?"}
  </span>
</span>

Common code can logout the user (in src/login.component.js):

this.props.stitchClient.logout()

Once logged in, the application frontend can start making use of the services that we've configured for this app through the Stitch UI. In this case, we directly insert or update the user's details in the trackme.users collection (in src/login.component.js):

this.props.onLoginChange(true);
      this.props.userCollection.updateOne(
        {owner_id: this.props.stitchClient.authedId()},
        { 
          $set: {
            owner_id: 
            this.props.stitchClient.authedId(),
            userData: userData.data
          }
        },
        {upsert: true})
      .then(
        result=>{},
        error=>{console.log("Error: " + error)}
	);

While that code is using the Stitch SDK/API, it is invoking the MongoDB Atlas service in a traditional manner by performing an updateOne operation but the Stitch filters and rules we've configured for the users collection will still be enforced.

In this React application frontend, I have intentionally used a variety of different ways to interact with Stitch – you will later see how to call a Stitch function.

When adding a new friend, two of the functions we created through the Stitch UI (alreadyAFriend & addFriend) are executed to add a new email address to the list if and only if it isn't already there (src/addfriend.component.js):

import 'mongodb-stitch';
...
    this.props.stitchClient
      .executeFunction('alreadyAFriend', email)
      .then(
        response => {
          if (response) {
            this.setState({error: email + 
              " has already been included as a friend."});
          } else
          {
            this.props.stitchClient
              .executeFunction('addFriend', email)
              .then(
                response => {
                  if (response.success) {
                    this.setState({success: 
                      email + 
                      " added as a friend; they can now see your checkins."});
                  } else {
                    this.setState({
                      error: "Failed to add " +
                      email + " as a friend"});
                  }
                },
                error => {
                  this.setState({
                    error: "Error: " + error});
                  console.log({error: "Error: "
                    + error});
                }
              )
          }
        },
        error => {
            this.setState({error: "Error: " + 
              error});
            console.log({error: "Error: " + 
              error});
        }
      )

...

src/text.checkin.component.js invokes our executeFunction Stitch function – sending the venue name to the requested phone number via Twilio:

    this.props.stitchClient.executeFunction(
      "sendText", this.state.textNumber)
    .then (
      response => {
       this.setState({success: 
              "Text has been sent to " + this.state.textNumber});
      },
      error => {
        this.setState({error:
            "Failed to send text: " + error});
        console.log({error: 
            "Failed to send text: " + error});
      }
    );

Note that the pipeline refers to %%values.twilioNumber – this is why that value couldn't be tagged as Private within the Stitch UI.

This is the result:

Text message from Twilio – via MongoDB Stitch

The bar chart showing the most frequent checkin venues (for this user) is created in the Popular component in src/popular.component.js. The following code invokes the popularCheckins Stitch function (including the parameter to request the 10 most recent checkins):

  this.props.stitchClient
    .executeFunction('popularCheckins', 10)
    .then(
      checkinData => {
        this.setState({checkins: checkinData});
        this.createBarChart();
      },
      error => {
        console.log(
          "Failed to fetch most popular checkins: "
          + error)
    })

The checkins for the user and their friends are displayed in the Checkins component in src/checkins.component.js. The following code invokes the recentCheckins function (including the number parameter to request the 10 most recent checkins):

this.props.stitchClient
    .executeFunction('recentCheckins', 10)
    .then(
      checkinData => {
        this.setState({checkins: checkinData
          .map((checkin, index) => 
          <li key={index}>
            <a href={checkin.url} 
              target="_Blank">
            {checkin.venueName}</a> &nbsp;
            ( {checkin.date} )
             <br/>
             <img src={checkin.locationImg} 
              className="mapImg" 
              alt={"map of " + 
                checkin.venueName}/>
          </li>
        )})
      },
      error => {
        console.log(
          "Failed to fetch checkin data: "
          + error)
    })

A similar code block executes the friendsCheckin function and then loops over each of the friends, displaying the latest checkins for each one:

      .executeFunction('friendsCheckins', 5)
      .then(
        friendData => {
          this.setState({friendsCheckins: 
            friendData
            .map((friend, friendIndex) =>
            <li key={friendIndex}>
            <strong>{friend._id}</strong>
            <ul>
            {friend.checkins.map((checkin, checkinIndex) =>
              <li key={checkinIndex}>
                <a href={checkin.url} 
                  target="_Blank">
                  {checkin.venueName}</a>
                  &nbsp;( {checkin.date} )
                  <br/>
                  <img src={
                    checkin.locationImg} 
                    className="mapImg" 
                    alt={"map of " 
                      + checkin.venueName}/>
              </li>
            )}
            </ul>
            </li>
            )
          })
        },
        error => {
          console.log(
            "Failed to fetch friends' data: "
            + error)
      })

Continue accessing your data from existing applications (Amazon Alexa skill)

Not every MongoDB Stitch use-case involves building a greenfield app on top of a brand new data set. It's common that you already have a critical application, storing data in MongoDB, and you want to safely allow new apps or features to use some of that same data.

The good news is that your existing application can continue without any changes, and Stitch can be added to control access from any new applications. To illustrate this, you can reuse the Mongo Alexa Skill created in my earlier post. The JavaScript code needs a sight adjustment (due to a change I made to the schema) – use alexa/index.js.

Architecture for Amazon Alexa reading data from MongoDB Atlas

The Alexa skill uses the Express/Node.js REST API implemented in The Modern Application Stack – Part 3: Building a REST API Using Express.js.

Conclusion

MongoDB Stitch lets you focus on building applications rather than on managing data manipulation code, service integration, or backend infrastructure. Whether you’re just starting up and want a fully managed backend as a service, or you’re part of an enterprise and want to expose existing MongoDB data to new applications, Stitch lets you focus on building the app users want, not on writing boilerplate backend logic.

In this post, you've learned how to:

  • Create a new MongoDB Stitch app that lets you access data stored in MongoDB Atlas
  • Integrate with authentication providers such as Google and Facebook
  • Configure data access controls – ensuring that application end-users can access just the information they're entitled to
  • Enable access to the Twilio service
  • Define constants/values that you can use securely within your application backend, without exposing them to your frontend code, or the outside world
  • Implement functions to access MongoDB and your other services
  • Implement WebHooks that allow external services to trigger events in your application
  • Invoke your new WebHooks from other applications
  • Implement your application frontend in React/JavaScript
    • Authenticate users using Google and Facebook
    • Use the MongoDB Query Language to "directly" access your data through Stitch
    • Execute functions
  • Continue to access the same MongoDB data from existing apps, using the MongoDB drivers

When revisiting the original blog series, it's interesting to assess what work could you could save:

The following table summarizes the steps required to build an application without without the help of Stitch:

Without Stitch With Stitch
Configure MongoDB Cluster Use Atlas Use Atlas
Configure Stitch Services Pipeline, Rules, and WebHooks No Yes
Code user authentication Yes No
Code data access controls Yes No
Code against Twilio, and Slack APIs Yes No
Provision Node.js backend to service static content Yes No
Provision Node.js backend to interact with MongoDB and all other services Yes No
Code REST API Yes No
Code UI in React frontend Yes Yes
Code data access service in frontend (to use REST API) Yes No
Code business logic in frontend Yes Yes
Make REST call from iOS Workflow app, IFTTT, & Alexa Skill Yes Yes

Both MongoDB Atlas and MongoDB Stitch come with a free tier – so go ahead and try it out for yourself.