A newer version of this documentation is available.

View Latest

Full Text Search (FTS) using the Node.js SDK with Couchbase Server

Using the Node.js SDK, you can use the Couchbase Full Text Search service (FTS) to create queryable full-text indexes in Couchbase Server.

Couchbase offers Full-text search support, allowing you to search for documents that contain certain words or phrases. A general introduction to FTS, with pointers to detailed descriptions of its principal features, is provided in Full Text Search: Fundamentals. In the Node.js SDK you can search full-text indexes by using the SearchQuery object and executing it with the Bucket.query() API.

The following example shows how to send a simple Search query:

var SearchQuery = couchbase.SearchQuery;
var query = SearchQuery.new('travel-search', SearchQuery.term('office'));
bucket.query(query, function(err, res, meta) {
  for (var i = 0; i < res.length; ++i) {
    console.log('Hit:', res[i].id);
  }
});

The Bucket.query() method returns a SearchQueryResponse object which provides access to all of the information returned by the query.

Other search result data may be accessed using the various other available methods on the SearchResults interface.

bucket.query(…, function(err, res, meta) {
  console.log('Total Hits:', meta.status.total);
  for (var i in meta.facets) {
    console.log('Facet:', i);
  }
});

Query Types

Query types may be found within the SearchQuery class. The package contains query classes corresponding to those enumerated in Query Types. The query object should be instantiated by using the associated function matching the type of query you wish to create (i.e.: SearchQuery.term() for a TermQuery), passing the search term (usually a string) as the first argument, followed by some query modifiers.

It is important to distinguish between query options and general search options. Some options affect the search process in general (such as the Limit, indicating how many results to return) while others only affect a specific query (such as Fuzziness for a given query). Because multiple queries can be combined in a single search operation, query specific options can be specified only in the query object itself, while search options are specified through methods on the SearchQuery object itself.

Query Facets

Query facets may also be added to the general search parameters by using the addFacet method. The addFacet method accepts a facet name as well as a facet definition You can create facets by instantiating a Facet object using methods found in the SearchFacet class (i.e.: SearchFacet.term() for a TermFacet).

var query = SearchQuery.new('travel-search', SearchQuery.term('office'));
query.addFacet('countries', SearchFacet.term('country', 5));
bucket.query(query, function(err, res, meta) {
  console.log('Total Countries:', meta.facets['countries'].total);
});

Detailed Examples

The code example below demonstrates the Node.js SDK Full Text Search API. The example assumes that Couchbase Server is running, and that the username Administrator and the password password provide authorization for performing the searches. It also assumes that the travel-sample bucket has been installed. For information on creating users and managing roles, see 6.0@server:security:security-authorization.adoc. For information on installing sample buckets, see 6.0@server:settings:settings.adoc.

The example also assumes the existence of three specific Full Text Indexes, defined on the travel-sample bucket. These are:

  • travel-sample-index-unstored: Uses only the default settings.

  • travel-sample-index-stored: Uses default settings, with one exception: dynamic fields are stored, for the whole index.

  • travel-sample-index-hotel-description: Indexes only the description fields of hotel documents, and disables the default type mapping. The index has a custom analyzer named myUnicodeAnalyzer defined on it: the analyzer’s main characteristic is that it uses the unicode tokenizer.

See Creating Indexes for details on how to create these indexes: they can be created interactively, by means of the Couchbase Web Console; however, there may be greater efficiency in using the Couchbase REST API, as described in the section Index Creation with the REST API. The JSON objects that constitute index-definitions (for inclusion as bodies to the index-creation REST calls), are provided in Demonstration Indexes.

