Access Azure CosmosDB in TypeScript

Azure Cosmos DB is a globally distributed, multi-model database hosted in Azure. It provides turn-key global distribution, elastic scaling of throughput and storage worldwide, single-digit millisecond latencies at the 99th percentile, five well-defined consistency levels, and guaranteed high availability, all backed by industry-leading SLAs. Azure Cosmos DB automatically indexes data without requiring you to deal with schema and index management.

We can either use DocumentDB API or MongoDB API to access Cosmos DB. If you would like to use CosmosDB in node.js, https://docs.microsoft.com/en-us/azure/cosmos-db/documentdb-nodejs-application will be a good article to start. In this article, I will demonstrate how to perform CosmosDB CRUD in TypeScript via DocumentDB API. TypeScript is a strict syntactical superset of JavaScript, and adds optional static typing to the language. This makes me easier to find out the syntax error instead of runtime.

Configure Node.js Environment

The following is the packages referenced in the project.json. Notice that typescript and @types/body-documentdb are located in the devDependencies and they enable us to use TypeScript in the project.

 
  "dependencies": {
    "body-parser": "^1.18.2",
    "documentdb": "^1.13.0",
    "express": "^4.16.2"
  },
  "devDependencies": {
    "@types/body-parser": "^1.16.8",
    "@types/documentdb": "^1.10.3",
    "@types/express": "^4.0.39",
    "typescript": "^2.6.1"
  }

Side by side with the project.json, we will create another file named tsconfig.json shown below. tsconfig.json is used to indicate the directory which is the root of a TypeScript project and specify the comiler options required to compile the project. For example, the following module attribute means the generated code will use commonjs as module loader and outDir attribute means dist is the output directory to store the compiled javascript files. Please check https://json.schemastore.org/tsconfig for a full specification of the compile options schema.

 
{
  "compilerOptions": {
      "module": "commonjs",
      "target": "es6",
      "noImplicitAny": true,
      "moduleResolution": "node",
      "sourceMap": true,
      "outDir": "dist",
      "baseUrl": ".",
      "paths": {
          "*": [
              "node_modules/*",
              "src/types/*"
          ]
      }
  },
  "include": [
      "./**/*"
  ]
}

In this demo, we will use express as the application framework. Let's create file app.ts shown below. Notice that we have imported itemsController from ./controllers/itemsController and also added the routing as well, we will add itemsController.js soon later.

 
import * as express from "express";
import * as bodyParser from "body-parser";
import * as itemsController from './controllers/itemsController';

const app = express();

app.set("port", process.env.PORT || 3000);
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.get('/', itemsController.getItems);
app.get('/items/:id', itemsController.getItem);
app.post('/items', itemsController.addItem);
app.put('/items/:id', itemsController.updateItem);
app.delete('/items/:id', itemsController.deleteItem);

const port = process.env.port || 3000;
app.listen(app.get("port"),() => {
    console.log('Server Started');
} );

Specify DB authKey

After we create the Cosmos DB in Azure, we will be able to get the authKey in the Keys blade shown below.

Let's copy the host and authKey, create a file named dbConfig.ts and paste then.

 

let cosmosDbConfig: any = {};

cosmosDbConfig.host = process.env.HOST || ;
cosmosDbConfig.authKey = process.env.AUTH_KEY || ;

export {cosmosDbConfig};


CRUD by DocumentDB API

All resources within Azure Cosmos DB are modeled and stored as JSON documents. Resources are managed as items, which are JSON documents containing metadata, and as feeds which are collections of items. A database account consists of a set of databases, each containing multiple collections, each of which can contain stored procedures, triggers, UDFs, documents, and related attachments. A database also has associated users, each with a set of permissions to access various other collections, stored procedures, triggers shown below.

Azure Cosmos DB supports both of DocumentDB API and MongoDB API, The DocumentDB API offers a simple HTTP based RESTful programming model for all resources and I will use it to perform CRUD in this demo. The following is the source code of dbRepository.ts, notice that all the required DocumentDB API comes from the imported module documentdb shown below. For a full specification of Document API, please check azure.github.io/azure-documentdb-node/ .

 
import {DocumentClient, CollectionMeta, DocumentQuery, RetrievedDocument} from "documentdb";
import {cosmosDbConfig} from './dbConfig';

export type DocumentsCallback = (err: number, docs: RetrievedDocument[])=> void;
export type DocumentCallback = (err: number, doc: RetrievedDocument)=> void;

