Azure Cosmos DB: Using Node.js and @azure/cosmos to perform Read/Write Operations with Azure Cosmos DB

In this article, we will implement the Azure Cosmos DB operations using Node.js by creating REST APIs using Express.js. As we all know that the Azure Cosmos DB is a globally distributed, multi-model database service that supports document, key-value, wide-column, and graph databases. Large scale data collection applications always prefer to store the structured and unstructured data in globally distributed databases. This provides the great advantage of data availability, scalability and accessibility.

 

While developing JavaScript full-stack applications, developers always faces challenge of using JavaScript object model to connect to such a huge database service for performing read/write operations. To address this challenge, the Azure Cosmos DB Client Library (@azure/cosmos) is provided which can be used for following operations

  • Create the Cosmos DB databases and modify their settings
  • Operations to Create and modify containers to store collections of JSON documents
  • Operations to Create, read, update, and delete the items  in your containers
  • Manage Querying the documents in your database using SQL-like syntax

We will be using Express and Babel object model to use the Azure Cosmos DB Client Library, to perform various operations on Cosmos DB using SQL APIs. Figure 1 will provide an idea of the application implementation.


 


Figure 1: The Application

 

Creating Azure Cosmos DB Account

 

Login to the Azure Portal and click on Create a resource link. This will open create resources page from where we can create Azure Cosmos DB as shown in Figure 2

 



Figure 2: Creating Azure Cosmos DB resource

 

Next, we can create the Azure Cosmos DB Account for which we need to select API as NoSQL as shown in Figure 3



Figure 3: Creating Account with API

 

Next, we need to select the Subscription Name, Resource Group Name, Account Name, Location and the most important is Capacity mode, in our case we will select the value for Capacity mode as Serverless. This will charge based on the consumption. Figure 4 shows the details of creation of account



Figure 4: The NoSQL API details. 

 

Once the Azure Cosmos DB account is created, we need connection string so that we can connect to it from the application to perform read/write operations. Figure 5 shows the page from where we can select the connection string.



Figure 5:  The Connection String

 

Copy this connection string and paste it in notepad so that we can use it later in application.

 

Step 1: Open command prompt and create a folder named nodecosmosopen this folder Visual Studio Code. Open the command prompt and navigate to the nodecosmos folder. Run the following command to create package.json

 

npm init -y

 

Step 2: Install necessary packages as devDependencies in the project as shown in the below command

 

npm install —save-dev @babel/core @babel/node @babel/plugin-proposal-decorators @babel/preset-env @types/cors @types/node"

 

These packages will be used to transpile the JavaScript code using Babel as well as will help to provide type definitions while writing code.

 

Install Nodemon package to continuously run the application even the code is changed. Use the below command to install Nodemon package

npm install –global nodemon

 

Install package shown below for the application development. We need Express and CORS package to create REST APIs as well as we need the Azure Cosmos DB Client Library for working with Azure Cosmos DB.

npm install @azure/cosmos @types/express @types/node  cors express ts-node-dev

 

 

Step 3: In the nodecosmos project folder, add a new folder and name it appconfigIn this fodler, add a new JavaScript file and name it config.js. In this file we will keep the connection string, database id and container id. Listing 1 show the code in config.js

export const appConfig = {

    conneectionString: “CONNECTION-STRING-COPIED-FROM-PORTAL”,

    databaseId: 'eShopping',

    containerId:'ProductsCatelog'

};

 

Listing 1: config.js

We will use these values while establishing connection with database

 

