Document Operations
Edit this article in GitHub
Version 2.3

Document Operations

You can access documents in Couchbase using the Bucket class (constructed via cluster.openBucket()). You can retrieve documents using the get() and lookupIn methods, and mutate documents using the upsert(), insert(), replace(), and mutateIn() methods.

Document Input Types

Documents can be anything convertible to JSON (JSON.stringify). Simply pass the object or array to the mutation operation, and the SDK will convert it to JSON and store it on the server. Do not pass already-serialized JSON to the SDK, as the SDK will serialize it again.

Callbacks for operations

Due to the non-blocking nature of the node.js SDK and node.js in general, the results of all operations are signalled by callbacks. Callbacks are specified as the last parameter to any operation. The callback signature is generally
function(err, result) {
    if (err) {
        console.error("Got error!: %j", err);
    } else {
        console.log("Got result: %j", result);
    }
}

Additional Options

Additional options may be passed to some methods using an object.

Mutation operations (upsert, insert, replace, mutate_in) operations also accept a TTL (expiry) value (expiry) which will instruct the server to delete the document after a given amount of time. This option is useful for transient data (such as sessions). By default documents do not expire. See Expiration Overview for more information on expiration.

Some mutation operations can also accept a CAS (cas) value to protect against concurrent updates to the same document. See CAS for a description on how to use CAS values in your application. Note that the CAS value is represented internally as Buffer.

Creating and updating full documents

Documents may be created and updated using the Bucket.upsert(), Bucket.insert(), and Bucket.replace() family of methods. Read more about the difference between these methods at Primitive Key-Value Operations in the Couchbase developer guide.
These methods accept two mandatory arguments:
  • key: The ID of the document to modify. This should be a string.
  • value: The desired new value of the document. This may be anything that can be serialized as JSON.
Additional options can be specified in the options:
  • cas: The CAS value for the document. If the CAS on the server does not match the CAS supplied to the method, the operation will fail with a couchbase.exceptions.KeyExistsError error. See Concurrent Document Mutations for more information on the usage of CAS values.
  • expiry: Specify the expiry time for the document. If specified, the document will expire and no longer exist after the given number of seconds. See ../core-operations.html#expiry for more information.
  • persist_to, replicate_to: Specify durability requirements for the operations.

When the operation is complete the callback will be invoked, and if successful, the result (the second callback argument) will contain the new CAS value of the document. If the document was not mutated successfully, the callback's first argument (err) will contain appropriate error information. See Handling Exceptions and Other Errors with the Node.js SDK in Couchbase for more information on error types and how to handle them.

bucket.insert('document_name', {'some': 'value'}, function(err, result) {
    if (!err) {
        console.log("stored document successfully. CAS is %j", result.cas);
    } else {
        console.error("Couldn't store document: %j", err);
    }
});
Output on success:
stored document successfully. CAS is "216000820675072"
Couldn't store document: {"message":"The key already exists in the server. If you have supplied a CAS then the key exists with a CAS value different than specified","code":12}

If the document being inserted already exists, the error will be set and its code field will be set to couchbase.errors.keyAlreadyExists. If your application simply wants to set the value ignoring whether it exists or not, use the upsert() method.

Retrieving full documents

Documents may be retrieved using the Bucket.get() method. The get() method requires the document ID to retrieve, and the callback to invoke with the result of the operation.

When the operation is complete the callback is invoked. If successful, you may access actual document may be access by using result.value and the CAS using result.cas.

The document will be a native javascript type resulting from the parsing of the stored server value.

Upon failure, the error (first callback argument) will be populate with an appropriate code and message. If the document does not exist, the code will be couchbase.errors.keyNotFound
bucket.get('document_id', function(err, result) {
    if (err) {
        if (err.code == couchbase.errors.keyNotFound) {
            console.log('Key does not exist');
        } else {
            console.log('Some other error occurred: %j', err);
        }
    } else {
        console.log('Retrieved document with value: %j', result.value);
        console.log('CAS is %j', result.cas);
    }
});

Sample output:

Retrieved document with value: ["Hello","World"]
CAS is "166079006573056"

Removing full documents

Documents may be removed using the Bucket.remove() method. This method takes the document ID as a mandatory positional:
  • key: The ID of the document to remove
Some additional options:
  • cas: Only remove the document if the CAS has not changed.

