is a globally distributed, multi-model, NoSQL database service that allows us to build highly available and scalable applications. Cosmos DB supports applications that use Document model data through its and . Azure Cosmos DB SQL API MongoDB API I’ve meant to produce more content on Cosmos DB’s Mongo API, so in this article, I will be developing a Serverless API in that uses a Cosmos DB MongoDB API account. This article has been loosely based on this fantastic tutorial on . Azure Functions creating a Web API with ASP.NET Core and MongoDB By the end of this article, you’ll know how to create a Cosmos DB account that uses the MongoDB API. You’ll also know how to create a simple CRUD Web API in C# that interacts with a Mongo DB API account. If you want to see the whole code before diving into the article, you can check it out on my GitHub . here What you’ll need to follow along with this article: Visual Studio 2019 with the Azure Development workload. Azure Subscription. . Postman How does Azure Cosmos DB support a MongoDB API? , allowing us to use client drivers and tools that we’re used to, but allow us to host our data in Azure Cosmos DB. Azure Cosmos DB implements the wire protocol for MongoDB This is great if we already have applications that use MongoDB. We can change our applications to use Azure Cosmos DB without making significant changes to our codebase. We can also benefit from Azure Cosmos DB, such as Turnkey distribution and elastic scalability in both throughput and storage. Setting up an Account with the MongoDB API Let’s set up our Cosmos DB account. Login to Azure and click on Create a resource. Look for Azure Cosmos DB and click ‘New’. On the ‘Create Azure Cosmos DB Account’ page, provide the following configuration: — Resource groups in Azure are a logical collection of resources. For this tutorial, you can create a new one or add your account to an existing one. Resource Group — This will be the name of your account. It needs to be unique across Azure, so make it a good one :) API — Azure Cosmos DB is a multi-model database, but when we create a Cosmos DB account, we can only pick one API, and that will be the API for the lifetime of the account. Since this is a Mongo DB API tutorial, pick the ‘ ’ option. Account Name Azure Cosmos DB for MongoDB API — Where we want to provision the account. I’ve chosen Australia East as that’s the data center close to me, but pick a data center close to you. Location — In a nutshell, this is how throughput will be provisioned in this account. I have written articles on how throughput works in Cosmos DB if you are interested, but for now, choose ‘ ’ (still in preview at the time of writing). Capacity Mode Serverless — Choose production Account Type — This is the version of the MongoDB wire protocol that the account will support. Choose 3.6 Version — Disable this for now. Availability Zones Your configuration should look like the figure below: Click , then to create your Cosmos DB account. Feel free to grab a cup of tea while you wait. Review+Create Create Once our account is set up, we can create the Database and collection we need for this tutorial. In the original blog post, we would create our Database and Collection via the Mongo Shell. But to keep things simple, we can do this in the portal. In your Cosmos DB account, head into your Data Explorer and click ‘ ’. Enter ‘ ’ as your database name and Books as your collection name. We are then asked to choose a shard key. New Collection BookstoreDB Sharding in Mongo DB is a method for distributing data across multiple machines. If you’ve used the SQL API in Azure Cosmos DB, this is similar to a Partition Key. Essential this shard will help your application scale in terms of both throughput and storage through horizontal scaling. For this tutorial, I’m going to pick the category as the shard key. Click OK to create your database and collection. We now have our Database, collection, and account all set up. We just need one more thing before setting up, our connection string. Click on Connection String and copy the value. Save this for later. PRIMARY CONNECTION STRING Creating our Function Application. Let’s head into Visual Studio and create our Serverless API. Open Visual Studio and click ‘ ’. Create a new project Choose ‘ ’ as our template to create the project. (Make sure C# is the selected language). Azure Functions Call this project ‘ ’, store the project in a location of your choice, and click create: CosmosBooksApi Select Azure Functions v3 (.NET Core) as our Runtime and create an empty project with no triggers: Before we start creating any of our Functions, we need to install the package. To do this, right-click your project and select ‘ ’. In the Browse section, type in MongoDB.Driver and install the latest stable version. MongoDB.Driver Manage NuGet Packages Once that’s installed, let’s create a file that will instantiate our : Startup.cs MongoClient using CosmosBooksApi; using CosmosBooksApi.Services; using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using System.IO; using System.Security.Authentication; [assembly: FunctionsStartup(typeof(Startup))] namespace CosmosBooksApi { public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); builder.Services.AddSingleton<IConfiguration>(config); MongoClientSettings settings = MongoClientSettings.FromUrl(new MongoUrl(config["ConnectionString"])); settings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 }; builder.Services.AddSingleton((s) => new MongoClient(settings)); builder.Services.AddTransient<IBookService, BookService>(); } } } Since v2 of Azure Functions, . This will help us instantiate our as a Singleton, so we can share our amongst all of our Functions rather than creating a new instance of our client every time we want to invoke our Functions. we have support for Dependency Injection MongoClient MongoClient Let’s go through this class. To register our services, we need to add components to an , which we pass as a method. I FunctionsHostBuilder instance Configure To use this method, we need to add the assembly attribute to the Startup class itself. FunctionsStartup We then create a new configuration of type . All this does is pick up the configuration for the Function application from a file. We then add the service as a Singleton. IConfiguration local.settings.json IConfiguration We can then set up our . We start by setting our connection string by passing our from earlier as a object. Save this in your . For reference, this is what my file looks like: MongoClient PRIMARY CONNECTION STRING MongoUrl() local.settings.json file { "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "ConnectionString": "<PRIMARY_CONNECTION_STRING>", "DatabaseName": "BookstoreDB", "CollectionName": "Books" } } We can then pass the key of the setting (“ ”) into our object. ConnectionString MongoUrl We then need to enable SSL by using the Tls12 protocol in the for our . This is required by Azure Cosmos DB to connect to our MongoDB API account. SslSettings MongoClientSettings Once we’ve set up our , we can pass this into our object, which we set up as a Singleton Service. MongoClientSettings MongoClient Now we need to create a basic class to represent our Book model. Let’s write the following: using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace CosmosBooksApi.Models { public class Book { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } [BsonElement("name")] public string BookName { get; set; } [BsonElement("price")] public decimal Price { get; set; } [BsonElement("category")] public string Category { get; set; } [BsonElement("author")] public string Author { get; set; } } } In this class, we have properties for the Book’s Id, name, price, category, and author. The Id property has been annotated with the property to indicate that this will be the document’s primary key. We have also annotated the Id property with to pass our id as type string, rather than . Mongo will handle the conversion from string to for us. BsonId [BsonRepresentation(BsonType.ObjectId)] ObjectId ObjectId The rest of our properties have been annotated with . This will determine how our properties look within our collection. [BsonElement()] Now we’re going to create a service that handles the logic that works with our Cosmos DB account. Let’s define an interface called . IBookService.cs using CosmosBooksApi.Models; using System.Collections.Generic; using System.Threading.Tasks; namespace CosmosBooksApi.Services { public interface IBookService { /// <summary> /// Get all books from the Books collection /// </summary> /// <returns></returns> Task<List<Book>> GetBooks(); /// <summary> /// Get a book by its id from the Books collection /// </summary> /// <param name="id"></param> /// <returns></returns> Task<Book> GetBook(string id); /// <summary> /// Insert a book into the Books collection /// </summary> /// <param name="book"></param> /// <returns></returns> Task CreateBook(Book bookIn); /// <summary> /// Updates an existing book in the Books collection /// </summary> /// <param name="id"></param> /// <param name="book"></param> /// <returns></returns> Task UpdateBook(string id, Book bookIn); /// <summary> /// Removes a book from the Books collection /// </summary> /// <param name="book"></param> /// <returns></returns> Task RemoveBook(Book bookIn); /// <summary> /// Removes a book with the specified id from the Books collection /// </summary> /// <param name="id"></param> /// <returns></returns> Task RemoveBookById(string id); } } This is just a simple CRUD interface that defines the contract that our service should implement. Now let’s implement this interface: using CosmosBooksApi.Models; using Microsoft.Extensions.Configuration; using MongoDB.Driver; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace CosmosBooksApi.Services { public class BookService : IBookService { private readonly MongoClient _mongoClient; private readonly IMongoDatabase _database; private readonly IMongoCollection<Book> _books; public BookService( MongoClient mongoClient, IConfiguration configuration) { _mongoClient = mongoClient; _database = _mongoClient.GetDatabase(configuration["DatabaseName"]); _books = _database.GetCollection<Book>(configuration["CollectionName"]); } public async Task CreateBook(Book bookIn) { await _books.InsertOneAsync(bookIn); } public async Task<Book> GetBook(string id) { var book = await _books.FindAsync(book => book.Id == id); return book.FirstOrDefault(); } public async Task<List<Book>> GetBooks() { var books = await _books.FindAsync(book => true); return books.ToList(); } public async Task RemoveBook(Book bookIn) { await _books.DeleteOneAsync(book => book.Id == bookIn.Id); } public async Task RemoveBookById(string id) { await _books.DeleteOneAsync(book => book.Id == id); } public async Task UpdateBook(string id, Book bookIn) { await _books.ReplaceOneAsync(book => book.Id == id, bookIn); } } } I’ve injected my dependencies to my and , then I create my database and collection to perform operations against them. Lets explore the different methods one by one. MongoClient IConfiguration — This will insert a single document into our asynchronously. Here, we pass in the document we want to persist, this case being the Book object. We could also pass in some custom options ( ) and a . We’re not returning anything here expect the result of the insert operation ‘Task’. InsertOneAsync IMongoCollection InsertOneOptions CancellationToken — This will find a document that matches our filter asynchronously. Here, we use a lambda expression to find a book with the same id that we have provided in the method. We then use Linq to return the matching book. FindAsync — This will delete a single document that matches our expression asynchronously. Again, we use a lambda expression to find the book we wish to delete. We don’t return anything except the result of the operation. DeleteOneAsync — This will replace a single document asynchronously. ReplaceOneAsync So we’ve created our and have a basic CRUD service that we can use to interact with our Cosmos DB account. We’re now ready to start creating our Functions. MongoClient For this tutorial, we will create the following functions: CreateBook DeleteBook GetAllBooks GetBookById UpdateBook To create a new Function, we right-click our solution file and select ‘ ’. We should see a pop-up like this. Select Http Trigger and select Anonymous as the Function’s Authorization level. Add New Azure Function Let’s start with our function. Here is the code: CreateBook using CosmosBooksApi.Models; using CosmosBooksApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using MongoDB.Bson; using Newtonsoft.Json; using System; using System.IO; using System.Threading.Tasks; namespace CosmosBooksApi.Functions { public class CreateBook { private readonly ILogger<CreateBook> _logger; private readonly IBookService _bookService; public CreateBook( ILogger<CreateBook> logger, IBookService bookService) { _logger = logger; _bookService = bookService; } [FunctionName(nameof(CreateBook))] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "Book")] HttpRequest req) { IActionResult result; try { var incomingRequest = await new StreamReader(req.Body).ReadToEndAsync(); var bookRequest = JsonConvert.DeserializeObject<Book>(incomingRequest); var book = new Book { Id = ObjectId.GenerateNewId().ToString(), BookName = bookRequest.BookName, Price = bookRequest.Price, Category = bookRequest.Category, Author = bookRequest.Author }; await _bookService.CreateBook(book); result = new StatusCodeResult(StatusCodes.Status201Created); } catch (Exception ex) { _logger.LogError($"Internal Server Error. Exception: {ex.Message}"); result = new StatusCodeResult(StatusCodes.Status500InternalServerError); } return result; } } } Here we are injecting our and our into the function. We invoke this Function by making a post request to the ‘ ’ Route. We take the incoming HttpRequest and Deserialize it into a Book object. We then insert the Book into our Books collection. If we’re successful, we get a 201 response (Created). If not, we’ll throw a 500 response. IBookService ILogger /Book It’s a bit of a dramatic response code. We would want to throw a different code if there was a bad request of if our Cosmos DB account is unavailable, but for this basic example, it will suffice. Now let’s take a look at the function: DeleteBook using CosmosBooksApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace CosmosBooksApi.Functions { public class DeleteBook { private readonly ILogger<DeleteBook> _logger; private readonly IBookService _bookService; public DeleteBook( ILogger<DeleteBook> logger, IBookService bookService) { _logger = logger; _bookService = bookService; } [FunctionName(nameof(DeleteBook))] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "Book/{id}")] HttpRequest req, string id) { IActionResult result; try { var bookToDelete = await _bookService.GetBook(id); if (bookToDelete == null) { _logger.LogWarning($"Book with id: {id} doesn't exist."); result = new StatusCodeResult(StatusCodes.Status404NotFound); } await _bookService.RemoveBook(bookToDelete); result = new StatusCodeResult(StatusCodes.Status204NoContent); } catch (Exception ex) { _logger.LogError($"Internal Server Error. Exception thrown: {ex.Message}"); result = new StatusCodeResult(StatusCodes.Status500InternalServerError); } return result; } } } This time, we pass in an id to our Function (‘ ’) to find the Book that we want to delete from our collection. We first look for the book using the method . If the book doesn’t exist, the Function will throw a 404 (not found) response. /Book/id IBookService .GetBook(id) If we can find the book, we pass this book into our method to delete the book from our collection in Cosmos DB. If successful, we return a 204 response. RemoveBook(book) Here is the code for the function: GetAllBooks using CosmosBooksApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace CosmosBooksApi.Functions { public class GetAllBooks { private readonly ILogger<GetAllBooks> _logger; private readonly IBookService _bookService; public GetAllBooks( ILogger<GetAllBooks> logger, IBookService bookService) { _logger = logger; _bookService = bookService; } [FunctionName(nameof(GetAllBooks))] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Books")] HttpRequest req) { IActionResult result; try { var books = await _bookService.GetBooks(); if (books == null) { _logger.LogWarning("No books found!"); result = new StatusCodeResult(StatusCodes.Status404NotFound); } result = new OkObjectResult(books); } catch (Exception ex) { _logger.LogError($"Internal Server Error. Exception thrown: {ex.Message}"); result = new StatusCodeResult(StatusCodes.Status500InternalServerError); } return result; } } } In this function, we simply make a GET request to the ‘ ’ route. This Function will call the method on our to retrieve all the books in our collection. If there are no books, we throw 404. If there are books returned to us, the function will return these as an array back to the user. /Books .GetBooks() IBookService Our function is similar to our function, but this time we pass the id of the Book that we want to return to us: GetBookById GetAllBooks using CosmosBooksApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace CosmosBooksApi.Functions { public class GetBookById { private readonly ILogger<GetBookById> _logger; private readonly IBookService _bookService; public GetBookById( ILogger<GetBookById> logger, IBookService bookService) { _logger = logger; _bookService = bookService; } [FunctionName(nameof(GetBookById))] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Book/{id}")] HttpRequest req, string id) { IActionResult result; try { var book = await _bookService.GetBook(id); if (book == null) { _logger.LogWarning($"Book with id: {id} doesn't exist."); result = new StatusCodeResult(StatusCodes.Status404NotFound); } result = new OkObjectResult(book); } catch (Exception ex) { _logger.LogError($"Internal Server Error. Exception thrown: {ex.Message}"); result = new StatusCodeResult(StatusCodes.Status500InternalServerError); } return result; } } } We also pass an id to our function. We first make a call to our method to find the book we want to update. Once we’ve found the book, we read the incoming request and deserialize it into a Book object. We then use the deserialized request to update our Book object and then pass this object into out method along with the id that we used to invoke the Function. UpdateBook .GetBook(id) .UpdateBook() using CosmosBooksApi.Models; using CosmosBooksApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.IO; using System.Threading.Tasks; namespace CosmosBooksApi.Functions { public class UpdateBook { private readonly ILogger<UpdateBook> _logger; private readonly IBookService _bookService; public UpdateBook( ILogger<UpdateBook> logger, IBookService bookService) { _logger = logger; _bookService = bookService; } [FunctionName(nameof(UpdateBook))] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "Book/{id}")] HttpRequest req, string id) { IActionResult result; try { var bookToUpdate = await _bookService.GetBook(id); if (bookToUpdate == null) { _logger.LogWarning($"Book with id: {id} doesn't exist."); result = new StatusCodeResult(StatusCodes.Status404NotFound); } var input = await new StreamReader(req.Body).ReadToEndAsync(); var updateBookRequest = JsonConvert.DeserializeObject<Book>(input); Book updatedBook = new Book { Id = id, BookName = updateBookRequest.BookName, Author = updateBookRequest.Author, Category = bookToUpdate.Category, Price = updateBookRequest.Price }; await _bookService.UpdateBook(id, updatedBook); result = new StatusCodeResult(StatusCodes.Status202Accepted); } catch (Exception ex) { _logger.LogError($"Internal Server Error: {ex.Message}"); result = new StatusCodeResult(StatusCodes.Status500InternalServerError); } return result; } } } Testing our Function Now that we’ve finished coding up our Functions, let’s spin it up and test it! Press F5 to start running our function locally. Give it a second to spin up and you should see the following endpoints for each of our Functions. As you can see, our functions are running on localhost. We can use Postman to test these endpoints. Let’s start with our Function. Copy and Paste the endpoint for the Function into Postman. Set the request method to POST and click on the body tab. We need to send our request as a JSON payload, so set it to JSON and add the following body: CreateBook { "BookName" : "Computer Science: Distilled", "Price": 11.99, "Category": "Technology", "Author": "Wladston Ferreira Filho" } Your Postman request should look like this: Hit Send to send the request. We should get the following response (201). We can view the document in our Cosmos DB account to make sure that we added the document to our account, which it has: Insert a couple more books before moving on. We will now try to retrieve all the books in our collection using the Function. Clear the JSON payload from the Body and change the request method to GET. Hit Send to make the request: GetAllBooks We should get a response like this: Here we have all the books in our collection returned to us as a JSON array. Now, let’s test our Function. In response to our function, grab an id and add it as a parameter in your route. Keep the body clear and keep the GET request method. All that has changed here is that we are looking for a specific book by using its Id. GetBookById GetAllBooks Hit Send to make the request. We should have the book object returned to us like so: Now let’s remove this book from our Cosmos DB collection. Change your request method to DELETE in Postman and hit Send. We should get the following response: Checking our Collection in Azure Cosmos DB, we can see that the book is no longer there: Finally, let’s try updating a book. Let’s take the following book in our collection: { "id": "603ae1b621786dd7fd92d5c0", "bookName": "The Dark Net", "price": 18.99, "category": "Technology", "author": "Jamie Bartlett" } Grab the id and use it as a parameter to our function. Change the method to a PUT request and add the following body to our request: UpdateBook { "bookName": "The Dark Net v2", "price": 11.99, "author": "Jamie Bartlett" } We send this body as a JSON payload. Hit Send to update this Book document. We should get the following response. We can also verify that the document has updated successfully in our Cosmos DB account. Conclusion In this tutorial, we built a CRUD Web API using Azure Functions that manage books in a Cosmos DB Mongo API account. While this was a simple tutorial, hopefully, you can see that if you’ve already built applications using MongoDB as a datastore, you can easily change to Azure Cosmos DB without making drastic changes to your codebase. If you want to download the full code, check it out on my (If you notice anything wrong and want to help fix it, please feel free to make a PR!) GitHub If you have any questions, please feel to comment below or reach out to me via . Twitter Previously published on . dev.to