Step 4: In the project add a new folder named models. In this folder, add a new file and name it dataaccess.js. In this file we will add code to create database and container as mentioned in condig.js. In this file we will use CosmosClient class from @azure/cosmos. This class provides a client-side logical representation of Azure Cosmos DB database account. This class allows to configure and execute requests in the Azure Cosmos DB database service. This class has databases and containers properties of the type Databases and Containers respectively. These types contains createIfNotExist(method to create database and container if they are not already present.

Once the container is created, we can perform Read/Write operations using following methods and properties of Container type.

·      The items property of the type Items is used to create new items and reading/querying all items in the collection.

o   The create(method of Items type accepts a JSON schema that represents Key:Value pairs of item to be created in container.

o   The query(method accepts SQL like syntax query to read all items from the collection.

§  This is an asynchronous method that returns QueryIterator type instance. This type has the fetchAll(asynchronous method that return an instance of FeedResponse type. This type has resources property that represents data read from the collection.    

·      The item(method of the container accepts an id and PartitionKey arguments as input parameters. The item() method return an instance of Item type. This type contains an asynchronous method named read(). The read() method returns response of ItemResponse type that contains resource property representing a single item from the collection.

o   The delete(method of Item type is used to delete searched record using Item() method.

o   The replace() method of Item type is used to replace old contents of the item from container by new contents.    

 

Listing 2 shows an implementation of read/write operations.

import CosmosClient } from '@azure/cosmos';

import appConfig } from "../appconfig/config.js";

export class DataAcecss {

    constructor() {

        this.client = new CosmosClient(appConfig.conneectionString);

        this.databaseId = appConfig.databaseId;

        this.collectionId = appConfig.containerId;

    }

     async initDbAndContainer() {

        

         const responseDb = await this.client.databases.createIfNotExists(

                                 {id:this.databaseId});

         this.database = responseDb.database;

         console.log(`Database Created ${this.database}`);

         

          const responseContainer = await this.database.containers.createIfNotExists({

            id:this.collectionId

          });

          this.container =  responseContainer.container;

          console.log(`Container Created ${this.container}`);  

     }

    async addProduct(product) {

        try {

            const resp = await this.container.items.create(product);

            console.log(`In the addProduct ${JSON.stringify(resp.body)}`);

            return resp.resource;

        }catch(ex){

            return {

                Message: `The item creation failed ${ex.message}`

             } 

        }

      

    }

    async getItems() {

      try {

        const query = 'SELECT * FROM c';

        if(!this.container){

            throw new Error('The specified collection is not present');

        }

        const result = await this.container.items.query(query);

        console.log(`Query Result ${(await(result.fetchAll())).resources}`);

        var res =  await result.fetchAll();

        console.log(`The Asybc Res = ${res.resources}`);

        return res.resources;

      } catch(ex){

        return {

            Message: `Read oOperation failed`,

            Exception: ex.message

         }

      }     

    }

    async getItem(id){

        try {

            const record =  await this.container.item(id);

            const rec =  await record.read();

            if(rec.resource === {}) {

                throw new Error(`The item for record id as ${id} you are looking for is not avalaible.`);

            }

            console.log(JSON.stringify(rec.resource));

            return rec.resource;

        }catch(ex){

            return {

                Message: `The item for record id as ${id} you are looking for is not avalaible.`,

                Exception: ex.message

             }

        }

    }

 

    async deleteItem(id){

        try {

            const record =  await this.container.item(id).delete();

           

            return {

                Message: `Item by id ${id} is deleted successfully`,

                StatusCoderecord.statusCode

            };

           

        }catch(ex){

            return {

                Message: `The item for record id as ${id} you are looking for is not avalaible.`,

                Exception: ex.message

             }

        }

    }

    async updateProduct(id,product){

        try {

            const record =  await this.container.item(id);

                let obj = {

                    id:id,

                    ProductIdproduct.ProductId,

                    ProductName: product.ProductName,

                    CategoryNameproduct.CategoryName,

                    SubCategoryproduct.SubCategory,

                    Description: product.Description,

                    Price: product.Price

                };

 

                let result = await record.replace(obj);

                console.log(`Trying to Read update ${record}`);

                console.log(`Status Code ${result.statusCode}`);

                console.log(`Resource Updated ${result.resource}`);

                return result.resource;

        }catch(ex){

          return {

             Message: `The item for record id as ${id} you are looking for is not avalaible.`,

             Exception: ex.message

          } 

        }

    }

}

 

Listing 2: Read/Write operations

 

In the code shown in Listing 2, the initDbAndContainer(method will be used to create database and container if they are not already exist.

 

Step 5: In the project folder, add a new folder named controllers. In this folder add a new JavaScript file named appcontroller.js. In this file we will add code to access methods from DataAccess class to perform Read/Write operations on Cosmos DB. This JavaScript file contains code for ServiceController class. This class has all asynchronous methods those accepts Request and Response objects from Express so that we can read data from URL, Body to perform HTTP Get, Post, Put, and Delete operations. Code in Listing 3 shows the ServiceController class.

 

 

import DataAcecss } from "../models/dataaccess.js";

 

export class ServiceController {

   

    constructor() {

        this.dao = new DataAcecss();

    }

 

    async init() {

        await this.dao.initDbAndContainer();

    }

    

    async getRecords(request, response) {

        const products = await this.dao.getItems();

         console.log(`In COntroller ${JSON.stringify(products)}`);  

        response.send({ data: products });

    }

 

    async getRecord(request, response) {

        const product = await this.dao.getItem(request.params.id);

         console.log(`In Controller ${JSON.stringify(product)}`);  

        response.send({ data: product });

    }

 

    async deleteRecord(request, response) {

        const responseMessage = await this.dao.deleteItem(request.params.id);

         

        response.send(responseMessage);

    }

  

    async addProduct(request, response) {

        const body = request.body;

        console.log(`Received Body ${JSON.stringify(body)}`);

        const product = {

            ProductId:body.ProductId,

            ProductName:body.ProductName,

            CategoryName:body.CategoryName,

            SubCategory:body.SubCategory,

            Description:body.Description,

            Manyfacturer:body.Manyfacturer,

            Price:body.Price

        };

           

       

        console.log(`Product ${JSON.stringify(product)}`);

        let data = await this.dao.addProduct(product);

        response.send({ data: data });

    }

    // 4.5 this method is used to update the product

    async updateProduct(request, response) {

        const body = request.body;

        const id = request.params.id;

        console.log(`Received Body ${JSON.stringify(body)}`);

        const product = {

            ProductId:body.ProductId,

            ProductName:body.ProductName,

            CategoryName:body.CategoryName,

            SubCategory:body.SubCategory,

            Description:body.Description,

            Manyfacturer:body.Manyfacturer,

            Price:body.Price

        };

        console.log(`Product ${JSON.stringify(product)}`);

        let data = await this.dao.updateProduct(id,product);

        response.send({ data: data });

    }

 

}

 

 

Listing 3: The ServiceController class     

 

As shown in Listing 3, the init(method invokes initDbAndContainer() method from the DataAccess class to create database and container.

 

Step 6: In the project folder, add a new JavaScript file and name it as server.js. In this file we will add code to design REST APIs using express js. The REST APIs will call methods from ServiceController class to perform Read/Write operations.  Listing 4, shows the code for REST APIs.

import express from 'express';

import cors from 'cors';

import ServiceController } from "./controllers/appcontroller.js";

 

 

const instance = express();

instance.use(express.json());

instance.use(express.urlencoded({extended:false}));

instance.use(cors());

 

 

const appController = new ServiceController();

 

appController.init().then(() => {

console.log('database and container created Successfully'); }).catch(() => {

    console.log('database and container creation failed');

    process.exit(1);

});

 

// 6. API methods for get, post and put

instance.get('/api/products',

(req,res,next)=>appController.getRecords(req,res).catch(next));

 

instance.get('/api/products/:id',

(req,res,next)=>appController.getRecord(req,res).catch(next));

 

instance.post('/api/products',

(req,res,next)=>appController.addProduct(req,res).catch(next));

 

instance.put('/api/products/:id',

(req,res,next)=>appController.updateProduct(req,res).catch(next));

 

instance.delete('/api/products/:id',

(req,res,next)=>appController.deleteRecord(req,res).catch(next));

 

// 7. listen on the port

instance.listen(7078, () => {

    console.log('started on port 9078');

});

 

 

Listing 4: The REST API

 

As shown in Listing 4,  the init() method from the ServiceController is invokes to create database and container. Next, the code uses HTTP methods of express object to expose REST endpoints on port 7078.     

 

 

Step 7: Modify package.json to run the application as shown in Listing 5

 

"scripts": {

     “start”: “nodemon server.js”

  },

 

Listing 5: Command to run the application.

 

We are using nodemon to run the server.js file.

 

Step 8: Run the following command from the command prompt.

 

npm run start                                                                                                                                         

 

Once the app runs, you will see message showing that the database and container is created as shown in Figure 6

 



Figure 6: Database and Container creation messages

 

To verify if the database and container is created or not, visit back to the folder and in the Azure Cosmos DB database account page, click on the Data Explorer link, you will see that the eShopping database and ProductsCatelog container is created as shown in Figure 7

 



 

Figure 7:  The Database and Container is created.

 

To test the API, open postmon , and make a GET request using the following URL,

 

http://localhost:7080/api/products

 

Since, there is no data in container the response will be empty as shown in Figure 8

 



 

Figure 8: An Empty response

 

Now make a POST request with Content Type as application/json as shown in Figure 9



 

Figure 9: POST request

 

You will see the item create in container as shown in Figure 10

 



 

Figure 10: Data in Container

 

You can see that the item id is generated by the Cosmos DB.

In the record we can see the Price is 670000. Now let’s modify the record price to 900000 and make the PUT request.  The URL of the PUT request will contain the value of id of the item as shown below.

 

http://localhost:7078/api/products/9dec2824-35bd-4b84-921b-00150d9bb9c7

 

You can see that the Price is updated to 900000 as shown in Figure 11

 



Figure 11: The Updated Item

 

Similarly, we can make delete request.



Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach