Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Cosmos DB and Node SDK Part 1: Working With Databases

DZone 's Guide to

Cosmos DB and Node SDK Part 1: Working With Databases

In this post, we will focus on working with databases. We will see how you can perform various operations on databases like listing, creating, deleting, etc.

· Database Zone ·
Free Resource

There have been so many things I've learned in the last few years and I wanted to share my learnings, but one thing or another kept me away. I am hoping that I will break this silence and post regularly going forward.

With my work on Cerebrata Cerulean, I got an opportunity to work a lot with Node.js and some amazing Azure services. In this series of posts, I am going to focus on using Azure Cosmos DB service with their Node SDK. We will cover working with various entities like databases, containers (collections), documents, etc. using Node SDK.

In this post, we will focus on working with databases. We will see how you can perform various operations on databases like listing, creating, deleting, etc.

Before We Begin

There are certain things that you need before we can get started:

  • Cosmos DB Account Credentials: You need a Cosmos DB account (account name and key). This account should target SQL API.
  • Install Node SDK for Cosmos DB: You can find more information about installing Node SDK for Cosmos DB at https://www.npmjs.com/package/@azure/cosmos. At the time of writing this post, the latest version of the SDK is 2.1.5, so we will be using that only.
  • Install Bluebird: We will be showing code using both async/await as well as Promises. For Promises, we will be using the Bluebird library. You can find more information about this library at https://www.npmjs.com/package/bluebird.

Assuming you have created a folder and installed the packages, let's create a file called "database-samples.js" and add the following lines of code there:

const {Promise} = require('bluebird');
const {CosmosClient, Database} = require('@azure/cosmos');

const accountEndpoint = 'https://account-name.documents.azure.com:443/';
const accountKey = 'yM0g3KnPANPpBgKLi34OMz1UZ7Png2pjQrs209IrrQkyhtqZKmALludel1nizEOqeJMm1gavLb0dS0gAoMw3Pw==';

/**
** Method to get client connection object.
**/
const getClient = () => {
   return new CosmosClient({
     endpoint: accountEndpoint,
     auth: {
       masterKey: accountKey
     }
   });
 };

Please make sure to use the values for your Cosmos DB account credentials.

We're now all set to move forward!

Oh, and one more thing. Because we will be including code for both async/await and promises, we will just prefix the method name with the approach we're using. For example, "listDatabasesAsync" and "listDatabasesPromise" for async/await and promise respectively.

List Databases

Let's say we want to list all databases in an account. Here's how we would go about doing that.

Using Async/Await

Here's the code to list databases using async/await:

const listDatabasesAsync = async () => {
  const client = getClient();
  const iterator = await client.databases.readAll().toArray();
  const databases = [];
  iterator.result.forEach((item) => {
    databases.push(item);
  });
  return databases;
};

The first thing we're doing here is getting an instance of CosmosClient and then reading all databases using method in class. The output of this method would be an array, which looks something like the following:

[ 
    { 
        id: 'database-000',//name of the database
        _rid: 'xD4MAA==',//system assigned id to the database
        _self: 'dbs/xD4MAA==/',//self link
        _etag: '"00004d00-0000-0100-0000-5cf55d1d0000"',//etag
        _colls: 'colls/',
        _users: 'users/',
        _ts: 1559584029 //timestamp (in unix epoch)
    },
    {   id: 'database-001',
        _rid: 'B+kiAA==',
        _self: 'dbs/B+kiAA==/',
        _etag: '"00004e00-0000-0100-0000-5cf55d230000"',
        _colls: 'colls/',
        _users: 'users/',
        _ts: 1559584035 
    } 
]

Using Promise

And here's the code to list databases using promise:

const listDatabasesPromise = () => {
  return new Promise((resolve, reject) => {
    const client = getClient();
    client.databases.readAll().toArray()
    .then((databasesListingResult) => {
      const databases = [];
      databasesListingResult.result.forEach((item) => {
        databases.push(item);
      });
      resolve(databases);
    })
    .catch((error) => {
      reject(error);
    });
  });
}

Create Database

Next, let's see how we can create a database in a Cosmos DB account. Let's say we want to create a database by the name "awesomedb".

Using Async/Await

Here's the code to create a database using async/await:

const createDatabaseAsync = async (databaseId) => {
  const databaseDefinition = {
    id: databaseId
  };
  const client = getClient();
  const result = await client.databases.create(databaseDefinition);
  return result;
};

//This is how you would call it.
const runAsync = async () => {
  let databaseName = 'awesomedb';
  console.log(`creating database: ${databaseName}`);
  const result = await createDatabaseAsync(databaseName);
  console.log(result);
};

runAsync().catch((error) => {
  console.log(error);
});

What we're doing here is first getting an instance of CosmosClient and then calling method in class.

The output of this method is an object that has the following key members:

  • body: This contains the system properties of the database like _rid, _self, _etag, _ts etc.
  • headers: This contains the response headers.
  • database: This actually is an instance of class. You will need to use this object if you want to perform any operation on the database like reading its' properties, deleting, etc.

Please note that the method above will fail if a database by the same name already exists in the account.

