Connect a Node.js Mongoose application to Azure Cosmos DB

This tutorial demonstrates how to use the Mongoose Framework when storing data in Cosmos DB. We use the Azure Cosmos DB's API for MongoDB for this walkthrough. For those of you unfamiliar, Mongoose is an object modeling framework for MongoDB in Node.js and provides a straight-forward, schema-based solution to model your application data.

Cosmos DB is Microsoft's globally distributed multi-model database service. You can quickly create and query document, key/value, and graph databases, all of which benefit from the global distribution and horizontal scale capabilities at the core of Cosmos DB.

Prerequisites

If you don't have an Azure subscription, create a free account before you begin.

You can Try Azure Cosmos DB for free without an Azure subscription, free of charge and commitments. Or, you can use the Azure Cosmos DB Emulator with a URI of https://localhost:8081. The Primary Key is provided in Authenticating requests.

Node.js version v0.10.29 or higher.

Create a Cosmos account

Let's create a Cosmos account. If you already have an account you want to use, you can skip ahead to Set up your Node.js application. If you are using the Azure Cosmos DB Emulator, follow the steps at Azure Cosmos DB Emulator to set up the emulator and skip ahead to Set up your Node.js application.

  1. In a new window, sign in to the Azure portal.

  2. In the left menu, select Create a resource, select Databases, and then under Azure Cosmos DB, select Create.

    Screenshot of the Azure portal, highlighting More Services, and Azure Cosmos DB

  3. In the Create Azure Cosmos DB Account page, enter the settings for the new Azure Cosmos DB account.

    Setting Value Description
    Subscription Your subscription Select the Azure subscription that you want to use for this Azure Cosmos DB account.
    Resource Group Create new

    Then enter the same unique name as provided in ID
    Select Create new. Then enter a new resource-group name for your account. For simplicity, use the same name as your ID.
    Account Name Enter a unique name Enter a unique name to identify your Azure Cosmos DB account. Because documents.azure.com is appended to the ID that you provide to create your URI, use a unique ID.

    The ID can use only lowercase letters, numbers, and the hyphen (-) character. It must be between 3 and 31 characters in length.
    API Azure Cosmos DB's API for MongoDB The API determines the type of account to create. Azure Cosmos DB provides five APIs: Core (SQL) for document databases, Gremlin for graph databases, Azure Cosmos DB's API MongoDB for document databases, Azure Table, and Cassandra. Currently, you must create a separate account for each API.

    Select MongoDB because in this quickstart you are creating a table that works with the MongoDB.
    Location Select the region closest to your users Select a geographic location to host your Azure Cosmos DB account. Use the location that's closest to your users to give them the fastest access to the data.

    Select Review+Create. You can skip the Network and Tags section.

    The new account page for Azure Cosmos DB

  4. The account creation takes a few minutes. Wait for the portal to display the Congratulations! Your Cosmos account with wire protocol compatibility for MongoDB is ready page.

    The Azure portal Notifications pane

Set up your Node.js application

Note

