DEV Community

Cover image for Explore cosmos with Serverless
Chris Noring for Microsoft Azure

Posted on • Updated on

Explore cosmos with Serverless

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

You thought this was about space didn't you? Yea my bad :). But now that I have your attention let's talk about Serverless and how we can use that with Azure Cosmos DB. It's so easy it's out of this world ;)

What we are going to do is show:

  • Serverless and how to create a function in the Cloud
  • Create a Azure Cosmos DB database
  • Show how can read and write data to your Azure Cosmos DB

 Serverless - look Ma no servers

Serverless is the new black, the thing that everybody talks about and for good reason. Gone are the days when you had that server room with no oxygen and full of cables and lord knows what else.

Now you can just focus on the code and rest assured that your code lives in someone else server room ;), that is the Cloud. The point is, it's not a thing you need to care about anymore.

Serverless is, of course, a bit more than just no servers, it's fully managed, there is not even a web server to configure. I've covered the whys and what offerings in this article, so if you are interested, go and have a read.

We will focus on a specific offering namely Azure Functions.
The reason it's chosen for this specific article is:

  • Easy to get started with , comes with great extensions for all major IDEs including VS Code that lets you scaffold functions, debug and more.
  • Creates a connection to your Azure Cosmos DB database so you can just focus on changing the data and not having to bother with instantiating connections

Resources

 Steps

Let's look at this from a mile-high view and see what we need to do, to accomplish what we want. We need to:

  1. Scaffold a Azure Cosmos DB database in Azure
  2. Create a Serverless app and create an Azure Function for each call. We show how you can read data, create it and update it
  3. Configure our functions, to connect to our database, so working with the database is made real easy

 Create a Azure Cosmos DB database

Ok. There are two ways to do this. We can either:

  • Create it through the Azure CLI
  • Use the portal and create the database

Let's show how to do it in the Azure-CLI at first and then switch to the terminal gradually. Let's face it, it's great to feel like a hacker but sometimes you want to see what you are doing. You also want to make sure it's all there and some things are more practical to do in a UI, like data entry.

Using Azure CLI

To create the database we need to take the following steps:

  1. Resource group, Create a resource group for our database or use an existing group
  2. Azure Cosmos DB, Create a Azure Cosmos DB account
  3. Database, Create the database
  4. Add collections and data

Create the resource group

az group create \
    --name mycosmosgroup \
    --location "West Europe"

The above says "provisioningState": "Succeeded", which means we succeeded in creating our resource group.

Create the Azure Cosmos DB Account
Next step is about creating our own account. The account name should be lowercase.

az cosmosdb create \
    --resource-group mycosmosgroup \
    --name cosmosaccount-chris-sql \
    --kind GlobalDocumentDB \
    --locations "North Europe"=0 "West Europe"=1 \
    --default-consistency-level "Session" \
    --enable-multiple-write-locations true

This might take a little while. All databases take a little time to scaffold.

If you are impatient you can go visit it in the portal and it should look something like this:

Last step is to create the database.

Creating the database

Ok, we have a database account but we don't have a database, yet. Now, to create a database we need to create something called a container. Let's head to the portal and find our database account. Now click the tab Data Explorer. It should now look something like:

By hitting the indicated button we get a dialog opened that's asking us to create a new container, like so:

Fill in values for the highlighted parts. You will notice that the Partition key will prepend the value with a / so id, becomes /id.

I know what you are thinking, what the heck is a partition key? Let's see what the info icon tells us:

Partitions are a chapter all to itself. If you are really interested in knowing more have a look at this article
For now, we are happy knowing it's a column we point out in our table.

Ok, so we created our container and we should have something looking like this:

Now what? Now we fill it with data and we do that by clicking the New Item button. It should present you with an edit view like so:

So change the item to say:

{
  "id": "1",
  "name": "tomato"
}

Now hit Save and repeat the procedure and create another item with the value { "id": "2", "name": "lettuce" }.

We want to show this data in a Serverless function right? Right?

Yea I thought so :)

So Serverless is next.

Serverless - Reading and Writing to/from our database

Ok then, Serverless part it is. We need to support interacting with our Azure Cosmos DB database

We will build the following:

  1. An Azure Function App, this will hold all of our functions
  2. Three different Azure Functions, each supporting an HTTP verb
  3. in/out bindings to Azure Cosmos DB, this is configuration we write that creates a database connection for us

 Create an Azure Function App

To be able to use Azure Functions they need to exist inside of an Azure Function app. We will create it using VS Code so we need to make sure we have the correct extension installed.

So look for Azure Functions in the extension area. It should look like this:

Next step is to scaffold our app.

We do that by opening up the command palette. That is done by either selecting View > Command Palette in the menu or you can hit CMD + SHIFT + P, if you are on a Mac.