To fix this, we simply have to use createIfNotExists method. It's that simple! Here's the code to do so:

const createDatabaseIfNotExistsAsync = async (databaseId) => {
  const databaseDefinition = {
    id: databaseId
  };
  const client = getClient();
  const result = await client.databases.createIfNotExists(databaseDefinition);
  return result;
};

Using Promise

And here's the code to do so if you were to use promise:

const createDatabasePromise = (databaseId) => {
  return new Promise((resolve, reject) => {
    const databaseDefinition = {
      id: databaseId
    };
    const client = getClient();
    client.databases.create(databaseDefinition)
    .then((result) => {
      resolve(result);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

const createDatabaseIfNotExistsPromise = (databaseId) => {
  return new Promise((resolve, reject) => {
    const databaseDefinition = {
      id: databaseId
    };
    const client = getClient();
    client.databases.createIfNotExists(databaseDefinition)
    .then((result) => {
      resolve(result);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Create Database With Provisioned Throughput

By default when you create a database, it doesn't have a throughput of its own. Each container in that database will have its own throughput that you define whenever you create that container.

However, what you want to do is define a throughput at the database level so that all containers share that. If you're coming from SQL Database world, this is somewhat similar to Elastic Database Pool there (though the similarity is at the conceptual level only; there are some significant differences there but that's beyond the scope of this post).

So, let's say we want to create a database called "awesomedb" with a provisioned throughput of 1000 RU/s.

Using Async/Await

Here's the code to create a database with a provisioned throughput using async/await:

const createDatabaseWithProvisionedThroughputAsync = async (databaseId, throughput) => {
  const databaseDefinition = {
    id: databaseId
  };
  const requestOptions = {
    offerThroughput: throughput
  };
  const client = getClient();
  const result = await client.databases.create(databaseDefinition, requestOptions);
  return result;
};

//This is how you would call it.
const runAsync = async () => {
  let databaseName = 'awesomedb';
  console.log(`creating database: ${databaseName}`);
  const result = await createDatabaseWithProvisionedThroughputAsync(databaseName, 1000);
  console.log(result);
};

runAsync().catch((error) => {
  console.log(error);
});
const createDatabaseIfNotExistsWithProvisionedThroughputAsync = async (databaseId, throughput) => {
  const databaseDefinition = {
    id: databaseId
  };
  const requestOptions = {
    offerThroughput: throughput
  };
  const client = getClient();
  const result = await client.databases.createIfNotExists(databaseDefinition, requestOptions);
  return result;
};

//This is how you would call it.
const runAsync = async () => {
  let databaseName = 'awesomedb';
  console.log(`creating database: ${databaseName}`);
  const result = await createDatabaseWithProvisionedThroughputAsync(databaseName, 1000);
  console.log(result);
};

runAsync().catch((error) => {
  console.log(error);
});

If you look at the code above, you will notice that it is very similar to the code we used for creating a database without provisioned throughput. To create a database with provisioned throughput, all we have to define a requestOption object and specify the throughput we want as value for offerThroughput property. It's really that simple!

Please note that at the time of writing this post, the minimum throughput that a database can have is 400 RU/s and the maximum that you can set programmatically is 100000 RU/s. For throughputs beyond this limit, you would need to call Azure support.

Using Promise

And here's the code to do so if you were to use promise:

const createDatabaseWithProvisionedThroughputPromise = (databaseId, throughput) => {
  return new Promise((resolve, reject) => {
    const databaseDefinition = {
      id: databaseId
    };
    const requestOptions = {
      offerThroughput: throughput
    };
    const client = getClient();
    client.databases.create(databaseDefinition, requestOptions)
    .then((result) => {
      resolve(result);
    })
    .catch((error) => {
      reject(error);
    })
  });
};

const createDatabaseIfNotExistsWithProvisionedThroughputPromise = (databaseId, throughput) => {
  return new Promise((resolve, reject) => {
    const databaseDefinition = {
      id: databaseId
    };
    const requestOptions = {
      offerThroughput: throughput
    };
    const client = getClient();
    client.databases.createIfNotExists(databaseDefinition, requestOptions)
    .then((result) => {
      resolve(result);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Get Database Properties

Next, let's see how you can read properties of a database.

Using Async/Await

Here's the code to get properties of a database with a provisioned throughput using async/await:

const getDatabasePropertiesAsync = async (databaseId) => {
  const client = getClient();
  const db = client.database(databaseId);
  const result = await db.read();
  return result;
}

The code is pretty straightforward. First, we get an instance of CosmosClient and then get an instance of class, and then we call method on that object.

The output of this method is an object that has the following key members:

  • body: This contains the system properties of the database like _rid, _self, _etag, _ts etc.
  • headers: This contains the response headers.
  • database: This actually is an instance of class.

Using Promise

And here's the code to do so if you were to use promise:

const getDatabasePropertiesPromise = (databaseId) => {
  return new Promise((resolve, reject) => {
    const client = getClient();
    const db = client.database(databaseId);
    db.read()
    .then((result) => {
      resolve(result);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Delete Database

Next, let's see how you can delete a database.

Using Async/Await

Here's the code to delete a database using async/await:

const deleteDatabaseAsync = async (databaseId) => {
  const client = getClient();
  const db = client.database(databaseId);
  await db.delete();
  return true;
};

Again, the code is pretty straightforward. First, we get an instance of CosmosClient and then get an instance of class and then we call method on that object.

My only wish is that SDK introduces a function like deleteIfExists, similar to createIfNotExists.

Using Promise

And here's the code to do so if you were to use promise:

const deleteDatabasePromise = (databaseId) => {
  return new Promise((resolve, reject) => {
    const client = getClient();
    const db = client.database(databaseId);
    db.delete()
    .then((result) => {
      resolve(true);
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Get Database Throughput

Say we created a database with provisioned throughput and we would want to find out how much that throughput is.

Considering we defined throughput on the database, we would expect a property of that database that would tell us about this, but that's not the case :).

To get the throughput on a database (or a container), one would actually need to make use of API. Let's see how that works.

Using Async/Await

Here's the code to get an offer on a database using async/await:

const getDatabaseOfferAsync = async (databaseId) => {
  const client = getClient();
  const db = client.database(databaseId);
  const {body: databaseProperties} = await db.read();
  const selfLink = databaseProperties._self;
  const querySpec = {
    query: 'SELECT * FROM root r WHERE  r.resource = @link',
    parameters: [{
        name: '@link',
        value: selfLink
    }]
  };
  const offerListingResult = await client.offers.query(querySpec).toArray();
  const offer = offerListingResult.result[0];
  return offer;
};

In order to get an offer on a resource (database in this case), we would need the selfLink property of that resource. And that's what we're doing in the code above.

First, we get an instance of CosmosClient. Next, we create an instance of Database and read database properties so that we can get access to _self property of that database. Then, we call method in class to get the offer available on our database.

This is what the output looks like:

{ 
    resource: 'dbs/dEwOAA==/',
    offerType: 'Invalid',
    offerResourceId: 'dEwOAA==',
    offerVersion: 'V2',
    content:
    { 
        offerThroughput: 400,
        offerIsRUPerMinuteThroughputEnabled: false 
    },
    id: 'BlZx',
    _rid: 'BlZx',
    _self: 'offers/BlZx/',
    _etag: '"00005200-0000-0100-0000-5cf5ee200000"',
    _ts: 1559621152 
}

To get the throughput, simply read the offerThroughtput property of the content property of the result.

Using Promise

And here's the code to do so if you were to use promise:

const getDatabaseOfferPromise = (databaseId) => {
  return new Promise((resolve, reject) => {
    const client = getClient();
    const db = client.database(databaseId);
    db.read()
    .then((result) => {
      const databaseProperties = result.body;
      const selfLink = databaseProperties._self;
      const querySpec = {
        query: 'SELECT * FROM root r WHERE  r.resource = @link',
        parameters: [{
            name: '@link',
            value: selfLink
        }]
      };
      client.offers.query(querySpec).toArray()
      .then((offerListingResult) => {
        const offer = offerListingResult.result[0];
        resolve(offer);
      })
      .catch((error) => {
        reject(error);
      });
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Update Database Throughput

This is one last thing we would do for this post. So let's say you create a database with some provisioned throughput and now you would want to change that throughput.

The way to accomplish this is by getting an offer on the database, updating the offerThroughtput property in that offer and then replacing the existing offer with the modified one.

Using Async/Await

Here's the code to update an offer on a database using async/await:

const updateDatabaseProvisionedThroughputAsync = async (databaseId, provisionedThroughput) => {
  const offerDetails = await getDatabaseOfferAsync(databaseId);
  offerDetails.content.offerThroughput = provisionedThroughput;
  const client = getClient();
  const offer = client.offer(offerDetails.id);
  await offer.replace(offerDetails);
};

The code is pretty straightforward. First, we are getting an offer on the database using our getDatabaseOfferAsync method. Then we're updating the provisioned throughput in that offer.

Next, we get an instance of CosmosClient. Using that and the offer id, we create an instance of class and finally calling method on that class to replace an offer.

Using Promise

And here's the code to do so if you were to use promise:

const updateDatabaseProvisionedThroughputPromise = (databaseId, provisionedThroughput) => {
  return new Promise((resolve, reject) => {
    getDatabaseOfferPromise(databaseId)
    .then((offerDetails) => {
      offerDetails.content.offerThroughput = provisionedThroughput;
      const client = getClient();
      const offer = client.offer(offerDetails.id);
      offer.replace(offerDetails)
      .then((result) => {
        resolve(result);
      })
      .catch((error) => {
        reject(error);
      });
    })
    .catch((error) => {
      reject(error);
    });
  });
};

Wrapping Up

That's it for this post! In the next series of posts, we will do the same thing with containers (collections), documents, and other things, so stay tuned for that.

If you find any issues with the code samples or any other information in this post, please let me know and I will fix them at the earliest.

Happy coding!

Topics:
database ,tutorial ,cosmos db ,node sdk ,install bluebird ,azure cosmos db

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}