Mongodb error: $ in needs an array

0

I am modifying the delete function in a crud. Originally taskID was a single ID to be deleted; while in my program it is an array of multiple IDs. I do not know what the line of code with mongodb or mongoose would be like to eliminate all the tasks in the array. Next I put what I have tried and the error that gives me.

Frontend method

deleteTask(taskId) {
    var pairs = taskId.map(function (value) { return "taskId=" + encodeURIComponent(value) }); //mi codigo
    var query_string = pairs.join("&"); //mi codigo
    fetch('/api/tasks/' + taskId, {
        method: 'DELETE',
        headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
        }
    })
    .then(res => res.json())
    .then(data => {
      this.getTasks();
    });
    },

Backend method

router.delete('/:taskId', async (req, res) => {
    //await Task.findByIdAndRemove(req.params.taskId); código original
    await Task.deleteMany({ id : { $in : req.params.taskId } }); //mi código
    res.json({ status: 'Task deleted' });
});

The error is because I do not know how to deal with the URI I receive in the backend:

  

(node: 15540) UnhandledPromiseRejectionWarning: CastError: Cast to   ObjectId failed for value   "taskId = 1 & taskId = 2 & taskId = 3" at   path "_id" for model "Task"

    
asked by german 04.01.2019 в 05:08
source

1 answer

0

I think you have a little confusion regarding the use of req.param and req.query .

In your backend, as you say, you originally receive a parameter ( :taskId ) and pass that parameter to the remove of your controller Task .

Now, your requirements have changed and you must pass not one, but several taskId to perform a batch process.

To solve this you have some alternatives, I will mention one that will help you solve your problem and at the same time it will give you an idea of how req.param and req.query are used.

Another thing that I can notice, in your frontend code you declare query_string but then I do not see that you use it.

Let's start:

The first thing we are going to do is create a route in the backend that captures '/api/tasks' without including any parameter:

// nueva ruta en el backend, la pondremos antes de la ruta antigua.

router.route('/api/tasks')
  .delete(async (req, res, next) => {
    // Ahora verificamos si la URI contiene la query que esperamos
    if (!req.query.taskId) {
      return res.status(400).json({
        message: 'Nada para procesar'
      });
    }
    // Nuestra API espera una query con la clave taskId
    // y que contenga una lista de id válidos para realizar
    // el proceso por lotes.
    
    let taskId = []; // aqui almacenaremos los id
    let id = ''; // aqui construiremos los id desde el string, carácter por carácter
    
    for (let index in req.query.taskId) {
      // Los valores vendrán separados por coma
      // (los puedes separar como gustes, pero prefiero
      // usar el viejo CSV: Comma Separated Value)
      if (req.query.taskId[index] !== ',') {
        id = id.concat(req.query.taskId[index]); // almacenamos el carácter en id.
      } else {
        taskId.push(id); // Encontramos una comma, guardamos el id
        id = '' // reiniciamos para volver a empezar
      }
    }
    // Si todo va bien, tendremos el array con los taskId a procesar.
    // Ahora podemos procesar los datos:

    await Task.deleteMany({ id : { $in : taskId } });
    res.json({ status: 'Task deleted' });

    
  });

We already have the route in our backend, we just need to modify in the frontend to create the correct URI that contains the taskId in the form of query :

deleteTask(taskId) {
    // Asumo que taskId es un array con los "id"
    // Dado que este es tu código, no tengo mucho que explicarte
    // salvo que he quitado el string "taskId=" y
    // cambié el nombre de la variable de "pair" a "ids"
    var ids = taskId.map(function (value) { return encodeURIComponent(value) });
    
    // aqui tampoco debo explicarte mucho porque también es tu código, solo que he cambiado "&" por ","
    var query_string = ids.join(",");
    
    // como ves, aqui paso el query string en la URI
    // y si te das cuenta, tu mismo habías intuído que
    // debías usar una query, dado el nombre de tu variable
    //Una cosa que debes observar es cómo se pasa un query en una URI.
    
    fetch('/api/tasks?taskId=' + query_string, {
        method: 'DELETE',
        headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
        }
    })
    .then(res => res.json())
    .then(data => {
      this.getTasks();
    });
    },

I hope that with this you solve the problem. As I say it is not the only way to do it, but if it avoids you less modifications in your frontend.

Notice that when you use req.param you pass the route in the following way: '/api/tasks/:taskId' of this form Express understands that you are passing one (1) parameter. Already in your API you must have the controller that manages the parameter.

On the other hand, if you want to use req.query you must do it in the following way: '/api/tasks?query1=value1&query2=value2&...&queryN=valueN' . You should note that a query is a consistent list of key=value and every key must have a different name.

In this specific case, as we do not know how many taskId we are going to erase, then I decided to pass them as a CSV in a single key called taskId .

However, if your taskId are too long and too many, this is not the best method to do so, since you are facing problems with the size of the URI that your server can handle.

You can read more at link

Here is an excerpt from the Section "3.2.1 General Syntax":

  

The HTTP protocol does not place any a priori limit on the length of      to URI. Servers MUST be able to handle the URI of any resource they      serve, and SHOULD be able to handle URIs of unbounded length if they      provide GET-based forms that could generate such URIs. A server      SHOULD return 414 (Request-URI Too Long) status if a URI is longer      than the server can handle (see section 10.4.15).

 Note: Servers ought to be cautious about depending on URI lengths
  above 255 bytes, because some older client or proxy
  implementations might not properly support these lengths.

Edit:

As you put in your comment, your values of taskId will be id of Mongo, therefore it is not appropriate to send them by means of the URI to the server, since if you send many id the URI would be too long .

What you can do is send them in the body of your request, in the following way:

deleteTask(taskId) {
    // Asumo que taskId es un array con los "id"
    
    
    // Ahora apuntamos directamente a la ruta '/api/tasks'
    fetch('/api/tasks', {
        method: 'DELETE',
        headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
        },
        body: JSON.stringify(taskID) // <=== Aqui enviamos la data en el 'body' del mensaje.
    })
    .then(res => res.json())
    .then(data => {
      this.getTasks();
    });
    },

Now we must change the code in the backend to receive and process the data properly:

router.route('/api/tasks')
  .delete(async (req, res, next) => {
    // Nuestra data viene en 'req.body', verificamos
    if (!req.body) {
      return res.status(400).json({
        message: 'Nada para procesar'
      });
    }
    
    // Almacenamos la data de body en 'taskId'
    const taskId = req.body;
    
    // Si quieres ver el contenido de taskId:
    for (let index in taskId) {
      console.log('index: ', index, ' taskId: ', taskId[index]);
      }
      
    // Ya podemos procesar la data
    await Task.deleteMany({ id : { $in : taskId } });
    res.json({ status: 'Task deleted' });

    
  });

This is already enough to get what you need.

I hope you have been helpful.

    
answered by 04.01.2019 в 15:45