class DbRepository{
    docClient: DocumentClient;
    collection: CollectionMeta;
    constructor(docClient: DocumentClient){
        this.docClient = docClient;
    }

    init(){
        let dbQuery: any = {
            query: 'SELECT * FROM root r WHERE r.id= @id',
            parameters: [{
                name: '@id',
                value: 'ToDoList'
            }]
        };

        this.docClient.queryDatabases(dbQuery).toArray((err, dbs) => {
            let db = dbs[0];
            const colQuery: DocumentQuery = {
                query: 'SELECT * FROM root r WHERE r.id=@id',
                parameters: [{
                    name: '@id',
                    value: 'Items'
                }]
            }; 

            this.docClient.queryCollections(db._self, colQuery).toArray((err, collections) => {
                this.collection = collections[0];
                console.log('initialization finished');
            });
        });
    }

    getItems(callback:DocumentsCallback){
        let querySpec: DocumentQuery  = {
            query: 'SELECT * FROM root',
            parameters: []
        };

        this.docClient.queryDocuments(this.collection._self, querySpec).toArray((err, results) => {
            if(null == err){
                callback(0, results);
            }else{
                callback(err.code, null);
            }
        });
    }

    getItem(id: string, callback: DocumentCallback){
        let querySpec: DocumentQuery = {
            query: 'SELECT * FROM root r WHERE r.id = @id',
            parameters: [{
                name: '@id',
                value: id
            }]
        };

        this.docClient.queryDocuments(this.collection._self, querySpec).toArray((err, results) => {
            if (null == err && results.length > 0) {
                callback(0, results[0]);
            } else {
                callback(404, null);
            }
        });
    }

    updateItem(id: string, target: any, callback: DocumentCallback){
        this.getItem(id, (err, doc) => {
            if(0 == err){
                doc.title = target.title;
                doc.description = target.description;
                this.docClient.replaceDocument(doc._self, doc, (err, result) => {
                    callback(0, result);
                });
            }else{
                callback(404, null);
            }
        });
    }

    deleteItem(id: string, callback: DocumentCallback){
        this.getItem(id, (err, doc) => {
            if(0 == err){
                this.docClient.deleteDocument(doc._self, (err) => {
                    callback(0, null);
                });
            }else{
                callback(404, null);
            }
        });
    }

    addItem(item: any, callback: DocumentCallback){
        this.docClient.createDocument(this.collection._self, item, (err, result) => {
            if(null == err){
                callback(0, result);
            }else{
                callback(err.code, null);
            }
        });
    }
}

let docDbClient = new DocumentClient(cosmosDbConfig.host, {
    masterKey: cosmosDbConfig.authKey
});

let cosmosDb = new DbRepository(docDbClient);
cosmosDb.init();

export {cosmosDb};

Add Controller

Lastly, let's create a folder named controllers and file itemsController.ts as well. itemsController accepts all the user requests and then perform database operations.

 
import {Request, Response} from "express";
import {cosmosDb} from '../dbRepository';

export let getItems = (req: Request, res: Response) => {
    cosmosDb.getItems((err, results)=>{
        if(err > 0){
            res.sendStatus(err);
        }else{
            res.status(200).json(results);
        }
    });   
};

export let getItem = (req: Request, res: Response) => {
    cosmosDb.getItem(req.params.id, (err, result) => {
        if(err > 0){
            res.sendStatus(err);
        }else{
            res.status(200).json(result);
        }
    });
};

export let addItem = (req: Request, res: Response) => {
    cosmosDb.addItem(req.body, (err, result) => {
        if(err > 0){
            res.sendStatus(err);
        }else{
            res.status(201).json(result);
        }
    });
};

export let updateItem = (req: Request, res: Response) => {
    cosmosDb.updateItem(req.params.id, req.body, (err, result) => {
        if(err > 0){
            res.sendStatus(err);
        }else{
            res.status(200).json(result);
        }
    });
};

export let deleteItem = (req: Request, res: Response) => {
    cosmosDb.deleteItem(req.params.id, (err, result) => {
        if(err > 0){
            res.sendStatus(err);
        }else{
            res.sendStatus(204);
        }
    });
};

Now, we can invoke command "tsc" to compile the application, the compiled out javascript will be located inside dist folder which is specified in the tsconfig.json file. Then, invoke command "node app.js" to run the application. As you can see, it is so easy to achieve the all and hope you like Azure Cosmos DB and TypeScript as well.