Node.js Cookbook

Getting Started

To get started with the Node.js driver, please visit the Cloud CMS Node.js Driver Page.

One thing to keep in mind is that the Node.js driver is based on the JavaScript driver. As such, they're pretty similar.
That said, the Node.js driver can do a few important tricks that you can't do in the JavaScript driver.

Connecting to Cloud CMS

To connect, create a file called gitana.json in your application root. For information on how to acquire this file,
please read up on Acquiring your API Keys.

You can then simply do:

Gitana.connect(function(err) {
  
    var platform = this;
    
});

Alternatively, you can pass in your API keys as an argument to the connect() method.

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com"
}, function(err) {
  
    var platform = this;
    
});

If a problem was encountered during the authentication, the err variable will be populated. If not, the this variable
will the Platform instance you can use to start doing interesting things!

If you supply an application argument in your API Keys, you'll get back a helper object that you can use to access
all of the resources on your application's associated Stack.

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com"
}, function(err) {
  
    var helper = this;
    
    var repository = helper.datastore("content");    
    
});

Asynchronous Chaining

The driver is unique and unusual in that it offers a Chaining based solution to making asynchronous
series of API calls. It's similar to Promises (but pre-dates them!) and makes it possible to make a series of API calls without
having to write as much code.

For example, with this driver, you can write the following:

platform.readRepository("1234567890abcdef1234").readBranch("master").then(function() {
    this.createNode({"title": "Article #1"});
    this.createNode({"title": "Article #2"});
    this.createNode({"title": "Article #3"});
});

This might look similar to Promises but it has a few differences. One important difference is that the then() method
modifies the this to be the result. This allows you to chain calls as shown above, or even like this:

platform.readRepository("1234567890abcdef1234").readBranch("master").createNode({"title": "Article #1"});

Which is effectively the same, in concept, as a callback approach where you might do something like:

readRepository(platform, function(err, repository) {

    if (err) {
        return handleError(err);
    }
    
    readBranch(repository, "master", function(err, branch) {
    
        if (err) {
            return handleError(err);
        }
    
        createNode(branch, {"title": "Article #1"}, function(err, node) {
        
            if (err) {
                return handleError(err);
            }
            
            // yay I made it!
        
        });
    })'
});

Not so with our driver. You can keep your code lean and clean. You just need to be mindful of the
changes in scope (the this variable). Which is unique. But there it is.

Code Samples

Here are some code samples to help you get started.

In these examples, we'll assume that you're connecting to Cloud CMS using API keys that either provide the application
value or they do not. The examples below show an essentially skeleton for a JS application that uses the driver for both
cases.

In all of the examples that follow, assume that your code gets inserted into the TODO section.

In Cloud CMS, you can have as many repositories as you'd like. And each repository can multiple branches (similar to Git).
The first thing you should do is connect and get the branch that you want to work on.

For cases where the repository and branch must be explicitly referenced, we'll assume that the repository
ID is 1234567890abcdef1234 and the branch ID is master.

API Keys with application

Gitana.connect(function(err) {

    if (err) {
        return console.log("Failed to connect", err);
    }

    var helper = this;
    
    var repository = helper.datastore("content");
        
    repository.readBranch("master").then(function() {
        var branch = this;
        
        // TODO: add your custom code here
    });    
    
});

Standard API Keys (without application)

Use the readRepository method to fetch a specific repository. The user you've logged in as must have READ permissions
to the repository. If they do not have read rights, a null response is expected.

Use the readBranch method to fetch a specific branch. The user you've logged in as must have READ permissions
to the branch. If they do not have read rights, a null response is expected.

Gitana.connect(function(err) {

    if (err) {
        return console.log("Failed to connect", err);
    }

    var platform = this;
        
    platform.readRepository("1234567890abcdef1234").then(function() {
        var repository = this;
        
        this.readBranch("master").then(function() {
            var branch = this;
            
            // TODO: add your custom code here
        });    
    });
    
});

Node Creation

Create a Node

Create a basic node of type n:node (the default type).

var branch = this;

