Node + MongoDB + Elasticsearch

By June 24, 2019MongoDB
Node mongodb ( Blog)

In this article I am trying to highlight the steps required to implement text search using Elasticsearch in a MEAN application. These are the things we did in our MEAN application for implementing full text search functionality.

Install mongoosastic –

Mongoosastic is a mongoose plugin that can automatically index your models into elasticsearch. The latest version of mongoosastic plugin will be as close as possible to the latest elasticsearch and mongoose packages.

npm install -S mongoosastic

Prepare model schema –

var BlogSchema = new mongooseSchema({
title: { type: String, es_indexed: true },
blog_text: { type: String, es_indexed: true },
author_name: { type: String, es_indexed: true },
sequence_num: Number,
category: { type: String, es_indexed: true },
created_at: { type: Date, default: Date.now },
modified_at: { type: Date, default: Date.now },
is_verified: { type: Boolean, es_indexed: true }
});

Just add the plugin to the model, to index model into elasticsearch.

BlogSchema .plugin(mongoosastic);
var Blog = mongoose.model(‘Blog’, BlogSchema , ‘blogs’);

By default the plugin will use the pluralization of model name (‘blogs’) as the index name and model name (‘blog’) as type in elasticsearch.

So, assuming if elasticsearch is running locally on 9200 port, you can search in above index as http://localhost:9200/blogs/blog/_search

By default all fields in the model get indexed into Elasticsearch which will duplicate the document from mongodb to Elasticsearch. That can be avoided by indexing only required fields, by using ‘ex_indexed : true’ as shown above.

In above example, title, author_name, blog_text fields will be indexed in Elasticsearch and you can search for the text present in any of these fields.

In our case collection was existing. To index an existing mongodb collection –

var stream = Blog.synchronize();
stream.on('error', function (err) {
console.log("Error while synchronizing" + err);
});

In case your application is saving/removing documents from DB, the indexing takes place after saving in mongodb and is a deferred process.

blog.save(function(err){
if (err) throw err;
/* Document indexation on going */
blog.on('es-indexed', function(err, res){
if (err) throw err;
/* Document is indexed */
});
});

In case when document is removed, it is also removed from index.

blog.remove(function(err) {
if (err) throw err;
/* Document unindexing in the background */
blog.on('es-removed', function(err, res) {
if (err) throw err;
/* Document is unindexed */
});
});

Create custom analyser –

In our application, we had a field which contained “.” in a title. While searching user may not enter “.”,  so it was needed to replace “.” with “ ”. For that elasticsearch custom analyzer can be created on specific field.

Blog.createMapping({
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "classic",
"char_filter": [ "my_pattern" ],
"filter": ["lowercase"] }
},
"char_filter": {
"my_pattern": {
"type": "pattern_replace",
"pattern": "\\.",
"replacement": " "
}
}
}
},
"mappings": {
"blog": {
"dynamic_templates": [{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}],
"properties": {
"title": {
"type": "text",
"analyzer": "my_analyzer"
},
"category": {
"type": "keyword"
}
}
}
}
}, (err, mapping) => {
if (err) {
console.log('error creating mapping (you can safely ignore this)');
console.log(err);
} else {
console.log('mapping created!');
console.log(mapping);
}
});

Search –

For search queries, search method is used. Elasticsearch query DSL can be used through search method.

In following example, search will be performed on ‘blog_text’ and ‘title’ fields. Search for ‘blog_text’ field is boosted three times using ^ sign. That means, ‘blog_text’ field is given importance while searching the text compared to ‘title’. In the following example, we are doing multi match elasticsearch query for matching the search term. The example is specific to our application, just to get an idea.

var collections = ['blogs'];
var types = ['blog'];
var fields = ["blog_text^3", "title"] var filter = {
"is_verified": true
};
var sort = "_score";
Blog.search({
bool: {
must: [{
multi_match: {
query: searchString,
type: "phrase",
fields: fields,
},
}],
should: [{
multi_match: {
query: searchString,
fields: fields,
operator: "and",
boost: 10,
minimum_should_match: "100%"
},
}],
filter: [{
term: filter
}] }
}, {
index: collections,
type: types,
from: from,
size: 10,
track_scores: true,
sort: sort
},
function (err, results) {
if (err) {
console.log("Error in searchController: ", err);
} else {
res.json({result: results.hits.hits})
}
});

 

Summary
Review Date
Reviewed Item
Node + MongoDB + Elasticsearch
Author Rating
51star1star1star1star1star
Kshitija Ambulgekar

Author Kshitija Ambulgekar

More posts by Kshitija Ambulgekar

Leave a Reply