The example features the following Full Text Searches on the travel-sample bucket, within Couchbase Server:

  • Simple Text Query on a single word, targeting an index with dynamic fields unstored.

  • Simple Text Query on Non-Default Index, specifying an index that consists only of content derived from a specific field from a specific document-type.

  • Simple Text Query on Stored Field, specifying the field to be searched; targeting an index with dynamic fields stored, to ensure that field-content is included in the return object.

  • Match Query with Facet, showing how query-results can be displayed either by row or by hits; and demonstrating use of a facet, which provides aggregation-data.

  • DocId Query, showing results of a query on two document IDs.

  • Unanalyzed Term Query with Fuzziness Level of 0, demonstrating how to query on a term with no analysis. Zero fuzziness is specified, to ensure that matches are exact.

  • Unanalyzed Term Query with Fuzziness Level of 2, which is almost identical to the immediately preceding query; but which this time specifies a fuzziness factor of 2, allowing partial matches to be made. The output from this query can be compared to that of the one immediately preceding.

  • Match Phrase Query, using Analysis, for searching on a phrase.

  • Phrase Query, without Analysis, for searching on a phrase without analysis supported.

  • Query String Query, showing how a query string is specified as search-input.

  • Conjunction Query, whereby two separate queries are defined and then run as part of the search, with only the matches returned by both included in the result-object.

  • Wild Card Query, whereby a wildcard is used in the string submitted for the search.

  • Numeric Range Query, whereby minimum and maximum numbers are specified, and matches within the range returned.

  • Regexp Query, whereby a regular expression is submitted, to generate the conditions for successful matches.

'use strict';

var couchbase = require('couchbase');

var cluster = new couchbase.Cluster('couchbase://10.111.181.101');
cluster.authenticate('Administrator', 'password');
var travelSample = cluster.openBucket('travel-sample');
var SearchQuery = couchbase.SearchQuery;
var SearchFacet = couchbase.SearchFacet;

function simpleTextQuery(bucket, done) {
    var indexName = 'travel-sample-index-unstored';
    var match = SearchQuery.match('swanky');
    var query = SearchQuery.new(indexName, match).limit(10);
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Simple Text Query Error:', err);
            done();
            return;
        }
        printResult('Simple Text Query:', res);
        done();
    });
}

function simpleTextQueryOnStoredField(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var match = SearchQuery.match('MDG').field('destinationairport');
    var query = SearchQuery.new(indexName, match).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Simple Text Query on Stored Field Error:', err);
            done();
            return;
        }
        printResult('Simple Text Query on Stored Field:', res);
        done();
    });
}

function simpleTextQueryOnNonDefaultIndex(bucket, done) {
    var indexName = 'travel-sample-index-hotel-description';
    var match = SearchQuery.match('swanky');
    var query = SearchQuery.new(indexName, match).limit(10);
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Simple Text Query on Non-Default Index Error:', err);
            done();
            return;
        }
        printResult('Simple Text Query on Non-Default Index:', res);
        done();
    });
}

function textQueryOnStoredFieldWithFacet(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var match = SearchQuery.match('La Rue Saint Denis!!').field('reviews.content');
    var query = SearchQuery.new(indexName, match).limit(10).highlight()
        .addFacet('Countries Referenced', SearchFacet.term('country', 5));
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Match Query with Facet Error:', err);
            done();
            return;
        }
        console.log();
        console.log('Match Query with Facet, Result by hits:');
        for (var i = 0; i < res.length; ++i) {
            console.log(JSON.stringify(res[i]));
        }

        console.log();
        console.log('Match Query with Facet, Result by facets:');
        for (var facet in meta.facets) {
            console.log(facet, ':', JSON.stringify(meta.facets[facet]));
        }
        done();
    });
}

function docIdQueryMethod(bucket, done) {
    var indexName = 'travel-sample-index-unstored';
    var query = SearchQuery.new(indexName, SearchQuery.docIds('hotel_26223', 'hotel_28960'));
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('DocId Query Error:', err);
            done();
            return;
        }
        printResult('DocId Query:', res);
        done();
    });
}

function unAnalyzedTermQuery(bucket, fuzzinessLevel, done) {
    var indexName = 'travel-sample-index-stored';
    var term = SearchQuery.term('sushi').field('reviews.content')
        .fuzziness(fuzzinessLevel);
    var query = SearchQuery.new(indexName, term).limit(50).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Unanalyzed Term Query with Fuzziness Level of', fuzzinessLevel, 'Error:', err);
            done();
            return;
        }
        printResult('Unanalyzed Term Query with Fuzziness Level of ' + fuzzinessLevel + ':', res);
        done();
    });
}

