Search code examples
restelasticsearchrestifyelasticsearch-api

Multiple functions in restify function to elasticsearch client


I'm building a REST API using node and restify that communicaties with an elasticsearch database. Now when I delete an object, I want this to do a kind of cascading delete to some other objects. I know this is not really what to use elasticsearch for but bear with me.

So here is my code:

function deleteHostname(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    var endpoints = [];

    client.search({
        index: 'test',
        type: 'something',
        body: {
            from: 0, size: 100,
            query: {
                match: {
                    hostname: 'www.test.com'
                }
            }
        }
    }).then(function (error, resp) {
        if(error) {
            res.send(error);
        }

        endpoints = resp.hits.hits;

        for (index = 0, len = endpoints.length; index < len; ++index) {
            client.delete({
                index: 'test',
                type: 'something',
                id: endpoints[index]._id
            }, function (error, response) {
                if(error) {
                    res.send(error);
                }
            });
        }

        res.send(endpoints);
        return next();
    });
}

So basically I just want to search for any objects with hostname www.test.com ( I just hard coded this to test it ). Then I want to delete all objects I found. It follows the error path and sends me this:

{  
   "took":1,
   "timed_out":false,
   "_shards":{  
      "total":5,
      "successful":5,
      "failed":0
   },
   "hits":{  
      "total":1,
      "max_score":2.098612,
      "hits":[  
         {  
            "_index":"test",
            "_type":"something",
            "_id":"123456",
            "_score":2.098612,
            "_source":{  
               "duration":107182,
               "date":"2016-05-04 00:54:43",
               "isExceptional":true,
               "hostname":"www.test.com",
               "eta":613,
               "hasWarnings":false,
               "grade":"A+",
               "ipAddress":"ipip",
               "progress":100,
               "delegation":2,
               "statusMessage":"Ready"
            }
         }
      ]
   }
}

So in my opinion this doesn't look like an error? So why am I getting it back as an error? If I remove:

        if(error) {
            res.send(error);
        }

From my code, I won't get any response.


Solution

  • You need to change your code like this (see the changes denoted by -> to the left):

        if(error) {
    1->     return res.send(error);
        }
    
        endpoints = resp.hits.hits;
    
        for (index = 0, len = endpoints.length; index < len; ++index) {
    2->     (function(id){
                client.delete({
                    index: 'test',
                    type: 'something',
    3->             id: id
                }, function (error, response) {
                    if(error) {
    4->                 next(error);
                    }
                });
    5->     })(endpoints[index._id]);
        }
    
    6-> //res.send(endpoints);
    

    I'm now explaining each change:

    1. If you don't return you'll send the error and then you'll continue with processing the hits
    2. (3/5) Since client.delete is an asynchronous function, you need to call it in an anonymous function
    3. In case of error you need to call next(error) not res.send
    4. You cannot send the response at this point since your for loop might not be terminated yet. Instead of a for loop, you should use the excellent async library instead (see an example of using asynch.each below)

    Async example:

        var async = require('async');
        ...
    
        if(error) {
            return res.send(error);
        }
    
        endpoints = resp.hits.hits;
    
        async.each(endpoints, 
            function(endpoint, callback) {
                client.delete({
                   index: 'test',
                   type: 'something',
                   id: endpoint._id
                }, callback);
            }, 
            // this is called when all deletes are done
            function(err){
                if (err) {
                    next(err);
                } else {
                    res.send(endpoints);
                    next();
                }
            }
        );
    

    Another solution for you to achieve exactly what you want is to use the delete by query plugin. That feature allows you to do all the above in a single query.

    If you are still on ES 1.x, delete-by-query is still part of the core and you can simply call the deleteByQuery function of the Javascript client.

    If you are on ES 2.x, delete-by-query is now a plugin, so yo need to install it and then also require the deleteByQuery extension library for the Javascript client

    function deleteHostname(req, res, next) {
        res.setHeader('Access-Control-Allow-Origin', '*');
    
        client.deleteByQuery({
            index: 'test',
            type: 'something',
            body: {
               query: {
                   match: { hostname: 'www.test.com' }
               }
            }
        }, function (error, response) {
            if (error) {
                next(error);
            } else {
                res.send(endpoints);
                next();
            }
        });
    }