29

I want to give users the ability to create collections in my Node app. I have really only seen example of hard coding in collections with mongoose. Anyone know if its possible to create collections dynamically with mongoose? If so an example would be very helpful.

Basically I want to be able to store data for different 'events' in different collections.

I.E. Events: event1, event2, ... eventN

Users can create there own custom event and store data in that collection. In the end each event might have hundreds/thousands of rows. I would like to give users the ability to perform CRUD operations on their events. Rather than store in one big collection I would like to store each events data in a different collection.

I don't really have an example of what I have tried as I have only created 'hard coded' collections with mongoose. I am not even sure I can create a new collection in mongoose that is dynamic based on a user request.

var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');

var schema = mongoose.Schema({ name: 'string' });
var Event1 = mongoose.model('Event1', schema);

var event1= new Event1({ name: 'something' });
event1.save(function (err) {
  if (err) // ...
  console.log('meow');
});

Above works great if I hard code 'Event1' as a collection. Not sure I create a dynamic collection.

var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');

...

var userDefinedEvent = //get this from a client side request
...

var schema = mongoose.Schema({ name: 'string' });
var userDefinedEvent = mongoose.model(userDefinedEvent, schema);

Can you do that?

Cœur
  • 37,241
  • 25
  • 195
  • 267
lostintranslation
  • 23,756
  • 50
  • 159
  • 262

8 Answers8

22

I believe that this is a terrible idea to implement, but a question deserves an answer. You need to define a schema with a dynamic name that allows information of 'Any' type in it. A function to do this may be a little similar to this function:

var establishedModels = {};
function createModelForName(name) {
    if (!(name in establishedModels)) {
        var Any = new Schema({ any: Schema.Types.Mixed });
        establishedModels[name] = mongoose.model(name, Any);
    }
    return establishedModels[name];
}

Now you can create models that allow information without any kind of restriction, including the name. I'm going to assume an object defined like this, {name: 'hello', content: {x: 1}}, which is provided by the 'user'. To save this, I can run the following code:

var stuff = {name: 'hello', content: {x: 1}}; // Define info.
var Model = createModelForName(name); // Create the model.
var model = Model(stuff.content); // Create a model instance.
model.save(function (err) { // Save
    if (err) {
        console.log(err);
    }
});

Queries are very similar, fetch the model and then do a query:

var stuff = {name: 'hello', query: {x: {'$gt': 0}}}; // Define info.
var Model = createModelForName(name); // Create the model.
model.find(stuff.query, function (err, entries) {
    // Do something with the matched entries.
});

You will have to implement code to protect your queries. You don't want the user to blow up your db.

Deathspike
  • 8,582
  • 6
  • 44
  • 82
  • You say that this is a terrible idea but provide no alternative. Say I have 20 different events. Each event has 1 million entries. You have to provide an event to query. As such if this is all in one collection I will have to filter the collection by event for every CRUD op. I was thinking I could save on this by storing each event in its own collection. That way when a users comes in with a CRUD operation on an event I can go strait to that events collection, bypassing the other 19 million entries. If anyone has other ideas I am definitely all ears and would greatly appreciate the help. – lostintranslation Mar 09 '13 at 14:40
  • 7
    Just make an index? That's why we have indices. Also, it is not the job of an answer on a question to come up with an alternative. It is to answer the posed question. – Deathspike Mar 09 '13 at 17:09
  • 1
    The answer to your question has been provided. If you use a single collection and want to allow CRUD operations, you just need to track ownership. You could create an array in the user with owned event documents by storing the _id. All CRUD operations are then possible without performance impact, no matter the amount of documents (even if there are 19mil+, the database won't care). – Deathspike Mar 09 '13 at 19:19
8

From mongo docs here: data modeling

In certain situations, you might choose to store information in several collections rather than in a single collection.

Consider a sample collection logs that stores log documents for various environment and applications. The logs collection contains documents of the following form:

{ log: "dev", ts: ..., info: ... } { log: "debug", ts: ..., info: ...}

If the total number of documents is low you may group documents into collection by type. For logs, consider maintaining distinct log collections, such as logs.dev and logs.debug. The logs.dev collection would contain only the documents related to the dev environment.

Generally, having large number of collections has no significant performance penalty and results in very good performance. Distinct collections are very important for high-throughput batch processing.

lostintranslation
  • 23,756
  • 50
  • 159
  • 262
5

Say I have 20 different events. Each event has 1 million entries... As such if this is all in one collection I will have to filter the collection by event for every CRUD op.

I would suggest you keep all events in the same collection, especially if event names depend on client code and are thus subject to change. Instead, index the name and user reference.

mongoose.Schema({
  name: { type: String, index: true },
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true } 
});