function matchPhraseQueryOnStoredField(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var match = SearchQuery.matchPhrase('Eiffel Tower')
        .field('description');

    var query = SearchQuery.new(indexName, match).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Match Phrase Query, using Analysis Error:', err);
            done();
            return;
        }
        printResult('Match Phrase Query, using Analysis:', res);
        done();
    });
}

function unAnalyzedPhraseQuery(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var phrase = SearchQuery.phrase(['dorm', 'rooms'])
        .field('description');

    var query = SearchQuery.new(indexName, phrase).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Phrase Query, without Analysis Error:', err);
            done();
            return;
        }
        printResult('Phrase Query, without Analysis:', res);
        done();
    });
}

function conjunctionQueryMethod(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var firstQuery = SearchQuery.match('La Rue Saint Denis!!')
        .field('reviews.content');
    var secondQuery = SearchQuery.match('boutique')
        .field('description');

    var conjunctionQuery = SearchQuery.conjuncts(firstQuery, secondQuery);

    var query = SearchQuery.new(indexName, conjunctionQuery).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Conjunction Query Error:', err);
            done();
            return;
        }
        printResult('Conjunction Query:', res);
        done();
    });
}

function queryStringMethod(bucket, done) {
    var indexName = 'travel-sample-index-unstored';
    var queryString = SearchQuery.queryString('description: Imperial');
    var query = SearchQuery.new(indexName, queryString).limit(10);
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Query String Query Error:', err);
            done();
            return;
        }
        printResult('Query String Query:', res);
        done();
    });
}

function wildCardQueryMethod(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var wildcard = SearchQuery.wildcard('bouti*ue').field('description');
    var query = SearchQuery.new(indexName, wildcard).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Wild Card Query Error:', err);
            done();
            return;
        }
        printResult('Wild Card Query:', res);
        done();
    });
}

function regexpQueryMethod(bucket, done) {
    var indexName = 'travel-sample-index-stored';
    var reg = SearchQuery.regexp('[a-z]').field('description');
    var query = SearchQuery.new(indexName, reg).limit(10).highlight();
    bucket.query(query, function(err, res, meta) {
        if (err) {
            console.log();
            console.log('Regexp Query Error:', err);
            done();
            return;
        }
        printResult('Regexp Query:', res);
        done();
    });
}

function printResult(label, result) {
    console.log();
    console.log('= = = = = = = = = = = = = = = = = = = = = = =');
    console.log('= = = = = = = = = = = = = = = = = = = = = = =');
    console.log();
    console.log(label);
    console.log();

    for (var i = 0; i < result.length; ++i) {
        console.log(JSON.stringify(result[i]));
    }
}

function querySet1(bucket, done) {
    simpleTextQuery(bucket, function() {
        simpleTextQueryOnStoredField(bucket, function() {
            simpleTextQueryOnNonDefaultIndex(bucket, function() {
                textQueryOnStoredFieldWithFacet(bucket, done);
            });
        });
    });
}

function querySet2(bucket, done) {
    docIdQueryMethod(bucket, function() {
        unAnalyzedTermQuery(bucket, 0, function() {
            unAnalyzedTermQuery(bucket, 2, function() {
                matchPhraseQueryOnStoredField(bucket, function() {
                    unAnalyzedPhraseQuery(bucket, done);
                });
            });
        });
    });
}

function querySet3(bucket, done) {
    conjunctionQueryMethod(bucket, function() {
        queryStringMethod(bucket, function() {
            wildCardQueryMethod(bucket, function() {
                regexpQueryMethod(bucket, done);
            });
        });
    });
}

querySet1(travelSample, function() {
    querySet2(travelSample, function() {
        querySet3(travelSample, function() {
            travelSample.disconnect();
            process.exit(0);
        });
    });
              });