It has been a while that I wrote something :). There have been so many things I learnt in last few years and I wanted to share my learnings but one thing or other kept me away. I am hoping that I will be break this 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 blog posts, I am going to focus on using Azure Cosmos DB service with their Node SDK. I am not going to write about what Cosmos DB is because you’ll find plenty of material to find that information. We will simply focus on how you can work with Azure Cosmos DB using 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 would need before we can get started:
- Cosmos DB Account Credentials: You would need a Cosmos DB account credentials (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 of this post, most 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 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 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; };
First thing we’re doing here is getting an instance of CosmosClient
and then reading all databases using readAll()
method in Databases
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 acccount. 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 create()
method in Databases
class.
The output of this method is an object that has 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
Database
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; }
Thee code is pretty straight forward. First we get an instance of CosmosClient
and then get an instance of Database
class and then we call read
method on that object.
The output of this method is an object that has 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
Database
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 Database
class and then we call delete
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 Offers
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 query
method in Offers
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, update the offerThroughtput
property in that offer and then replace 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 Offer
class and finally calling replace
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 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!