If you'd like to just walkthrough the sample code instead of setup the application itself, clone the sample used for this tutorial and build your Node.js Mongoose application on Azure Cosmos DB.

  1. To create a Node.js application in the folder of your choice, run the following command in a node command prompt.

    npm init

    Answer the questions and your project will be ready to go.

  2. Add a new file to the folder and name it index.js.

  3. Install the necessary packages using one of the npm install options:

    • Mongoose: npm install mongoose@5 --save

      Note

      The Mongoose example connection below is based on Mongoose 5+, which has changed since earlier versions.

    • Dotenv (if you'd like to load your secrets from an .env file): npm install dotenv --save

      Note

      The --save flag adds the dependency to the package.json file.

  4. Import the dependencies in your index.js file.

    var mongoose = require('mongoose');
    var env = require('dotenv').load();    //Use the .env file to load the variables
    
  5. Add your Cosmos DB connection string and Cosmos DB Name to the .env file. Replace the placeholders {cosmos-account-name} and {dbname} with your own Cosmos account name and database name, without the brace symbols.

    COSMOSDB_CONNSTR=mongodb://{cosmos-account-name}.documents.azure.com:10255/{dbname}
    COSMODDB_USER=cosmos-account-name
    COSMOSDB_PASSWORD=cosmos-secret
    
  6. Connect to Cosmos DB using the Mongoose framework by adding the following code to the end of index.js.

    mongoose.connect(process.env.COSMOSDB_CONNSTR+"?ssl=true&replicaSet=globaldb", {
      auth: {
        user: process.env.COSMODDB_USER,
        password: process.env.COSMOSDB_PASSWORD
      }
    })
    .then(() => console.log('Connection to CosmosDB successful'))
    .catch((err) => console.error(err));
    

    Note

    Here, the environment variables are loaded using process.env.{variableName} using the 'dotenv' npm package.

    Once you are connected to Azure Cosmos DB, you can now start setting up object models in Mongoose.

Caveats to using Mongoose with Cosmos DB

For every model you create, Mongoose creates a new collection. However, given the per-collection billing model of Cosmos DB, it might not be the most cost-efficient way to go, if you've got multiple object models that are sparsely populated.

This walkthrough covers both models. We'll first cover the walkthrough on storing one type of data per collection. This is the defacto behavior for Mongoose.

Mongoose also has a concept called Discriminators. Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection.

You can store the various data models in the same collection and then use a filter clause at query time to pull down only the data needed.

One collection per object model

The default Mongoose behavior is to create a MongoDB collection every time you create an Object model. This section explores how to achieve this with Azure Cosmos DB's API for MongoDB. This method is recommended when you have object models with large amounts of data. This is the default operating model for Mongoose, so, you might be familiar with this, if you're familiar with Mongoose.

  1. Open your index.js again.

  2. Create the schema definition for 'Family'.

    const Family = mongoose.model('Family', new mongoose.Schema({
        lastName: String,
        parents: [{
            familyName: String,
            firstName: String,
            gender: String
        }],
        children: [{
            familyName: String,
            firstName: String,
            gender: String,
            grade: Number
        }],
        pets:[{
            givenName: String
        }],
        address: {
            country: String,
            state: String,
            city: String
        }
    }));
    
  3. Create an object for 'Family'.

    const family = new Family({
        lastName: "Volum",
        parents: [
            { firstName: "Thomas" },
            { firstName: "Mary Kay" }
        ],
        children: [
            { firstName: "Ryan", gender: "male", grade: 8 },
            { firstName: "Patrick", gender: "male", grade: 7 }
        ],
        pets: [
            { givenName: "Blackie" }
        ],
        address: { country: "USA", state: "WA", city: "Seattle" }
    });
    
  4. Finally, let's save the object to Cosmos DB. This creates a collection underneath the covers.

    family.save((err, saveFamily) => {
        console.log(JSON.stringify(saveFamily));
    });
    
  5. Now, let's create another schema and object. This time, let's create one for 'Vacation Destinations' that the families might be interested in.

    1. Just like last time, let's create the scheme

      const VacationDestinations = mongoose.model('VacationDestinations', new mongoose.Schema({
       name: String,
       country: String
      }));
      
    2. Create a sample object (You can add multiple objects to this schema) and save it.

      const vacaySpot = new VacationDestinations({
       name: "Honolulu",
       country: "USA"
      });
      
      vacaySpot.save((err, saveVacay) => {
       console.log(JSON.stringify(saveVacay));
      });
      
  6. Now, going into the Azure portal, you notice two collections created in Cosmos DB.

    Node.js tutorial - Screenshot of the Azure portal, showing an Azure Cosmos DB account, with multiple collection names highlighted - Node database

  7. Finally, let's read the data from Cosmos DB. Since we're using the default Mongoose operating model, the reads are the same as any other reads with Mongoose.

    Family.find({ 'children.gender' : "male"}, function(err, foundFamily){
        foundFamily.forEach(fam => console.log("Found Family: " + JSON.stringify(fam)));
    });
    

Using Mongoose discriminators to store data in a single collection

In this method, we use Mongoose Discriminators to help optimize for the costs of each collection. Discriminators allow you to define a differentiating 'Key', which allows you to store, differentiate and filter on different object models.

Here, we create a base object model, define a differentiating key and add 'Family' and 'VacationDestinations' as an extension to the base model.

  1. Let's set up the base config and define the discriminator key.

    const baseConfig = {
        discriminatorKey: "_type", //If you've got a lot of different data types, you could also consider setting up a secondary index here.
        collection: "alldata"   //Name of the Common Collection
    };
    
  2. Next, let's define the common object model

    const commonModel = mongoose.model('Common', new mongoose.Schema({}, baseConfig));
    
  3. We now define the 'Family' model. Notice here that we're using commonModel.discriminator instead of mongoose.model. Additionally, we're also adding the base config to the mongoose schema. So, here, the discriminatorKey is FamilyType.

    const Family_common = commonModel.discriminator('FamilyType', new     mongoose.Schema({
        lastName: String,
        parents: [{
            familyName: String,
            firstName: String,
            gender: String
        }],
        children: [{
            familyName: String,
            firstName: String,
           gender: String,
            grade: Number
        }],
        pets:[{
            givenName: String
        }],
        address: {
            country: String,
            state: String,
            city: String
        }
    }, baseConfig));
    
  4. Similarly, let's add another schema, this time for the 'VacationDestinations'. Here, the DiscriminatorKey is VacationDestinationsType.

    const Vacation_common = commonModel.discriminator('VacationDestinationsType', new mongoose.Schema({
        name: String,
        country: String
    }, baseConfig));
    
  5. Finally, let's create objects for the model and save it.

    1. Let's add object(s) to the 'Family' model.

      const family_common = new Family_common({
       lastName: "Volum",
       parents: [
           { firstName: "Thomas" },
           { firstName: "Mary Kay" }
       ],
       children: [
           { firstName: "Ryan", gender: "male", grade: 8 },
           { firstName: "Patrick", gender: "male", grade: 7 }
       ],
       pets: [
           { givenName: "Blackie" }
       ],
       address: { country: "USA", state: "WA", city: "Seattle" }
      });
      
      family_common.save((err, saveFamily) => {
       console.log("Saved: " + JSON.stringify(saveFamily));
      });
      
    2. Next, let's add object(s) to the 'VacationDestinations' model and save it.

      const vacay_common = new Vacation_common({
       name: "Honolulu",
       country: "USA"
      });
      
      vacay_common.save((err, saveVacay) => {
       console.log("Saved: " + JSON.stringify(saveVacay));
      });
      
  6. Now, if you go back to the Azure portal, you notice that you have only one collection called alldata with both 'Family' and 'VacationDestinations' data.

    Node.js tutorial - Screenshot of the Azure portal, showing an Azure Cosmos DB account, with the collection name highlighted - Node database

  7. Also, notice that each object has another attribute called as __type, which help you differentiate between the two different object models.

  8. Finally, let's read the data that is stored in Azure Cosmos DB. Mongoose takes care of filtering data based on the model. So, you have to do nothing different when reading data. Just specify your model (in this case, Family_common) and Mongoose handles filtering on the 'DiscriminatorKey'.

    Family_common.find({ 'children.gender' : "male"}, function(err, foundFamily){
        foundFamily.forEach(fam => console.log("Found Family (using discriminator): " + JSON.stringify(fam)));
    });
    

As you can see, it is easy to work with Mongoose discriminators. So, if you have an app that uses the Mongoose framework, this tutorial is a way for you to get your application up and running using Azure Cosmos's API for MongoDB without requiring too many changes.

Clean up resources

When you're done with your web app and Azure Cosmos DB account, you can delete the Azure resources you created so you don't incur more charges. To delete the resources:

  1. In the Azure portal, select Resource groups on the far left. If the left menu is collapsed, select Expand button to expand it.

  2. Select the resource group you created for this quickstart.

    Metrics in the Azure portal

  3. In the new window, select Delete resource group.

    Metrics in the Azure portal

  4. In the next window, enter the name of the resource group to delete, and then select Delete.

Next steps

  • Learn how to use Studio 3T with Azure Cosmos DB's API for MongoDB.
  • Learn how to use Robo 3T with Azure Cosmos DB's API for MongoDB.
  • Explore MongoDB samples with Azure Cosmos DB's API for MongoDB.