Menu Close

How to Build a serverless CRUD app with Azure function and Cosmos DB

In this article, we will learn How to Build a serverless CRUD app with Azure function and Cosmos DB. We will walk you through the entire process of creating a restful CRUD API hosted on Azure cloud infrastructure. We will also leverage serverless technologies, specifically Azure functions and Cosmos in serverless capacity mode. Azure Functions is a serverless service that allows you to offer APIs to do certain activities. Today we will look at an example of retrieving data from an Azure SQL using the Azure function API. With a few small adjustments, you can utilize the API to do almost every CRUD activity on the database. We’ll use Postman to launch our API. Please read my previous article on How to create Azure Functions using Azure Portal.

Please find the source code on this link.

What do we cover in this article?

Here we are going to create a Product information CRUD using the below tools.

  • Create the Azure Function CRUD locally and publish it to the Azure Portal
  • Azure Cosmos DB to handle the CRUD Operation
  • End-to-end testing with Postman

What is Azure Functions?

Azure function apps are a serverless compute service offered by Microsoft Azure that allows developers to group multiple Azure functions as a logical unit for easier management, deployment, scaling, and resource sharing. Function apps provide an efficient and scalable way to develop cloud-based serverless applications without worrying about the underlying infrastructure.

Azure Functions enables you to run short chunks of code (referred to as “functions”) without having to worry about application infrastructure. The cloud infrastructure delivers all the up-to-date servers you need to keep your application functioning at scale using Azure Functions.

Azure functions

Azure functions are event-driven, which means that you can run your function code when an event is triggered from either an existing on-premise service, any other Azure service, or a third-party service. The Azure function can be scaled, and you need to pay only for the resources you consume. Azure functions support many trigger points, such as HTTP triggers, queue triggers, etc.

Creating Azure Functions Using Visual Studio

Create_Azure_Function_VisualStudio

Create_Azure_Function_VisualStudio-nextstep
Create_Azure_Function_HTTP-Trigger

Creating the Azure Cosmos DB and Get the ConnectionString

Let’s create an Azure Cosmos DB to connect our Azure function. We have created the NoSQL DB name “ProductDB” using the following steps. You can go through the below images and learn how we can set up the cosmos db.

Azure_Cosmos_db_create
  • Here the cosmos db account name we set as “demo-cosmos-db-azure-function”
Azure_Cosmos_db_account_for_NoSQL
Azure_Cosmos_db_create_new_container
  • We have set as database ID named as “ProductDB”
  • The container id is “Items”
  • Partition Key as /category. The product category may be Laptop, Mobile, Television etc..

Here below, we have created the containers, the below two images are one step to create the container.

Azure_Cosmos_db_create_new_containercreation-2
  • Database id: This is the database name assigned to a Cosmos DB database.
  • Database throughput: This determines the capacity for handling read and write operations on the database (container). You can set it to automatically scale or to manual scaling.
  • Database Max RU/s: RU/s stands for Request Units per Second. Request Units (RUs) are a way to quantify the amount of resources consumed by database operations, including reads, writes, and queries. Request units are the currency for database operations in Cosmos DB. An RU is the cost of the combined system resources needed to read 1 KB item.
  • Container id: This is the name assigned to a collection in a Cosmos DB database.
  • Indexing: You can set this property to Cosmos DB automatically indexes every property for all items in your container or you can choose to not create any indexes for the properties.
  • Partition key: This is the partition key in a container, that is used to distribute and organize data across multiple logical partitions.

Get the Connection String of Azure Cosmos DB

As per highlight, you can get the connection string here.

Azure_Cosmos_db_connection_string

Implementing the Azure Function CRUD with Cosmos DB

Adding the below packages to connect with Cosmos DB and run the Azure Function

Install-Package Microsoft.Azure.Cosmos
Install-Package Microsoft.Azure.Functions.Extensions
Install-Package Microsoft.Azure.Functions.Worker
Install-Package Microsoft.Azure.Functions.Worker.Extensions.Http
Install-Package Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore
Install-Package Microsoft.Azure.Functions.Worker.Sdk
Install-Package Newtonsoft.Json

Creating the Product Models

We have created the product the model class that holds the Id as the primary key and the category as the portion key of the table.