Modifying expiraton

Document expiration can be performed using the Bucket.touch() and Bucket.getAndTouch methods. You can also set the expiry parameter for methods which support it:
expiry with upsert
bucket.upsert('deleteMe', {'delete': 'me'}, {'expiry': 1}, function(err){
    bucket.get('deleteMe', function(err, result) {
        console.log('Have item: %j', result.value);
        console.log('Will attempt get later on..');
        setTimeout(function(){
            bucket.get('deleteMe', function(err, result){
                console.log('Got error: %s', err.message);
            })
        }, 3500);
    })
});
Have item: {"delete":"me"}
Will attempt get later on..
Got error: The key does not exist on the server
Using getAndTouch
bucket.getAndTouch('some_document', 2, function(err, result) {
    console.log('Got value: %j', result.value);
    console.log('Will try again..');
    setTimeout(function(err, result){
        bucket.get('some_document', function(err, result) {
            console.error('Got error: %s', err.message);
        });
    }, 3000);
});

Atomic document modifications

Additional atomic document modifications can be performing using the node.js SDK. You can modify a counter document using the Bucket.counter() method. You can also use the Bucket.append and Bucket.prepend methods to perform raw byte concatenation

Batching Operations

All operations performed in the node.js SDK are inherently batched between event loop iterations. In simple terms, this means that there is no added network performance for using any form of multi operation.

As a convenience, the node.js SDK provides an Bucket.getMulti which accepts multiple keys and returns their results.

Operating with sub-documents

Tip: Sub-Document API is available starting Couchbase Server version 4.5. See Sub-Document Operations for an overview.

Sub-document operations save network bandwidth by allowing you to specify paths of a document to be retrieved or updated. The document is parsed on the server and only the relevant sections (indicated by paths) are transferred between client and server. You can execute sub-document operations in the node.js SDK using the Bucket.mutateIn, and Bucket.lookupIn methods.

Each of these methods accepts a key as its mandatory first argument and returns a builder object which can be used to add one or more command specifications specifying a specifying an operation and a document field operand. To submit the operation, invoke the execute() method on the builder, passing it the callback to be invoked.
bucket.lookupIn('some_document').
    get('path.to.get').
    exists('check.path.exists').
    execute(function(err, fragment) {
    // ...
});

The callback function is invoked with an error and a fragment. The fragment is a DocumentFragment object containing the fragments of the document (corresponding to each path). In contrast with a normal callback which returns a single error and result, each operation in the builder has its own result and status. You can use fragment.contentByIndex or fragment.content(path) or fragment.contentByIndex(index) to retrieve the result for an individual path/command. If a path has an error, the SDK will throw an exception.

Unlike other operations as well, both the error and fragment can be set. If the error is set, and error.code is couchbase.errors.checkResults then it means that at least one command has failed. If the top-level error code is something else, it means that the document itself could not be accessed.
bucket.lookupIn('some_document').
    get('path.to.get').
    exists('check.path.exists').
    execute(function(err, result) {
    if (!err || err.code == couchbase.errors.checkResults) {
        for (var i = 0; i < 2; i++) {
            try {
                console.log('Result for path index %d: %j', i, result.contentByIndex(i));
            } catch (e) {
                console.error('Error for path index %d: %s', i, e.message);
            }
        }
    } else {
        console.error("Couldn't access document: %s", err.message);
    }
});
You can also mutate documents in a similar manner:
bucket.mutateIn('some_document').
    upsert('path.to.upsert', 'value', true).
    remove('path.to.del').
    execute(function(err, frag) {
    if (!err || err.code == couchbase.errors.checkResults) {
        for (var i = 0; i < 2; i++) {
            try {
                frag.contentByIndex(i);
            } catch (e) {
                console.error('Error for index %d: %s', i, e.message);
            }
        }
    } else {
        console.error('Top-level document error: %j', err)
    }
});

Formats and Non-JSON Documents

Tip: See Non-JSON Documents for a general overview of using non-JSON documents with Couchbase
All JavaScript types which can be represented as JSON may be passed unmodified to a storage function, and be received via the get method without any additional modifications.

You can implement a custom transcoder if you would like to serialize documents to/from a format other than JSON, or if you would like to interact with other SDKs which use non-JSON serialization formats. See Bucket.setTranscoder().