Select the current directory, JavaScript as language and HTTP trigger. The last will ensure that your project contains at least one function. Name the function ProductsGet and give it Anonymous as authorization level.

Create remaining functions

For this step, we need to bring up our command palette again. CMD + SHIFT + P. Select Azure Functions: Create Function and select HttpTrigger. Name the function ProductsCreate.

Now repeat this process and create another function called ProductsUpdate

We should now configure the allowed HTTP methods for each function. Go into each function directory and open up function.json. There you will find bindings and specific config looking like this:

"methods": [
  "get",
  "post"
]

Ensure you configure it in the following way:

  • ProductsGet, should only support get
  • ProductsCreate, should only support post
  • ProductsUpdate, should only support put

 Configure functions with Azure Cosmos DB

Next up we need to revisit the function.json for each function. This time we need to add a binding that lets us talk to our Azure Cosmos DB database.

Essentially we need to create something called a binding that not only holds a connection to our Azure Cosmos DB database but also easily lets us do a CRUD on a specific collection.

The configuration looks pretty much like this:

{
  "name": "productDocument",
  "type": "cosmosDB",
  "databaseName": "ToDoList",
  "collectionName": "Items",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDB",
  "direction": "in"
}

Depending on what we are trying to do we either need a binding of "direction": "in" or for it to be binding of type out, in which case it would look like this "direction": "out".

If we only want to read data we need this to say "direction": "in". Creating we need it to say "direction": "out". Updating is a different matter, in fact, we need two sets of bindings, one that retrieves our record and one to create/replace our record, more on that later.

So what are we saying here? We are saying that we have a databaseName called TodoList, change that to whatever you called your database. We also have a collectionName called Items which you could see in the Azure portal was the case. We also define it as direction having the value in. This means that this will set up the connection for us and whatever object we are given will contain the data we need.
There is one more thing here connectionStringSetting pointing to the value CosmosDB, now this is an AppSetting we set up in our Azure Functions app project. We haven't deployed it to Azure yet so we need to store this somewhere, for now, that somewhere is in the file local.settings.json. It should look like this currently:

  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "CosmosDB": "[connection string to our database]"
  }
}

You need to go to the portal and our created database, click the tab menu Keys and copy the value in the field Primary Connection String and then set it to the value of above key CosmosDB.

Additional configuration

We are not quite done there I'm afraid. There are two more things we need to do:

  1. Dependencies, set up dependencies to be installed
  2. Storage account, set up a storage account

The first bit we solve by opening up the file host.json and ensure it has the following content:

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

The key and value for extensionBundle instruct Azure that it should be installing all needed libraries so we can talk to queues, databases and pretty much all cool things we can integrate our Azure Function App with as input and output bindings.

The second and last thing we need to do is to create a storage account. We can easily do so in the portal by clicking Add Resource, type Storage Account and follow the instructions. Then we need to obtain the connection string and we need to add an entry to the file local.settings.json, like so:

  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "[storage account connection string]",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "CosmosDB": "[database connection string]"
  }
}

 Write some code

At this point, we want to write some code in our ProductsGet functions. So we need to edit ProductsGet/index.js to say the following:

module.exports = function (context, req) {
  for(var i =0; i< context.bindings.productDocument.length; i++) {
      let product = context.bindings.productDocument[i];
      context.log('id', product.id); 
      context.log('name', product.name);
  }

  context.res = {
      // status: 200, /* Defaults to 200 */
      body: "Ran ProductsGet"
  };
};

Looking at code above we see that context.binding.productDoucment is containing a list of our Items. We iterate that list in the code and print out id and name. Because of configuration like this in function.json:

{
  "name": "productDocument",
  "type": "cosmosDB",
  "databaseName": "ToDoList",
  "collectionName": "Items",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDB",
  "direction": "in"
}

we establish a connection to our Azure Cosmos DB database and a specific collection Items and ensure to create a handle productDocument. Imagine having to write the connect to database code yourself, not fun right?

Deploy and test it out

Ok, next step is to deploy it. To deploy it we need to do two things:

  1. Deploy the Azure Function app, this is as simple as clicking a button in VS Code
  2. Deploy the local app settings to Azure, this is needed to ensure our AppSettings in Azure correctly points out our Azure Cosmos DB database but also our storage account.

Let's start with the deploy part. Ensure you have the following extension installed in VS Code:

This will enable you to easily deploy anything to Azure. We started this article talking about another extension namely Azure Functions. Ensure you have them both installed and life will be a lot simpler.

Now as for the deployment part. Start by clicking the highlighted Azure icon below

Then we come here:

Indicated at the top right is an icon looking like a flash. Clicking that will deploy the app to Azure.

Afterward, it will become part of the list below the flash icon. It's also where we would go to redeploy changes.

The last step is to ensure we upload all the local app settings to Azure. To do that we need to talk our deployed Azure Function App and right-click it and choose to upload them. It should look like so:

At this point, the app should actually work. So lets head to the Azure Portal and try it out:

Just click on the correct function ProductsGet then click Run. This will produce a terminal window below that will print the following:

As you can see from the above image it is reading from the Azure Cosmos DB database.

Awesome right, we have a working connection. Now to talk the remaining operations.

 Adding remaining operations

So far we have been reading so how to support these additional operations? Well, we need to configure each function.json and give it the correct type of configuration and we, of course, need to go into each index.js and add the necessary code to either Create or Update.

 Supporting creation

So far we have been supporting how to read data from our database. We accomplished that by creating a binding that had a direction value of in and we also needed to point out our database and what collection to read from.

Creating something is about creating a binding with direction having the value out. This gives us a handle that we can add data to.

Let's start with the configuration part. In the file ProductsCreate/function.json we add the following entry to our bindings array:

 {
  "name": "productDocument",
  "type": "cosmosDB",
  "databaseName": "ToDoList",
  "collectionName": "Items",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDB",
  "direction": "out"
}

Ok that sets us up nicely so we can focus on the second step, adding code to ProductsCreate/index.js:

module.exports = function (context, req) {
     const { id, name } = req.body;

    // creating doc
    context.bindings.productDocument = JSON.stringify({
        id: id,
        name: name
    });

    // saving doc
    context.done();
};

Note how the configuration value for they key name in function.json matches context.bindings.productDocument. What we do then is to assign an object to productDocument:

context.bindings.productDocument = JSON.stringify({
  id: id,
  name: name
});

Above we are creating the record we want to be inserted. To actually do the save we end it all with the command context.done()

We don't believe this until we tried it right? The first thing we need to do is to redeploy our project to Azure. Because we deployed it once already, it's super simple to redeploy. Click the Azure extension on the left panel then right-click your Azure Functions app project and select Deploy to Function App like below:

This will redeploy it all. Wait for it to finish.

Done? good let's continue

For this one, let's head to the Azure Portal and test it (you can use cURL or any client that supports sending REST calls).

In the portal we want to select our function and click the Test tab on the right:

Now enter a request like so:

Next step is to click the Run button. We should be getting a result like the below

To really verify that this worked, head to the resource page for your database and see if you have a third record:

There it is ! :)

That's it for creation, let's talk about updating next.

 Supporting update

Updating a record is a different matter. We need to support two bindings. Why is that you ask?

Well, we need a binding of type in so we can retrieve the record we need.

Then we need a second out binding to actually replace the record.

Hmm ok, show me

Ok then, bindings first, they should look like this:

{
  "name": "productDocument",
  "type": "cosmosDB",
  "databaseName": "ToDoList",
  "collectionName": "Items",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDB",
  "direction": "out"
},
{
  "name": "productDocumentsIn",
  "type": "cosmosDB",
  "databaseName": "ToDoList",
  "collectionName": "Items",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDB",
  "direction": "in"
}

now to the code:

module.exports = function (context, req) {
    const {
        id,
        name
    } = req.body;

    // or you could limit the in to only be one doc
    const foundDoc = context.bindings.productDocumentsIn.find(p => p.id == id);

    // do the update
    context.bindings.productDocument = foundDoc;
    context.bindings.productDocument.name = name;

    context.done();

    context.res = {
        // status: 200, /* Defaults to 200 */
        body: "Record updated"
    };
};

What we can see is how we first retrieve the record from our input binding productDocumentsIn and assign it to the variable foundDoc. Next thing we do is assign foundDoc to our out handle found on context.bindings.productDocument. We do this to ensure we don't lose any values on this record should or values on the body only represent a partial update. Next thing we do is take the name that came with our body and apply that, like so context.bindings.productDocument.name = name and of course we finish that off with a context.done().

That's it, that is how we support updating.

 Summary

We started off by creating a Azure Cosmos DB database. Then we put some data in there. Next, we created some serverless functions. Thereafter we learned about bindings and how those could save us time when it came to creating a connection towards our database. Thanks to the bindings we ended up writing quite little code to support reading and writing.

How about you try this too? :)

Top comments (6)

Collapse
 
macdonst profile image
Simon MacDonald

Just wanted to say thanks for this blog post. Somehow I had missed the ability to bind my function code to my CosmosDB instance. I've been able to make my backend code much smaller now and it will greatly speed up my frontend coding.

Collapse
 
softchris profile image
Chris Noring

Glad to hear that Simon :)

Collapse
 
theodesp profile image
Theofanis Despoudis

Amazing.

Collapse
 
renanlbl profile image
renanlbl

wowww, that is amazing

Collapse
 
proyaz profile image
Yasar

This article is what I was looking for. Thank you. But, how do we delete an item/product from the DB ?

Collapse
 
softchris profile image
Chris Noring

We need to use the Cosmos client for that for now. github.com/simonaco/serverless-cos...