public class Product
{
    [JsonProperty("id")]
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public string Name { get; set; }
    public int? Quantity { get; set; }
    public decimal? Price { get; set; }
    [JsonProperty("category")]
    public string Category { get; set; }
    public string Description { get; set; }
    public DateTime Created { get; set; } = DateTime.Now;
    public bool Updated { get; set; }
}

Adding the connection string details on local.settings.json

Added the connection string like below, copied the end-point details from cosmos db, and add here. For security purposes, I added a * mark for the Account key.

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "Logging__LogLevel__Default": "Information",
    "CosmosDBConnectionString": "AccountEndpoint=https://demo-cosmos-db-azure-function.documents.azure.com:443/;AccountKey=**********************;"
  }
}

Register the Dependency Injection to read the connection string in the Program.cs

using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

// Register the CosmosClient with the dependency injection container
builder.Services.AddSingleton<CosmosClient>(sp =>
{
    var cosmosDbConnectionString = Environment.GetEnvironmentVariable("CosmosDbConnectionString");
    return new CosmosClient(cosmosDbConnectionString);
});

// Register other services if needed
// Register logging services
builder.Services.AddLogging();
// Build the application
builder.Build().Run();
  • Line #9-13: Here we register the dependency injection.
  • Line #11: Environment.GetEnvironmentVariable("CosmosDbConnectionString") read the connection string that added to the local.settings.json.

Creating the Azure Function class to implement the CRUD

Creating the Product End-Point

 public class ProductEndPoint
 {
     private readonly ILogger<ProductEndPoint> _logger;
     private readonly CosmosClient _cosmosClient;
     private Container documentContainer;
     public ProductEndPoint(ILogger<ProductEndPoint> logger, CosmosClient cosmosClient)
     {
         _logger = logger;
         _cosmosClient = cosmosClient;
         documentContainer = _cosmosClient.GetContainer("ProductDB", "Items");
     }
     [Function("CreateProduct")]
     public async Task<IActionResult> CreateProductItem(
         [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "product")] HttpRequestData req)
     {
         if (req == null)
         {
             _logger.LogError("HttpRequest is null");
             return new BadRequestResult();
         }
         _logger.LogInformation("Creating Product Item");
         string requestData = await new StreamReader(req.Body).ReadToEndAsync();
         var data = JsonConvert.DeserializeObject<Product>(requestData);

         var item = new Product
         {
             Name = data.Name,
             Category = data.Category,
             Quantity = data.Quantity,
             Price = data.Price,
             Description = data.Description
         };

         await documentContainer.CreateItemAsync(item, new Microsoft.Azure.Cosmos.PartitionKey(item.Category));

         return new OkObjectResult(item);
     }
 }
  • We created the End-Point named as ProductEndPoint and register the CosmosClient in constructor dependency along with documentContainer.
  • Line #10: _cosmosClient.GetContainer("ProductDB", "Items") It read the ProductDB with NoSQL DB.
  • Line #12-37: Function CreateProduct responsible for creating the product information.
  • The [Function("CreateProduct")] attribute declares this method as an Azure Function named CreateProduct.This is required for Azure Functions in isolated mode (Microsoft.Azure.Functions.Worker)
  • The [HttpTrigger] attribute binds the function to an HTTP trigger.AuthorizationLevel.Anonymous allows anyone to access the function without authentication. The HTTP method is "post", so the function will only respond to POST requests. The Route = "product" sets the URL path for the function, e.g., http://<base-url>/api/product
  • Checks if the HttpRequestData (req) object is null. If it is null, logs an error and returns a 400 Bad Request response.

Here on below, we have requested the create product.

Azure_Function_Create_Product
Azure_Function_Create_Product_cosmosDB

Read Update Delete operation Azure Function