branch.createNode({
    "title": "My First Node"
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

Create a Node for a Custom Content Type

Suppose we have a custom content type called custom:article that looks like this:

{
    "title": "Custom Article",
    "_qname": "custom:article",
    "type": "object",
    "properties": {
        "title": {
            "type": "string"
        },
        "body": {
            "type": "string"
        },
        "categories": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "rating": {
            "type": "number"
        }
    }
}

Create a content instance of this type like this:

var branch = this;

branch.createNode({
    "_type": "custom:article",
    "title": "Article #1",
    "body": "A still more glorious dawn awaits",
    "categories": ["category1", "category2", "category3"],
    "rating": 5            
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

Create a Folder

In Cloud CMS, folders are just nodes with the f:container feature on them.

var branch = this;

branch.createNode({
    "title": "My Folder",
    "_features": {
        "f:container": {}
    }
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

If you want your folder to appear in the general folder hierarchy, you must link it to a parent folder that is already
a member of the folder hierarchy. The main folder hierarchy descends from the branch root node.

Here is the long way of doing this:

var branch = this;

branch.rootNode().then(function() {

    var rootNode = this;

    Chain(branch).createNode({
       "title": "My Folder",
       "_features": {
           "f:container": {}
       }
    }).then(function() {
        var folder = this;
        
        Chain(rootNode).associate(folder, {
            "_type": "a:child"
        }).then(function() {
            // done!
        });
    });
    
});   

An easier way to do this is to use the following special properties when the node is created:

  • _parentFolderPath - the path to the parent folder where the created node should be linked. The folder must exist. If it does not exist, the operation will fail.
  • _fileName - the f:filename to be assigned to the node once it is created.

Alternatively, you can use:

  • _filePath - an absolute path where the file should be created. This allows you to specify the parent folder path and the file name at the same time,.

You can also adjust the association type (between the discovered parent folder and your newly created item).

  • _associationTypeString - the QName for the newly created association. The default is a:child.

Thus, you could do:

var branch = this;

branch.createNode({
   "title": "My Folder",
   "_features": {
       "f:container": {}
   },
   "_parentFolderPath": "/",
   "_fileName": "my_folder"
}).then(function() {
    var folder = this;        
});

Or you could do:

var branch = this;

branch.createNode({
   "title": "My Folder",
   "_features": {
       "f:container": {}
   },
   "_parentFolderPath": "/",
   "_filePath": "/my_folder"
}).then(function() {
    var folder = this;        
});

Associations

In Cloud CMS, any two nodes can be connected via an association. You can use an out-of-the-box association type, such
as a:linked or a:owned or you can create your own.

Let's imagine that we have two articles that look like this:

  • Article #1
{
    "_doc": "1234567890abcdef1111",
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_doc": "1234567890abcdef2222",
    "_doc": "",
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}

Let's create an a:linked association that points from the first article (1234567890abcdef1111) to the second article
(1234567890abcdef2222), like this:

var branch = this;

branch.associate("1234567890abcdef1111", "1234567890abcdef2222", "a:linked').then(function() {
    var association = this;
});

Or we can do this using the nodes themselves:

var branch = this;

branch.readNode("1234567890abcdef1111").then(function() {
    var article1 = this;
    
    Chain(branch).readNode("1234567890abcdef2222").then(function() {
        var article2 = this;
        
        Chain(article1).associate(article2, "a:linked").then(function() {        
            var association = this;
        });    
    });
});   

You can also find all associations around article1. This will include associations that are INCOMING, OUTGOING
and MUTUAL.

article1.associations().each(function() {
    console.log("Found association: " + this._doc);
});

Or find only the INCOMING associations:

article1.associations({
    "direction": "incoming"
}).each(function() {
    console.log("Found incoming association: " + this._doc);
});

Or find only the OUTGOING associations of type a:linked:

article1.associations({
    "direction": "outgoing",
    "type": "a:linked"
}).each(function() {
    console.log("Found outgoing linked association: " + this._doc);
});

Query for Nodes

Let's now look at querying for content. This makes use of MongoDB's query language to express some pretty powerful
queries. We recommend further reading on Query within Cloud CMS.

Let's assume that we have the following:

  • Article #1
{
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}
  • Article 3
{
    "_type": "custom:article",
    "title": "Article #3",
    "body": "we are star stuff",
    "categories": ["category3", "category1"],
    "rating": 3
}

Basic Querying

Find all of the articles that have a category with the value category1.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}).count(function(c) {
    console.log("The size was: " + c);
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
}).then(function() {
    // done!
});

Pagination

If you have a lot of potential query hits, you'll want to paginate. Here is an example where we return results starting
at index 2 and hand back at most 5 results.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}, {
    "skip": 2,
    "limit": 5    
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
}).then(function() {
    console.log("The size was: " + this.size()); // 1
    console.log("The offset was: " + this.offset()); // 2
    console.log("The totalRows was: " + this.totalRows()); // 3
});

Sorting

Use the pagination option to sort the results as you see fit. Here we sort descending on rating.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}, {
    "sort": {
        "rating": -1
    }   
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

The results will look like:

Found a result, title: Article #3, rating: 3
Found a result, title: Article #2, rating: 2
Found a result, title: Article #1, rating: 1

Query for values in a range

Find all of the articles where the rating is greater than or equal to 2.
This demonstrates the power of MongoDB's query language.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "rating": {
        "$gte": 2
    }
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

