Build a serverless API using an Azure Function that reads and writes data to an Azure Cosmos DB with little to no code
Dimitar Iliev ??
Azure AI Solutions Architect ● B. Sc. Computer Science and Engineering ● 7 x Microsoft Certified ● 23 x Microsoft Applied Skills ● Speaker ● Generative AI ● Scrum Master Certified ● 1 x GitHub Certified
What are Azure Functions?
According to the official?MS Docs (Azure Functions Overview | Microsoft Docs)?an Azure Function is a serverless solution
Some of the benefits of using Azure Functions are:?
? If you want to find out more about Azure Functions, I cover other information and usage for them in my other article:
What is Azure Cosmos DB?
According to the official MS Docs (Introduction to Azure Cosmos DB | Microsoft Learn) Azure Cosmos DB is a fully managed NoSQL database for modern app development
There are lots of benefits to using Azure Cosmos DB, and some of them are:
and many others.
Creating the Azure Functions project
First let’s create the Azure Functions project. Open Visual Studio and create a new project of type 'Azure Functions'.
Set 'Serverless.Library' as the project name.
For the trigger choose 'Http trigger' and click on 'Create'.
Modifying the project
Before we start changing the code, first rename the Function1.cs file to Library.cs.
Next let’s put the endpoints we will consume and for now leave their implementation empty.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Serverless.Library
{
? ? public static class Library
? ? {
? ? ? ? [FunctionName("GetAll")]
? ? ? ? public static async Task<IActionResult> GetAllBooks(
? ? ? ? ? ? [HttpTrigger(AuthorizationLevel.Function, "get", Route = "books")] HttpRequest req,
? ? ? ? ? ? ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation("Get All Books called!");
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Get")]
? ? ? ? public static async Task<IActionResult> GetBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "get", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Get Book called with id {id}!");
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Post")]
? ? ? ? public static async Task<IActionResult> AddBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "post", Route = "books")] HttpRequest req,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation("Add Book called!");
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Put")]
? ? ? ? public static async Task<IActionResult> UpdateBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "put", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Update Book called with id {id}!");
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Delete")]
? ? ? ? public static async Task<IActionResult> DeleteBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "delete", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Delete Book called called with id {id}!");
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? }
}
Start the Azure Function and observe the endpoints. We have a total of five endpoints which do the following:
Creating the Function App in the Azure portal
Now let us move on and create a Function App in the Azure portal, to which we will publish our serverless API.
Go to the Azure portal and search for 'Function App'.
Click on 'Create'.
Fill in the details as below:
Click on 'Review + create' and wait for the deployment to succeed.
Click on 'Go to resource' and take a look at the Function app.
Creating the Azure Cosmos DB
Now let’s move on to creating our Azure Cosmos DB.
In the Azure portal search for 'Cosmos DB'.
Click on 'Create Azure Cosmos DB account'.
We have a couple of APIs to choose from. The recommended is to use Core (SQL) so we will choose that one.
For more information on the APIs I suggest you read the official MS Docs (Choose an API in Azure Cosmos DB | Microsoft Learn).
Fill in the following information as below:
Wait for the deployment to succeed.
Click on 'Go to resource' and take a look at the initial Azure Cosmos DB account.
Next step is to create our database and container.
Go to the 'Data Explorer' on the left menu and click on the 'New Container' button.?
Fill in the 'New Container' information as below:
领英推荐
After this, open the books container and click on 'Items'. You will see that currently we have nothing inside.
Last thing to do is to get the Primary Connection String that we will use in our Azure Function to access the database. It can be found when clicking on the 'Keys' option from the left menu.
Perfect. We are all set to implement our code for the serverless API we created previously.
Working with Azure Cosmos DB
First thing we will do is install the following NuGet packages: Microsoft.Azure.Cosmos and Microsoft.Azure.WebJobs.Extensions.CosmosDB.
Next, add the connection string located in the 'Keys' option to the local.settings.json file of our Function app (if you want to test this locally):
{
"IsEncrypted": false,
? "Values": {
? ? "AzureWebJobsStorage": "UseDevelopmentStorage=true",
? ? "FUNCTIONS_WORKER_RUNTIME": "dotnet",
? ? "CosmosDbConnectionString": "YOUR CONNECTION STRING HERE"
? }
}
Implementing the API
Time to implement our endpoints. For this we will use Azure Cosmos DB trigger and bindings for Azure Functions.
For more information on this topic, I suggest you read the official MS Docs (Azure Cosmos DB bindings for Functions 2.x and higher | Microsoft Learn).
Replace the code in the Library.cs file with the following code:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
using Microsoft.Azure.Documents.Client;
namespace Serverless.Library
{
? ? public static class Library
? ? {
? ? ? ? [FunctionName("GetAll")]
? ? ? ? public static async Task<IActionResult> GetAllBooks(
? ? ? ? ? ? [HttpTrigger(AuthorizationLevel.Function, "get", Route = "books")] HttpRequest req,
? ? ? ? ? ? [CosmosDB(databaseName: "Library", collectionName: "books", SqlQuery = "SELECT * FROM books", ConnectionStringSetting = "CosmosDbConnectionString")] IEnumerable<dynamic> books,
? ? ? ? ? ? ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation("Get All Books called!");
? ? ? ? ? ? return new OkObjectResult(books);
? ? ? ? }
? ? ? ? [FunctionName("Get")]
? ? ? ? public static async Task<IActionResult> GetBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "get", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ? [CosmosDB(databaseName: "Library", collectionName: "books", Id = "{id}", PartitionKey = "{id}", ConnectionStringSetting = "CosmosDbConnectionString")] dynamic book,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Get Book called with id {id}!");
? ? ? ? ? ? return new OkObjectResult(book);
? ? ? ? }
? ? ? ? [FunctionName("Post")]
? ? ? ? public static async Task<IActionResult> AddBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "post", Route = "books")] HttpRequest req,
? ? ? ? ? ? ? [CosmosDB(databaseName: "Library", collectionName: "books", ConnectionStringSetting = "CosmosDbConnectionString")] IAsyncCollector<dynamic> documentsOut,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation("Add Book called!");
? ? ? ? ? ? string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
? ? ? ? ? ? dynamic data = JsonConvert.DeserializeObject(requestBody);
? ? ? ? ? ? await documentsOut.AddAsync(new
? ? ? ? ? ? {
? ? ? ? ? ? ? ? id = Guid.NewGuid().ToString(),
? ? ? ? ? ? ? ? name = data?.name,
? ? ? ? ? ? ? ? author = data?.author
? ? ? ? ? ? });
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Put")]
? ? ? ? public static async Task<IActionResult> UpdateBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "put", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ? ? [CosmosDB(databaseName: "Library", collectionName: "books", Id = "{id}", PartitionKey = "{id}", ConnectionStringSetting = "CosmosDbConnectionString")] dynamic book,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Update Book called with id {id}!");
? ? ? ? ? ? string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
? ? ? ? ? ? dynamic data = JsonConvert.DeserializeObject(requestBody);
? ? ? ? ? ? book.name = data?.name;
? ? ? ? ? ? book.author = data?.author;
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? ? ? [FunctionName("Delete")]
? ? ? ? public static async Task<IActionResult> DeleteBook(
? ? ? ? ? ?[HttpTrigger(AuthorizationLevel.Function, "delete", Route = "books/{id}")] HttpRequest req,
? ? ? ? ? ? ?[CosmosDB(databaseName: "Library", collectionName: "books", Id = "{id}", PartitionKey = "{id}", ConnectionStringSetting = "CosmosDbConnectionString")] dynamic book,
? ? ? ? ? ? ?[CosmosDB(databaseName: "Library", collectionName: "books", ConnectionStringSetting = "CosmosDbConnectionString")] DocumentClient client,
? ? ? ? ? ?string id,
? ? ? ? ? ?ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation($"Delete Book called called with id {id}!");
? ? ? ? ? ? await client.DeleteDocumentAsync(book.SelfLink, new Microsoft.Azure.Documents.Client.RequestOptions { PartitionKey = new Microsoft.Azure.Documents.PartitionKey(id) });
? ? ? ? ? ? return new OkResult();
? ? ? ? }
? ? }
}
As you can see, we have little to almost no code in our endpoints.
Let’s publish our Azure Function and see if everything works as expected.
Publishing the Azure Function to the Function App
We will publish our app by using the publish profile which you can download from the Azure portal by clicking on the 'Get publish profile'.
Next add the connection string in the Configuration of our Function App.
Additionally, you can put the connection string in Azure Key Vault and use Key Vault Reference to reference it in the Function App Configuration.
? Read more about this topic in my other article:
In order for us to call our Function App we need to add the x-functions-key header and set a value. This value can be found in 'App keys'.
Set the header in Postman as below:
Perfect. Now we have our Azure Function published and our Cosmos DB ready.
Let us now test all of this out using Postman.?
Testing the serverless API
Creating new books
Get all books
Get a single book by id
Update a book
Delete a book
As we can see our serverless API is functioning as intended.
Perfect. We have successfully created a serverless API that reads and writes data to an Azure Cosmos DB. We managed to do it will little to no code as well.
Thanks for sticking to the end of another article from?"Iliev Talks Tech". #ilievtalkstech
The full, more detailed implementation of this example can be found on my GitHub repository on the following link:
Business Professional | Creative Problem Solver | Ambitious & Dedicated | Solution-Oriented | MSc LUSEM
2 年Very educational ?? Keep it up, and can't wait for the next one!
Web Developer at MCA.mk
2 年Great article, keep it up!