public class ProductEndPoint
{
    private readonly ILogger<ProductEndPoint> _logger;
    private readonly CosmosClient _cosmosClient;
    private Container documentContainer;
    public ProductEndPoint(ILogger<ProductEndPoint> logger, CosmosClient cosmosClient)
    {
        _logger = logger;
        _cosmosClient = cosmosClient;
        documentContainer = _cosmosClient.GetContainer("ProductDB", "Items");
    }
    [Function("GetAllProducts")]
    public async Task<IActionResult> GetShoppingCartItems(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "product")] HttpRequestData req)
    {
        _logger.LogInformation("Getting All Product Items");

        var items = documentContainer.GetItemQueryIterator<Product>();
        return new OkObjectResult((await items.ReadNextAsync()).ToList());
    }

    [Function("GetProductItemById")]
    public async Task<IActionResult> GetShoppingCartItemById(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "product/{id}/{category}")]

        HttpRequest req,string id, string category)
    {
        _logger.LogInformation($"Getting Product Item with ID: {id}");

        try
        {
            var item = await documentContainer.ReadItemAsync<Product>(id, new Microsoft.Azure.Cosmos.PartitionKey(category));
            return new OkObjectResult(item.Resource);
        }
        catch (CosmosException e) when (e.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            return new NotFoundResult();
        }
    }

    [Function("UpdateProduct")]
    public async Task<IActionResult> PutProductItem(
        [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "product/{id}/{category}")] HttpRequestData req,
        string id, string category)
    {
        _logger.LogInformation($"Updating Product Item with ID: {id}");

        try
        {
            // Read request body
            string requestData = await new StreamReader(req.Body).ReadToEndAsync();
            var data = JsonConvert.DeserializeObject<Product>(requestData);
            var partitionKey = new Microsoft.Azure.Cosmos.PartitionKey(category);

            // Fetch the existing item from Cosmos DB
            var itemResponse = await documentContainer.ReadItemAsync<Product>(id, partitionKey);

            if (itemResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return new NotFoundResult();
            }

            // Update fields in the existing item
            var existingItem = itemResponse.Resource;
            existingItem.Updated = data.Updated;

            existingItem.Name = data.Name ?? existingItem.Name;
            existingItem.Price = data.Price ?? existingItem.Price;
            existingItem.Description = data.Description ?? existingItem.Description;

            // Upsert the updated product
            var upsertResponse = await documentContainer.UpsertItemAsync(existingItem, partitionKey);


            // Explicitly fetch the updated item to ensure latest data
            var updatedItemResponse = await documentContainer.ReadItemAsync<Product>(id, new Microsoft.Azure.Cosmos.PartitionKey(category));

            // Return the updated item
            return new OkObjectResult(updatedItemResponse.Resource);
        }
        catch (Microsoft.Azure.Cosmos.CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            _logger.LogWarning($"Product with ID: {id} not found.");
            return new NotFoundResult();
        }
        catch (Exception ex)
        {
            _logger.LogError($"Error updating product item: {ex.Message}");
            return new StatusCodeResult(StatusCodes.Status500InternalServerError);
        }
    }

    [Function("DeleteProduct")]
    public async Task<IActionResult> DeleteProductItem(
        [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "product/{id}/{category}")] HttpRequestData req, 
        string id, string category)
    {
        _logger.LogInformation($"Deleting Product Item with ID: {id}");

        await documentContainer.DeleteItemAsync<Product>(id, new Microsoft.Azure.Cosmos.PartitionKey(category));
        return new OkResult();
    }
}
  • Here we have created the Read, Update, and Delete operation.
  • product/{id}/{category} is used to get the id as the primary key and the category as the partition key.

Test with Postman

Run the Azure function and you can see the below functions we can test using Postman.

Azure_Function
Azure_Function_Update_Product_cosmosDB

Azure_Function_GetAll_Product_cosmosDB

Azure_Function_GetById_Product_cosmosDB

Azure_Function_DeleteById_Product_cosmosDB

Publishing the Azure function using Visual Studio

On Visual Studio, Right-click on the project and click publish, and select publish method as Azure function.

azure-function-publish-in-vs

azure-function-publish

That’s it for this article.

Source Code

Conclusion

In this article, we discussed How to Build a serverless CRUD app with Azure function and Cosmos DB. We walked through the entire process of creating a restful CRUD API hosted on Azure cloud infrastructure. We will also covered serverless technologies, specifically Azure functions and Cosmos in serverless capacity mode. 

Leave behind your valuable queries and suggestions in the comment section below. Also, if you think this article helps you, do not forget to share this with your developer community. Happy Coding 🙂

Latest Articles

SUPPORT ME

Buy Me A Coffee

Leave a Reply

Your email address will not be published. Required fields are marked *