Search code examples
node.jselasticsearchmongoosastic

Use mongoosastic for an autocomplete


I'm trying to create an autocomplete using mongoosastic and Elastic Search, and so far, I have been able to create it using sense but I'm having trouble porting it to mongoosastic.

I followed this tutorial from ElasticSearch docs, and I was able to achieve what I wanted using "sense" with a mapping that looks like this:

PUT storys/story/_mapping
{
    "story" : { 
        "properties": {
            "description": {
                "type": "string"
            },
            "title": {
                "type" : "completion",
                "index_analyzer": "simple",
                "search_analyzer": "simple"
            }
        }  
    }
}

and a query like this:

GET storys/_suggest
{
    "story-suggest": {
        "text": "bow",
        "completion": {
            "field": "title"
        }
    }
}

However, I'm having trouble porting this to mongoosastic. I tried the following approach:

    var StorySchema = new Schema({
        title:{
            type: String, es_type:'completion', es_index_analyzer: 'simple', es_search_analyzer: 'simple', es_payloads: true
        },
        description: { 
            type: String
        }
    });

StorySchema.plugin(mongoosastic);

And when querying from the server controller:

Story.search({ 
    query: {
        "match": { title : req.query.q }
    },
    suggest: {
            "my-title-suggestions-1" :{
                text: req.query.q,
                completion: {
                    field: 'title'
                }
            }
        }
});

I understand that when I use "sense", I'm using the _suggest endpoint, and that's why the "story-suggest" query works. However, when using mongoosastic, I'm limited to use the .search({}) for querying which acts like _search I suppose. However, I cannot seem to find a way to accomplish the _suggest behavior I'm seeking for an autocomplete, and I keep getting parsing errors in ElasticSearch when I try to do a query with a suggest.

Is there a way to accomplish what I'm trying to do either with mongoosastic or elastic search?

I have tried doing this using "sense" but even though I get the suggestions for "autocomplete" I also get a bunch of SearchParseExceptions:

GET _search
{
    "query": {
       "match": { title : "bow" }
    },
    "suggest": {
        "story-suggest": {
            "text": "bow",
            "completion": {
                "field": "title"
            }
        }
    }
}

Solution

  • Here is a complete solution for a basic autocomplete:

    1. Add the necessary parameters to your schema:

      var TagSchema = new Schema({
          name: {
              type: String,
              unique: true,
              required: true,
              es_type: 'completion',
              es_index_analyzer: 'simple',
              es_search_analyzer: 'simple',
              es_payloads: true
          }
      });
      
    2. Add the plugin to your schema and create the model:

      TagSchema.plugin(mongoosastic);
      var Tag = mongoose.model('Tag', TagSchema);
      
    3. Use the create mapping method to register the mapping with ES. If you don't do this step your index will get registered with defaults, when the first document is created and indexed:

      Tag.createMapping(function(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);
          }
      });
      
    4. *Optional - Index existing documents in your database:

      var stream = Tag.synchronize(),
          count = 0;
      
      stream.on('data', function(err, doc) {
          count++;
      });
      stream.on('close', function() {
          console.log('indexed ' + count + ' documents!');
      });
      stream.on('error', function(err) {
          console.log(err);
      });
      
    5. Search with an empty query body and supply two options - 1) Use the ES suggest query which uses the "es_completion" field we created in our schema; 2) size = 0 so that no tags are returned by the empty body query.

      Tag.search(null, {
          suggest: {
              "tag-suggest": {
                  "text": "aTermToAutocomplete",
                  "completion": {
                      "field": "name"
                  }
              }
          },
          "size" : 0
      },
      function(err, results) {
          if (err) {
              return console.log(JSON.stringify(err, null, 4));
          }
          return console.log(JSON.stringify(results, null, 4));
      });