Serverless Backend with Azure Functions and Cosmos DB

Leon Fausten
Holisticon Consultants
8 min readApr 30, 2021

This is the second part of our way to a completely serverless application in azure. In the previous part, we talked about the motivation and the overall plan for the architecture. In this part we build the backend of the application with Azure Functions and Cosmos DB. The source code can be found on github.

The next part is about the static frontend, hosted in an Azure Blob Storage.

Serverless Azure Cosmos DB with SQL Api

To persist our tasks we use the fully managed noSQL database Cosmos DB as serverless instance. As api we use the Core SQL Api. This api enables us to write SQL like queries even if it is a noSQL database. The SQL Api is required for our usage as direct input of a function, as we will see later.

Create Cosmos DB Account Azure Portal Screenshot with Serverless Consumption Plan selected.
Azure Portal: Create Serverless Cosmos DB Account

At first we have to create a database account, then a database inside and at last a container for the data itself. On the account level we have to define that we want to use the serverless capacity mode. In our case we only need one database with one container inside of it.

One Cosmos DB account can contain several databases. One database can contain several container.
Cosmos DB Account, Database and Container Structure

With the serverless mode we are only billed for the consumed request units (RU) and the storage. We don’t have to provision any throughput upfront.
This is a good solution if we mostly have low traffic but need to handle some peaks.

In the azure documentation you can find further information about the Cosmos DB creation and Cosmos DB itself.

Azure Functions App

In azure, functions are logicly grouped inside a Function App. This app works also as management and deployment unit.

On creation we have to define a Storage Account where the functions will be stored inside a Blob Container.

Azure Portal: Create Function App Basic Settings

We also have to decide which runtime should be used, in our example we use NodeJs. It is not possible to have functions with different runtimes in one app. For this purpose you should create multiple apps.
Then we have to decide which operating system and which service plan to use. The plan is important for scaling and pricing.
We use the Consumption Plan. In this plan we are only billed for the time our functions are running and the storage size. Additionally we also get the automated scaling feature.

On the Consumption plan, instances of the Functions host are dynamically added and removed based on the number of incoming events.

Azure Portal: Create Function App Hosting Settings

As another possible plan you can choose an (existing or new) App Service Plan. You should consider this if you require a better cost prediction or already have an underutilized VM in this plan. This plan is better for long running actions, but misses the automated scaling feature. The same App Service Plan can be reused for different apps.

The service plans are listed as separate resources in azure.

Azure Function App configuration screenshot with the public url.
Overview section with public function app url.

After creation of the function app, we can see the public accessible url in the overview section. The url should show a default page, later the frontend will be available on this url.

Azure Function App default “It works page”.
Default “It Works” page.

Function App Configuration

We use Visual Studio Code and the Azure Functions Extension in combination with the Azure CLI to create and to deploy new functions. Take a look here to prepare your development environment and learn how to run your app locally.

Function app default files.
default function app configuration files without functions

When we create the first function with VS Code it creates the configuration files automatically.
The most important files for this part are the hosts.json and local.settings.json file.
The hosts.json file contains the global settings for all our functions inside the app. Beside a lot of other settings we can configure the logging configuration, the timeouts or the base path for the http trigger functions. Take a look at the documentation page for more information.

hosts.json

Here we added the route prefix api/v1. If we don’t specify any, the default prefixapi is used.

For local development the file local.settings.json is used to override settings and set configuration variables to run the function app locally. This file contains some connection strings for the database, so I will not add it to the committed source code.

In this part we don’t need the proxies.json configuration, we will come back to it in the next part about the static frontend hosting.

Azure Functions

A function has exactly one trigger and an optional number of input and output bindings.

A trigger defines an event when a function should be executed. In our case we only use the http trigger to start the function execution. In this case the http call is the event. A function can not have multiple triggers. Other triggers for example are a new messages in a queue, a timer or a changed files in a blob storage. Here you can find more information about the triggers and bindings.

Add a new Todo Task Function

Finally, let’s talk about our first function to add a new todo task. To add a new task we create a function with a http trigger and Cosmos DB as an output binding.

Add Task Function Integration View with the Trigger, Inputs and Outputs.
Add new Task Function Integration View

The http trigger is used to start the function. The http request is used as input and the response as output binding. The Azure Cosmos DB binding is defined as additional output binding to persist new tasks. These settings are configured inside the function.json file.

function.json

The function integration picture (from the azure portal) and the json are showing the same information.

Every binding has the three following attributes:

  • type → which kind of binding to use (e.g. http, timer, …)
  • direction → is it used as input or output (in or out)
  • name → name of the variable to access or to publish the data

All binding can have more additional specific attributes like the accepted http methods in the http binding or the connection information for the Cosmos DB binding.

In our example we enable the anonymous authentication for our function. Otherwise we have to provide a host or function token. For an easier protection it is still possible to enable the authentication for the entire function app. When we enable this we can use user based authentication with an Azure AD client.

Beside the configuration in the function.json file we need the function code itself. The function code is kept very simple and can be optimized for sure.

index.ts

Interesting to notice here is the access to the bindings. All bindings are accessible via context.bindings.<NAME>. The input bindings can also be added as a function parameter, like in the example above.
To persist a new task we just assign the value to the output binding context.bindings.newTask. No manual inserts or other database related things are required, everything is automatically handled by the output binding.
The http binding in- and outputs are accessible in different ways. We can use the context.bindings.res for the response or the shorter version context.res directly. This was added to support the conventional pattern.

In production code we should take more care on input validation, etc.

Get all uncompleted Tasks Function

To retrieve all uncompleted tasks we create a function with Cosmos DB and a custom sql query as input binding.

Get uncompleted Task Function Integration View

To use Cosmos DB as input binding with a custom sql query we add the following configuration.

function.json

Here we see the direction is defined as input and also a sqlQuery to select only the uncompleted tasks is added. The query is optional, if we remove it, all collection entries are returned. In our case we only want to retrieve the uncompleted tasks, so we have to exclude all entries with a completedDate .

index.ts

The function code is very simple. All open tasks are passed into the function as parameter and returned as http response.

Mark Todo Task as Completed Function

To mark a task as completed we first need to query the task by id, then set the completedDate and finally persist it again. This target will be reached with Cosmos DB as input and as output binding. For the input a parameterized id query is used.

Complete Task Function Integration View

To get the task by the given id we use the id parameter of the binding.

In this block we can see a few new things. The route of the trigger now contains the variable id . We can read its value in the typescript code by accessing context.bindingData.id .
In our case we don’t need the value in the function code itself, instead we use it directly in the Cosmos DB input binding. We use the feature to select the input directly by id.
We could also reach the same result (with a small difference) with the sqlQuery parameter we used in the function before:

SELECT * FROM c WHERE c.id = {id}

In difference to the first solution the query will wrap the result in a list.

The update of a task works like the creation of a new task but with an already existing id. The existing id causes that the existing entry will be overridden by the updated one.

Conclusion

In this part we have seen how we can use the http trigger binding in combination with the Cosmos DB binding. We used it successfully to add, retrieve and to update tasks.

If you want to take a closer look at the implementation and the other functions you can visit the github repository.

In the next part we will talk about the static frontend hosting.

If you don’t want to miss any part of this journey or a lot of other interesting stories, feel free to subscribe to Leon Fausten or Holisticon.

Sources and Further Information

--

--