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 nodecosmos, open 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 appconfig. In 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`, StatusCode: record.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, ProductId: product.ProductId, ProductName: product.ProductName, CategoryName: product.CategoryName, SubCategory: product.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.