A few days ago I started working with Azure Functions and Cosmos DB from scratch, since natively these types of services work very well to integrate small query services without the need to implement much code. In this tutorial we will show you how to create CRUD operations using only Azure Functions and Cosmos DB to store and query the information.
Create a new database on Azure Cosmos DB
The first thing we need is to create a new Cosmos DB service. Cosmos DB is a service for the administration of non-SQL databases for the development of applications. It offers support for some of the most popular APIs like SQL, MongoDB and Cassandra.
Service creation
To provision a new database we need to do the following:
- In the Azure main page let's a new resource.
- We search for Azure Cosmos DB and click create.
In Networking we mark the option Connectivity method as All Networks
In Encryption we mark the option Data Encryption as Service-managed key so Azure can handle the keys to connect to the service
We finish by clicking on Review + Create
Configure database and insert some data
Once we have the service activated we need to create the database, the collection, and insert some items. We are going to do the following:
- Click on + Add Container in the Cosmos DB instance we created.
- Set a database name. We can unselect the Provision database throughtput option, we don't need it for this tutorial.
- Set a container name and one partition key. The partition key is a logic form to store information in Cosmos DB. You can find more information in the Microsoft documentation.
-
In my case I used the following names:
- Database id: dbtodos
- Container id: items
- Partition key: /all
Go to the Data Explorer section on the left menum to insert a new item
Click on New Item and we add two properties, title and completed. If we don't write an id property it's going to be generated automatically when we save the item.
Azure Functions creation
We are going to start creating our functions. We are going to create different Azure Functions for each of the operations that we have to implement. Every function is going to be configured with the bindings referencing our Cosmos DB instance, and we're going to see that for the operation of removing an element from our database we'll be using the @azure/cosmos module.
Function app creation
- Search for Function App and click on create.
- Configure the services as follows:
- Function App name: function-crud-cosmosdb
- Publish: Code
- Runtine stack: Node.js
- Version: 12
- Region: Central US
- For Hosting section:
- Operating System: Windows
- Plan type: Consumption (Serverless)
- Click on Review + Create
Application Settings configuration
Before starting, we have to configure some environment variables that will be used for communication between Azure Functions and Cosmos DB, this allows us to make requests to our database, This steps needs to be done only once for all out Azure functions.
- Open your Cosmos DB service and click Keys on the left side menu.
Copy the string from URI, PRIMARY KEY and PRIMARY CONNECTION STRING. We are going to need these values later.
In the Function app, go to Application settings
Click on + New application setting to create a new setting.
Set a name and paste the PRIMARY CONNECTION STRING. In my case the setting is called cdb-vue-todos_DOCUMENTDB
Create another configuration and paste the URI string. (COSMOS_API_URL) and another one for the PRIMARY KEY (COSMOS_API_KEY) string.
Get items
- Once the function is created, click on Functions and click on + Add.
- Choose HTTP trigger so the function activates on every HTTP request.
- Give a name to your function, mine is called getTodos. Authorization level is Anonymous.
- Once the function is created go to Code + Test to configure the bindings and code.
- On the editor select the function.json file and paste the following code:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "inputDocument",
"databaseName": "dbtodos",
"collectionName": "items",
"connectionStringSetting": "cdb-vue-todos_DOCUMENTDB",
"partitionKey": "/all",
"direction": "in"
}
],
"disabled": false
}
function.json is the file where we configure the bindings for our function. It's an array of objects where every object is a binding. The last object is the binding for our Cosmos DB database. It has configured with a cosmosDB
type, and a variable associated to use in our code called inputDocument
. The properties databaseName, collectionName, connectionStringSetting and partitionKey must have your own values.
With the parameter direction we can say if it's an input in
or an output out
. For this function an input binding means we can query our database. Given that we aren't specifying any additional query the function will return all objects.
In index.js we implement our code to manage the function. The binding has the inputDocument
variable, that is where the results of our query are stored. We can show the items as a response.
module.exports = async function (context, req) {
context.res = {
// status: 200, /* Defaults to 200 */
body: context.bindings.inputDocument
};
};
If we test the URL to see the results we'll get the following:
Insert a new item
The next function has the same principle than the previous one, with only one change:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "outputDocument",
"databaseName": "dbtodos",
"collectionName": "items",
"createIfNotExists": false,
"connectionStringSetting": "cdb-vue-todos_DOCUMENTDB",
"partitionKey": "/all",
"direction": "out"
}
],
"disabled": false
}
module.exports = async function (context, req) {
const title = req.body.title;
const completed = req.body.completed;
if (title && completed != null) {
context.bindings.outputDocument = req.body;
context.res = {
body: {'result': 'success'}
};
}else{
context.res = {
status: 400,
body: {'result': 'error'}
};
}
};
This function works under POST requests. It expects two parameters in the body request to insert a new item in the database. If we don't set a title
and completed
parameter the function returns an error as a response, otherwise we use the variable outputDocument
to assign the req.body
object that has the values we want to insert.
Update an item
Repeat the steps 1 to 4 to create the updateTodo function. This is the binding:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "inputDocument",
"databaseName": "dbtodos",
"collectionName": "items",
"connectionStringSetting": "cdb-vue-todos_DOCUMENTDB",
"partitionKey": "/all",
"direction": "in",
"sqlQuery": "select * from c where c.id = {id}"
},
{
"type": "cosmosDB",
"name": "outputDocument",
"databaseName": "dbtodos",
"collectionName": "items",
"createIfNotExists": false,
"connectionStringSetting": "cdb-vue-todos_DOCUMENTDB",
"partitionKey": "/all",
"direction": "out"
}
],
"disabled": false
}
The Javascript file is as follows:
module.exports = async function (context, req, todo) {
const title = req.body.title;
const completed = req.body.completed;
context.bindings.outputDocument = todo[0];
context.bindings.outputDocument.title = title
context.bindings.outputDocument.completed = completed
context.res = {
body: {'result': 'success'}
};
};
For this function we have an input and output binding. We use the input binding with a sql query to get the specific item to update, and then the output binding to change the values.
Notice that the input binding has a sqlQuery parameter where we can explicitly a SQL query to get an item based on the id "sqlQuery": "select * from c where c.id = {id}"
. There's a placeholder for id. When the function detects that there's an id in the http request this is going to be replaced on the placeholder.
The output binding is used to assign the item we got from the query. The result can be context.bindings.inputDocument
or an additional parameter in our function, in this case the variable todo
Remove an item
For this function we need to do an additional implementation. Because we can't use the bindings to remove items from our database we need to use the @azure/cosmos module.
Go to Debug console > CMD and then in the diles list to site > wwwroot
Install the module using npm
npm install @azure/cosmos
Close the window and go back to create your deleteTodo function.
We configure only the index.js file.
const cosmos = require('@azure/cosmos');
const endpoint = process.env.COSMOS_API_URL;
const key = process.env.COSMOS_API_KEY;
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, key });
const container = client.database("dbtodos").container("items");
module.exports = async function (context, req) {
const id = req.query.id;
let res;
try{
res = await container.item(id).delete();
context.res = {
body: {'result': 'success'}
};
}catch(err){
context.res = {
status: 400,
body: {'result': 'error'}
};
}
This is how the code works:
- Create variables to import the Cosmos DB module and make reference to the application settings (COSMOS_API_URL y COSMOS_API_KEY).
- Create a new instance of
CosmosClient
and set the key and endpoint. - Get the reference to the container and item.
- Finally, execute the delete() method t finish the procedure.
Summary
Using Azure Functions with Cosmos DB allow us to execute queries and operations with minimal code and effort. This is just an introduction to understand how the bindings work and how easy is to connect to your database using a serverless service.
Top comments (2)
Thanks for the article! However many people use C# for this stuff. Can you point us to the exact same code, but written in C# Script? Thanks!
Great job man!