MongoDB Stitch Serverless Functions

A crash course in MongoDB Cloud’s bread and butter: serverless functions

At times, I've found my opinion of MongoDB Atlas and MongoDB Stitch to waver between two extremes. Sometimes I'm struck by the allure of a cloud which fundamentally disregards schemas (wooo no schema party!). Other times, such as when Mongo decides to upgrade to a new version and you find all your production instances broken, I like the ecosystem a bit less.

My biggest qualm with MongoDB is their poor documentation. The "tutorials" and sample code seems hacked-together, unmaintained, and worst of all, inconsistent with itself. Reading through the docs seems to always end up with Mongo forcing Twilio down my throat my for some miserable reason.

Just to illustrate how bad things can get, below are two totally sets of documentation for what is supposed to be the same product. Mongo's main documentation on the left frequently references the bastardized documentation on the right. What is the documentation on the right? It's a collection of nonsense living on an S3 bucket which lists the methods blackboxed into Stitch, often with zero explanation on how to actually utilize functionality.

Which one is real? And WHY?!

How frustrating is this? I've had email user authentication "working" for weeks as far as Stitch's logs say, although not a single user has actually been registered in that time. Anyways, I digress.

Making a Serverless Function

Stitch Serverless functions are of course strictly Javascript (I believe Mongo abides by ECMA2015 features). In your Stitch console, check out the "functions" link in the left hand nav:

Go ahead and create a new function.

There are just a few things we need to specify when creating a new function:

  • The name of the function (duh).
  • Whether or not the function can be accessed "publicly". A "Private" function is the equivalent of a function that only accessible to the VPC it belongs to (although technically MongoDB Cloud doesn't use this terminology).
  • A condition which needs to be met in order for the function to execute.
Here's a screenshot of everything we just went over. Because whatever.

Switch over to the function editor to start really F*&king Sh!t up.

Mongo's Serverless Function Editor

We can call a Serverless function in a number of ways, with one of those ways being directly from our frontend code. In this case, we're basically just taking a Javascript function which could live in our frontend codebase and moving it to the cloud, thus functions can be passed any number of arguments (just like a normal function).

Luckily for us, Mongo provides some commented out boilerplate code when creating a new function, which gives us an idea of what we might want to use these functions for:

exports = function(arg){
  /*
    Accessing application's values:
    var x = context.values.get("value_name");

    Accessing a mongodb service:
    var collection = context.services.get("mongodb-atlas").db("dbname").collection("coll_name");
    var doc = collection.findOne({owner_id: context.user.id});

    To call other named functions:
    var result = context.functions.execute("function_name", arg1, arg2);

    Try running in the console below.
  */
  return {arg: arg};
};

Pay special attention to context.services here. When using a serverless function to access MongoDB services such as our database or endpoints, we can access these via context.services along with whichever service we're trying to mess with.

Querying our Database Within a Function

Let's grab a single record from a collection in our Atlas collection:

exports = function(arg){
      const mongodb = context.services.get("mongodb-atlas");
      const collection = mongodb.db("blog").collection("authors");
      var result = collection.findOne({"author": arg});
      return result;
};

We use findOne here to return an object, whereas we'd probably use toArray if we'd be expecting multiple results. The query we're running is contained within findOne({"author": arg}). Our function takes an argument and returns a record where the value matches the argument: this makes our functions highly reusable, of course.

Calling Our Function via Our App

As a recap, you have the option of including Stitch in your app either via a link to a script or by installing the appropriate NPM modules. It's preferable to do the latter, but for the sake of this post, my patience with dealing with Javascript's babel browserify webpack gulp yarn npm requires package-lock .env pipify facepunch ecosystem has reached its limit.

Feel free to follow in my footsteps of worst practices by embedding stitch directly:

<script src="https://s3.amazonaws.com/stitch-sdks/js/bundles/4.0.8/stitch.js"></script>

Authenticating Before Calling Functions

Before making queries or interacting with any serverless functions of any kind, we need to authenticate a 'user' with the server; even if that user is an anonymous one (it's in our own best benefit to know which user crashed the server, even if that 'users' is a random string of numbers). Because we allowed anonymous users to peruse through our data, this is easy:

// Authenticates anonymous user
client.auth.loginWithCredential(new stitch.AnonymousCredential()).then(user => {
  console.log('logged in anonymously as user')
});

Calling our Function

Now that that's done, we can call our function immediately after:

// Authenticates anonymous user
client.auth.loginWithCredential(new stitch.AnonymousCredential()).then(user => {
  console.log('logged in anonymously as user')
});

// Calls function
client.callFunction("getUsers", ["{{author}}"]).then(result => {
  console.log(result)
});

Our function is called get users and we're passing a single parameter of {{author}}. Even though one parameter is being passed, we pass parameters as lists as Mongo Serverless functions, as these functions are agnostic to what might be coming their way.

Using Functions to Grab Stored Values

Let's look at one more use case where calling a Stitch Serverless function might come in handy.

Back in the Stitch UI, check out the "values" tab in the left-hand nav. This is a place where we can store constant values which should accessible through our application, or even a place to retrieve secrets:

2secret4u

Values can only be retrieved by functions, and this would be a good time to ensure those particular functions are marked "private" For instance, if you have an API call you need to make, It would be best to create a function that handles the logic of that API call, and within that function, invoke another private function whose job it is simply to retrieve the key in question. Make sense?  Ah well, you'll figure it out.

Making a Serverless Function that Does Something

Anyway, let's apply our knowledge of functions to actually do something. On our site we currently use a third party Medium widget which fetches stories from a user's Medium account. Here's how that would look in its entirety:

<script src="https://s3.amazonaws.com/stitch-sdks/js/bundles/4.0.8/stitch.js"></script>

<script src="https://medium-widget.pixelpoint.io/widget.js"></script>

<script>
function createMediumCard(medium){
  console.log('medium= ' + medium);
  MediumWidget.Init({
    renderTo: '#medium-widget',
    params: {
      "resource": 'https://medium.com/' + medium,
      "postsPerLine": 1,
      "limit": 3,
      "picture": "small",
      "fields": ["description", "publishAt"],
      "ratio": "square"
    }
  })
  $('#medium').css('display', 'block');
}
    
client.auth.loginWithCredential(new stitch.AnonymousCredential()).then(user => {
  console.log('logged in anonymously as user')
});
    
client.callFunction("getUsers", ["{{author}}"]).then(result => {
  console.log(result)
});
</script>

Normally, "resource": medium, would actually read the URL of the Medium profile we're trying to embed. However, when you blog on a platform like Ghost which only allows your authors to have either Facebook or Twitter profiles, we need to essentially go out of our way to build a second, nonintrusive database to pull data from to add functionality like this. Yeah - I'll have to show you what MY "stack" looks like for a single blog theme some day. It's ridiculous.

Anyway, that’s all I’ve got for now. I hope these ramblings help you assess MongoDB Cloud for yourself. No matter the provider, Enterprise Clouds target fat budgets and are designed to rake in big money. It almost makes you wonder why somebody would pay out of pocket for three of them just to write a stupid blog.

Author image
New York City Website
Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.