3

How do I have autoincrement ids in mongoose? I want my ids to start like 1, 2, 3, 4, not the weird id numbers mongodb creates for you?

Here's my schema:

var PortfolioSchema = mongoose.Schema({
    url: String,
    createTime: { type: Date, default: Date.now },
    updateTime: { type: Date, default: Date.now },
    user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Blakes Seven
  • 49,422
  • 14
  • 129
  • 135
Chris Hansen
  • 7,813
  • 15
  • 81
  • 165

2 Answers2

4

Use mongoose-auto-increment: https://github.com/codetunnel/mongoose-auto-increment

var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
var connection = ....;
autoIncrement.initialize(connection);

var PortfolioSchema = new mongoose.Schema({
    url: String,
    createTime: { type: Date, default: Date.now },
    updateTime: { type: Date, default: Date.now },
    user: {type: Schema.Types.ObjectId, ref: 'User'}
});

//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio' });

module.exports = mongoose.model('Portfolio', PortfolioSchema);

Or if you prefer to use an additional field instead of overriding _id, just add the field and list it in the auto-increment initialization:

var PortfolioSchema = new mongoose.Schema({
    portfolioId: {type: Number, required: true},
    url: String,
    createTime: { type: Date, default: Date.now },
    updateTime: { type: Date, default: Date.now },
    user: {type: Schema.Types.ObjectId, ref: 'User'}
});

//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio', field: 'portfolioId' });
Daniel Flippance
  • 7,734
  • 5
  • 42
  • 55
  • Would you be able to answer the question posted in https://stackoverflow.com/questions/45357813/issue-with-mongoose-auto-increment-plugin-in-complex-models – Sona Shetty Jul 29 '17 at 07:36
  • This plugin hasn't been maintained since 2015 and the repo is archived. There are a bunch of copycat autoincrement plugins at https://plugins.mongoosejs.io/ if you search for `autoincrement`. – Dan Dascalescu Apr 13 '20 at 07:02
2

If you want to have a incrementing numeric value in _id then the basic process is you are going to need something to return that value from a store somewhere. One way to do this is use MongoDB itself to store data that holds the counters for the _id values for each collection, which is described within the manual itself under Create and Auto-Incrementing Sequence Field.

Then as you create each new item, you use the implemented function to get that "counter" value, and use it as the _id in your document.

When overriding the default behavior here, mongoose requires that you both specify the _id and it's type explicitly with something like _id: Number and also that you tell it to no longer automatically try to supply an ObjectId type with { "_id": false } as an option on the schema.

Here's a working example in practice:

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

mongoose.connect('mongodb://localhost/test');

var counterSchema = new Schema({
  "_id": String,
  "counter": { "type": Number, "default": 1 }
},{ "_id": false });

counterSchema.statics.getNewId = function(key,callback) {
  return this.findByIdAndUpdate(key,
    { "$inc": { "counter": 1 } },
    { "upsert": true, "new": true },
    callback
  );
};

var sampleSchema = new Schema({
  "_id": Number,
  "name": String
},{ "_id": false });

var Counter = mongoose.model( 'Counter', counterSchema ),
    ModelA = mongoose.model( 'ModelA', sampleSchema ),
    ModelB = mongoose.model( 'ModelB', sampleSchema );


async.series(
  [
    function(callback) {
      async.each([Counter,ModelA,ModelB],function(model,callback) {
        model.remove({},callback);
      },callback);
    },
    function(callback) {
      async.eachSeries(
        [
          { "model": "ModelA", "name": "bill" },
          { "model": "ModelB", "name": "apple" },
          { "model": "ModelA", "name": "ted" },
          { "model": "ModelB", "name": "oranage" }
        ],
        function(item,callback) {
          async.waterfall(
            [
              function(callback) {
                Counter.getNewId(item.model,callback);
              },
              function(counter,callback) {
                mongoose.model(item.model).findByIdAndUpdate(
                  counter.counter,
                  { "$set": { "name": item.name } },
                  { "upsert": true, "new": true },
                  function(err,doc) {
                    console.log(doc);
                    callback(err);
                  }
                );
              }
            ],
            callback
          );
        },
        callback
      );
    },
    function(callback) {
      Counter.find().exec(function(err,result) {
        console.log(result);
        callback(err);
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

For convience this implements a static method on the model as .getNewId() which just descriptively wraps the main function used in .findByIdAndUpdate(). This is a form of .findAndModify() as mentioned in the manual page section.

The purpose of this is that it is going to look up a specific "key" ( actually again the _id ) in the Counter model collection and perform an operation to both "increment" the counter value for that key and return the modified document. This is also aided with the "upsert" option, since if no document yet exists for the requested "key", then it will be created, otherwise the value will be incremented via $inc, and it always is so the default will be 1.

The example here shows that two counters are being maintained independently:

{ _id: 1, name: 'bill', __v: 0 }
{ _id: 1, name: 'apple', __v: 0 }
{ _id: 2, name: 'ted', __v: 0 }
{ _id: 2, name: 'oranage', __v: 0 }
[ { _id: 'ModelA', __v: 0, counter: 2 },
  { _id: 'ModelB', __v: 0, counter: 2 } ]

First listing out each document as it is created and then displaying the end state of the "counters" collection which holds the last used values for each key that was requested.

Also note those "weird numbers" serves a specific purpose of always being guranteed to be unique and also always increasing in order. And note that they do so without requiring another trip to the database in order to safely store and use an incremented number. So that should be well worth considering.

Blakes Seven
  • 49,422
  • 14
  • 129
  • 135