1

I am using mongoose in nodejs(express) in backend. My array structure has THREE levels. At third level, some files are present. But I need to add entries at any level as per user demand.

   [
   {
   "name": "A folder at",
    "route": "level1_a"
  },
  {
    "name":"Another folder at Level1",
    "route": "level1_b",
    "children":
        [
            {
            "name": "A folder at Level2",
            "route": "level1_b/level2_a",
            "children": 
                [
                    {
                    "name": "A folder at Level3",
                    "route": "level1_b/level2_a/level3_a",
                    "children":
                        [
                            {
                            "name": "A file at last level",
                            "route": "level1_b/level2_a/level3_a/file1"
                            },
                            {
                            "name": "Add a new File",
                            "route":"level1_b/level2_a/level3_a/new_file"
                            }
                        ]
                    },
                    {
                    "name": "Add Folder at Level3",
                    "route":"level1_b/level2_a/new_folder"
                    }
                ]
            },
            {
                "name": "Add Folder at level2",
                "route":"level1_b/new_folder"
            }
        ]
  },
  {
      "name": "Add Folder at Level1",
      "route":"new_folder"
  }
]

Now I have to add an entry at a specified position. Suppose at level2, I need to add a folder. For adding, two parameters are sent from angular to the backend. These will be 'name' and a 'route'. So my entry would be having {name: 'Products', route: 'level1_a/products'} and similarily should be placed at correct position i.e. inside the children of level1_a. My backend has a schema which would be like:

const navSchema = mongoose.Schema({
name:{type:String,required:true},
route:{type:String},
children:{
    type: {
    name:{type:String,required:true},
    route:{type:String},
 }}
});
module.exports = mongoose.model('NatItems',navSchema);

And the API would be like:

router.post('/navlist',(req,res,next)=>{
  const name= req.body.folder;
  const route= req.body.url;
  console.log(folder,url);//it will be required parameters like name: 'Products', route:'level1_a/products'
  //let pathArray = route.split('/'); //if you want you can split the urls in the array

  //Help me write the code here

  res.status(201).json({
    message:"Post added successfully!"
  })
})

Please help me in adding entries in db. I know navlist.save() adds an entry directly but I am not able to add entries in a nested manner.

PS: I can't change the array structure because this array is easily read by angular and a complete navigation menu is made!! I am working for first time in nodejs and mongoose, so I am having difficulty in writing code with mongoose function.

1 Answer 1

4
+50

For the scenario you've provided ({name: 'Products', route: 'level1_a/products'}) the update statement is pretty straightforward and looks like this:

Model.update(
   { route: "level1_a" }, 
   { $push: { children: {name: 'Products', route: 'level1_a/products'} } })

Things are getting a little bit more complicated when there are more than two segments in the incoming route, e.g.

{  "name": "Add a new File", "route":"level1_b/level2_a/level3_a/new_file2" };

In such case you need to take advantage of the positional filtered operator and build arrayFilters and your query becomes this:

Model.update(
    {  "route": "level1_b"},
    {
        "$push": {
                "children.$[child0].children.$[child1].children": {
                    "name": "Add a new File",
                    "route": "level1_b/level2_a/level3_a/new_file2"
                }
            }
        },
    {
        "arrayFilters": [
            {
                "child0.route": "level1_b/level2_a"
            },
            {
                "child1.route": "level1_b/level2_a/level3_a"
            }
        ]
    })

So you need a function which loops through the route and builds corresponding update statement along with options:

let obj = {  "name": "Add a new File", "route":"level1_b/level2_a/level3_a/new_file2" };

let segments = obj.route.split('/');;
let query = { route: segments[0] };
let update, options = {};
if(segments.length === 2){
    update = { $push: { children: obj } }
} else {
    let updatePath = "children";
    options.arrayFilters = [];
    for(let i = 0; i < segments.length -2; i++){
        updatePath += `.$[child${i}].children`;
        options.arrayFilters.push({ [`child${i}.route`]: segments.slice(0, i + 2).join('/') });
    }
    
    update = { $push: { [updatePath]: obj } };
    
}

console.log('query', query);
console.log('update', update);
console.log('options', options);

So you can run:

Model.update(query, update, options);
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks @mickl for an apt answer. The concept of building arrayFilters was completely unknown to me. Happy to award you the bounty xD ! Just one thing, in a case where if(segments.length === 1) i.e. one could also put an entry directly into the array, I used the following in the if block -> {obj.save(); return;} . Can you suggest a better alternative here where I can still use the Update fn?
@SwapnilSourabh yeah, you need to handle such case as well and just save should be fine, good idea!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.