Search for Nodes

Let's now look at searching for content. This makes use of Elastic Search's DSL to express some very powerful
full text and structured searches. We recommend further reading on Search within Cloud CMS.

We also suggest reading up on Query Strings to understand how you can write searches textually (in addition to structuring them as JSON objects):

Let's assume that we have the following:

  • Article #1
{
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}
  • Article 3
{
    "_type": "custom:article",
    "title": "Article #3",
    "body": "we are star stuff",
    "categories": ["category3", "category1"],
    "rating": 3
}

Full Text Search

Find all of the nodes with the word star. Simple enough!

var branch = this;

branch.searchNodes("star").each(function() {    
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

The results will be:

Found a result, title: Article #2, body: harvesting star light
Found a result, title: Article #3, body: we are star stuff

Node Attachments

You can store binary files onto Nodes as attachments.

Note: If you're working with the JavaScript driver in the browser, there are some limitations in terms of the kinds of
payloads you can send to the Cloud CMS API. This is because the browser does not let you stream files from local disk.
You are limited to the kinds of content that can be expressed within JavaScript itself (usually text, JSON and similar
formats).

Attachments have the following methods:

  • getId() returns the ID of the attachment
  • getLength() returns the length of the attachment in bytes
  • getContentType() returns the mimetype of the attachment
  • getFilename() returns the optional filename of the attachment
  • getUri() returns a datastore-relative path to the attachment
  • getDownloadUri() returns a driver baseURI-relative path to the attachment
  • del() deletes the attachment
  • download() downloads the attachment and hands it back to you. The exact format depends on the mimetype. For text files, this will be a string. For JSON documents, this will be a JavaScript object literal. Otherwise, it will be a byte array.
  • stream() streams back the attachment (only available in Node.js)

Listing Attachments of a Node

To list the attachments of a Node, do the following:

var node = ...;

node.listAttachments().each(function() {
   console.log("The node: " + node._doc + " has an attachment: " + this.getId() + " (mimetype = " + this.getContentType() + ", length: " + this.getLength() + ")");
});

Uploading an Attachment to a Node

To upload an attachment, do the following:

var node = ...;

node.attach("default", "text/plain", "Hello World", "helloworld.txt");

Downloading an Attachment of a Node

To download an attachment:

var node = ...;

node.attachment("default").download(function(data) {
   console.log("Downloaded: " + data);
});

Attachments as Streams

To upload an attachment using a stream:

const fs = require("fs");
var readStream = fs.createReadStream("./images/photo.png");

var node = ...;

node.attach("default", "image/png", readStream, "photo.png").then(function() {
   console.log("Attachment created");
});

To read a stream for an attachment, try this:

const fs = require("fs");

var node = ...;

node.attachment("default").stream(function(err, readStream) {
   var writeStream = fs.createWriteStream("./downloads/photo.png");
   readStream.pipe(writeStream);
   
   console.log("Attachment downloaded");
});

Note that stream support is only available when running inside of Node.js.