Furthermore I think you came at the problem a bit backwards (but I might be mistaken). Are you finding events within the context of a user, or finding users within the context of an event name? I have a feeling it's the former, and you should be partitioning on user reference, not the event name in the first place.

If you do not need to find all events for a user and just need to deal with user and event name together you could go with a compound index:

schema.index({ user: 1, name: 1 });

If you are dealing with millions of documents, make sure to turn off auto index:

schema.set('autoIndex', false);

This post has interesting stuff about naming collections and using a specific schema as well:

How to access a preexisting collection with Mongoose?

Community
  • 1
  • 1
AJcodez
  • 31,780
  • 20
  • 84
  • 118
1

Create a dynamic.model.ts access from some where to achieve this feature.

import mongoose, { Schema } from "mongoose";

export default function dynamicModelName(collectionName: any) {
    var dynamicSchema = new Schema({ any: Schema.Types.Mixed }, { strict: false });
    return mongoose.model(collectionName, dynamicSchema);
}

Create dynamic model

import dynamicModelName from "../models/dynamic.model"

var stuff = { name: 'hello', content: { x: 1 } };
var Model = await dynamicModelName('test2')
let response = await new Model(stuff).save();
return res.send(response);

Get the value from the dynamic model

var Model = dynamicModelName('test2');
let response = await Model.find();
return res.send(response);
0

You could try the following:

var createDB = function(name) {

  var connection = mongoose.createConnection(
    'mongodb://localhost:27017/' + name);

  connection.on('open', function() {
    connection.db.collectionNames(function(error) {
      if (error) {
        return console.log("error", error)
      }
    });

  });
  connection.on('error', function(error) {
    return console.log("error", error)
  });

}
It is important that you get the collections names with connection.db.collectionNames, otherwise the Database won't be created.
Daniele Urania
  • 2,658
  • 1
  • 15
  • 11
0

This method works best for me , This example creates dynamic collection for each users , each collection will hold only corresponding users information (login details), first declare the function dynamicModel in separate file : example model.js

/* model.js */
'use strict';

var mongoose = require('mongoose'),
  Schema = mongoose.Schema;


  function dynamicModel(suffix) {
      var addressSchema = new Schema(
          {
               "name" : {type: String, default: '',trim: true},
               "login_time" : {type: Date},
               "location" : {type: String, default: '',trim: true},
          }
  );

     return mongoose.model('user_' + suffix, addressSchema);

  }

module.exports = dynamicModel;

In controller File example user.js,first function to create dynamic collection and second function to save data to a particular collection

/* user.js */
var  mongoose = require('mongoose'),

function CreateModel(user_name){//function to create collection , user_name  argument contains collection name

  var Model  = require(path.resolve('./model.js'))(user_name);

}

function save_user_info(user_name,data){//function to save user info , data argument contains user info
     var UserModel  = mongoose.model(user_name) ;
     var usermodel  = UserModel(data);
              usermodel.save(function (err) {

               if (err) {
                  console.log(err);
                } else {
                 console.log("\nSaved");
                }
           });
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Prem Sanil
  • 128
  • 8
0

yes we can do that .I have tried it and its working.

REFERENCE CODE:

app.post("/",function(req,res){

  var Cat=req.body.catg;
  const link= req.body.link;
  const rating=req.body.rating;
  Cat=mongoose.model(Cat,schema);
  const item=new Cat({
  name:link,
  age:rating
  });
 item.save();
  res.render("\index");
});
David Buck
  • 3,752
  • 35
  • 31
  • 35
0

I tried Magesh varan Reference Code ,

and this code works for me

   router.post("/auto-create-collection", (req, res) => {
  var reqData = req.body; // {"username":"123","password":"321","collectionName":"user_data"}

  let userName = reqData.username;
  let passWord = reqData.password;
  let collectionName = reqData.collectionName;

  // create schema
  var mySchema = new mongoose.Schema({
    userName: String,
    passWord: String,
  });

  // create model
  var myModel = mongoose.model(collectionName, mySchema);

  const storeData = new myModel({
    userName: userName,
    passWord: passWord,
  });
  storeData.save();

  res.json(storeData);
});
Ganesh MB
  • 1,109
  • 2
  • 14
  • 27