By HMR


2015-02-06 03:11:28 8 Comments

According to this mongodb article it is possible to auto increment a field and I would like the use the counters collection way.

The problem with that example is that I don't have thousands of people typing the data in the database using the mongo console. Instead I am trying to use mongoose.

So my schema looks something like this:

var entitySchema = mongoose.Schema({
  testvalue:{type:String,default:function getNextSequence() {
        console.log('what is this:',mongoose);//this is mongoose
        var ret = db.counters.findAndModify({
                 query: { _id:'entityId' },
                 update: { $inc: { seq: 1 } },
                 new: true
               }
        );
        return ret.seq;
      }
    }
});

I have created the counters collection in the same database and added a page with the _id of 'entityId'. From here I am not sure how to use mongoose to update that page and get the incrementing number.

There is no schema for counters and I would like it to stay that way because this is not really an entity used by the application. It should only be used in the schema(s) to auto increment fields.

12 comments

@Tigran 2019-10-15 15:03:52

I've combined all the (subjectively and objectively) good parts of the answers, and came up with this code:

const counterSchema = new mongoose.Schema({
    _id: {
        type: String,
        required: true,
    },
    seq: {
        type: Number,
        default: 0,
    },
});

// Add a static "increment" method to the Model
// It will recieve the collection name for which to increment and return the counter value
counterSchema.static('increment', async function(counterName) {
    const count = await this.findByIdAndUpdate(
        counterName,
        {$inc: {seq: 1}},
        // new: return the new value
        // upsert: create document if it doesn't exist
        {new: true, upsert: true}
    );
    return count.seq;
});

const CounterModel = mongoose.model('Counter', counterSchema);


entitySchema.pre('save', async function() {
    // Don't increment if this is NOT a newly created document
    if(!this.isNew) return;

    const testvalue = await CounterModel.increment('entity');
    this.testvalue = testvalue;
});

One of the benefits of this approach is that all the counter related logic is separate. You can store it in a separate file and use it for multiple models importing the CounterModel.

If you are going to increment the _id field, use should add it's definition in your schema:

const entitySchema = new mongoose.Schema({
    _id: {
        type: Number,
        alias: 'id',
        required: true,
    },
    <...>
});

@MASh 2019-09-12 06:58:03

Here is a proposal.

Create a separate collection to holds the max value for a model collection

const autoIncrementSchema = new Schema({
    name: String,
    seq: { type: Number, default: 0 }
});

const AutoIncrement = mongoose.model('AutoIncrement', autoIncrementSchema);

Now for each needed schema, add a pre-save hook.

For example, let the collection name is Test

schema.pre('save', function preSave(next) {
    const doc = this;
    if (doc.isNew) {
         const nextSeq = AutoIncrement.findOneAndUpdate(
             { name: 'Test' }, 
             { $inc: { seq: 1 } }, 
             { new: true, upsert: true }
         );

         nextSeq
             .then(nextValue => doc[autoIncrementableField] = nextValue)
             .then(next);
    }
    else next();
 }

As findOneAndUpdate is an atomic operation, no two updates will return same seq value. Thus each of your insertion will get an incremental seq regardless of number of concurrent insertions. Also this can be extended to more complex auto incremental logic and the auto increment sequence is not limited to Number type

This is not a tested code. Test before you use until I make a plugin for mongoose.

Update I found that this plugin implemented related approach.

@PALLAMOLLA SAI 2019-02-20 08:34:02

Other way is you can use external packages given by mongoose.(easy to understand)

mongoose sequence plugin

@Simon 2018-01-21 11:21:42

I know this has already a lot of answers, but I would share my solution which is IMO short and easy understandable:

// Use pre middleware
entitySchema.pre('save', function (next) {

    // Only increment when the document is new
    if (this.isNew) {
        entityModel.count().then(res => {
            this._id = res; // Increment count
            next();
        });
    } else {
        next();
    }
});

Make sure that entitySchema._id has type:Number. Mongoose version: 5.0.1.

@Hayden Braxton 2018-04-26 04:37:10

Don't think it would work in every context, but it solves my problem.

@Hammerbot 2018-05-15 12:34:51

IMO this breaks if some document gets deleted of the table at some point... But anyways, it also works for my usecase

@Akash Agarwal 2018-12-19 03:04:57

So combining multiple answers, this is what I ended up using:

counterModel.js

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

const counterSchema = new Schema(
  {
  _id: {type: String, required: true},
  seq: { type: Number, default: 0 }
  }
);

counterSchema.index({ _id: 1, seq: 1 }, { unique: true })

const counterModel = mongoose.model('counter', counterSchema);

const autoIncrementModelID = function (modelName, doc, next) {
  counterModel.findByIdAndUpdate(        // ** Method call begins **
    modelName,                           // The ID to find for in counters model
    { $inc: { seq: 1 } },                // The update
    { new: true, upsert: true },         // The options
    function(error, counter) {           // The callback
      if(error) return next(error);

      doc.id = counter.seq;
      next();
    }
  );                                     // ** Method call ends **
}

module.exports = autoIncrementModelID;

myModel.js

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

const autoIncrementModelID = require('./counterModel');

const myModel = new Schema({
  id: { type: Number, unique: true, min: 1 },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date },
  someOtherField: { type: String }
});

myModel.pre('save', function (next) {
  if (!this.isNew) {
    next();
    return;
  }

  autoIncrementModelID('activities', this, next);
});

module.exports = mongoose.model('myModel', myModel);

@Alberto Rubio 2018-11-28 15:09:25

I didn't wan to use any plugin (an extra dependencie, initializing the mongodb connection apart from the one I use in the server.js, etc...) so I did an extra module, I can use it at any schema and even, I'm considering when you remove a document from the DB.

module.exports = async function(model, data, next) {
    // Only applies to new documents, so updating with model.save() method won't update id
    // We search for the biggest id into the documents (will search in the model, not whole db
    // We limit the search to one result, in descendant order.
    if(data.isNew) {
        let total = await model.find().sort({id: -1}).limit(1);
        data.id = total.length === 0 ? 1 : Number(total[0].id) + 1;
        next();
    };
};

And how to use it:

const autoincremental = require('../modules/auto-incremental');

Work.pre('save', function(next) {
    autoincremental(model, this, next);
    // Arguments:
    // model: The model const here below
    // this: The schema, the body of the document you wan to save
    // next: next fn to continue
});

const model = mongoose.model('Work', Work);
module.exports = model;

Hope it helps you.

(If this Is wrong, please, tell me. I've been having no issues with this, but, not an expert)

@Akash Agarwal 2018-12-18 23:14:39

Interesting solution. My only concern is that if someone deletes a record from the entity collection, in your case Work, the auto-increment values generated using your function may not fulfil the purpose of acting as a primary key (given that it was the purpose).

@douira 2019-09-08 15:40:57

In the pre save function, how do you use model before it's been defined?

@Tính Ngô Quang 2018-10-09 10:12:54

var CounterSchema = Schema({
    _id: { type: String, required: true },
    seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema({
    testvalue: { type: String }
});

entitySchema.pre('save', function(next) {
    if (this.isNew) {
        var doc = this;
        counter.findByIdAndUpdate({ _id: 'entityId' }, { $inc: { seq: 1 } }, { new: true, upsert: true })
            .then(function(count) {
                doc.testvalue = count.seq;
                next();
            })
            .catch(function(error) {
                throw error;
            });
    } else {
        next();
    }
});

@nguyen.thom 2018-05-15 07:22:27

I use together @cluny85 and @edtech. But I don't complete finish this issues.

counterModel.findByIdAndUpdate({_id: 'aid'}, {$inc: { seq: 1} }, function(error,counter){ But in function "pre('save...) then response of update counter finish after save document. So I don't update counter to document.

Please check again all answer.Thank you.

Sorry. I can't add comment. Because I am newbie.

@cluny85 2016-10-19 17:16:00

The most voted answer doesn't work. This is the fix:

var CounterSchema = new mongoose.Schema({
    _id: {type: String, required: true},
    seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema({
    sort: {type: String}
});

entitySchema.pre('save', function(next) {
    var doc = this;
    counter.findByIdAndUpdateAsync({_id: 'entityId'}, {$inc: { seq: 1} }, {new: true, upsert: true}).then(function(count) {
        console.log("...count: "+JSON.stringify(count));
        doc.sort = count.seq;
        next();
    })
    .catch(function(error) {
        console.error("counter error-> : "+error);
        throw error;
    });
});

The options parameters gives you the result of the update and it creates a new document if it doesn't exist. You can check here the official doc.

And if you need a sorted index check this doc

@andrzej 2018-06-28 14:23:22

According to example: app.post(...) - where I want to use autoincrement - where I must insert this code and how it invoke?

@Akash Agarwal 2018-12-19 02:54:37

findByIdAndUpdateAsync is not a method in Mongoose's documentation. Even the link in the answer points to findByIdAndUpdate.

@mschwartz 2016-12-27 16:49:08

The answers seem to increment the sequence even if the document already has an _id field (sort, whatever). This would be the case if you 'save' to update an existing document. No?

If I'm right, you'd want to call next() if this._id !== 0

The mongoose docs aren't super clear about this. If it is doing an update type query internally, then pre('save' may not be called.

CLARIFICATION

It appears the 'save' pre method is indeed called on updates.

I don't think you want to increment your sequence needlessly. It costs you a query and wastes the sequence number.

@moorara 2015-11-19 17:48:44

You can use mongoose-auto-increment package as follows:

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

/* connect to your database here */

/* define your CounterSchema here */

autoIncrement.initialize(mongoose.connection);
CounterSchema.plugin(autoIncrement.plugin, 'Counter');
var Counter = mongoose.model('Counter', CounterSchema);

You only need to initialize the autoIncrement once.

@edtech 2015-05-11 09:58:03

Here is an example how you can implement auto-increment field in Mongoose:

var CounterSchema = Schema({
    _id: {type: String, required: true},
    seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema({
    testvalue: {type: String}
});

entitySchema.pre('save', function(next) {
    var doc = this;
    counter.findByIdAndUpdate({_id: 'entityId'}, {$inc: { seq: 1} }, function(error, counter)   {
        if(error)
            return next(error);
        doc.testvalue = counter.seq;
        next();
    });
});

@Drunken Master 2015-08-01 07:28:50

Where can I put this entitySchema.pre('save', callback); if I have each of my models in seperate files in a models folder?

@edtech 2015-08-05 22:19:26

You can put it right after declaring the Schema, in the same file

@realisation 2016-04-06 18:50:53

This is not a fool proof solution when it comes to concurrent requests -- it's possible that requests made very closely to one another will have the same number in the "sequence"

@Akos K 2016-04-08 08:35:00

@realisation findByIdAndUpdate according to mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate issues a mongodb findAndModify which is atomic.

@Sudhanshu Gaur 2017-07-17 11:56:21

Can you please tell me when there are multiple concurrent requests will your solution work or not ?? and if yes then how ??

@Ahmad Baktash Hayeri 2017-08-15 06:56:13

The findByIdAndUpdate will accept the id itself as the first parameter. Therefore, no need to provide a query object {_id: '<some value>'}

@Ahmad Baktash Hayeri 2017-08-15 07:41:08

Also note that in order to use the return value from the model, you need to pass in an options object with at least upsert: true set in case the model doesn't exist.

@Mankind1023 2018-05-03 21:45:24

I realize this is a bit old but, wouldn't this increment the counter when you do ANY updates to entitySchema (eg. update a status), making all references to the uniqe id useless? Maybe check isNew first.

@andrzej 2018-06-28 13:48:19

According to for example app.post(...) - where I want to use autoincrement - where I must insert this code and how it invoke?

@Chris B. 2019-06-01 00:58:41

This wonderful answer has ended my long nightmare.

@Jayadratha Mondal 2019-10-30 10:12:35

In counter schema should I not set unique:true for _id?

@edtech 2019-10-31 22:04:57

Of course, _id is considered to be unique.

Related Questions

Sponsored Content

1 Answered Questions

Mongoose pre.remove middleware of objects in array are never called

1 Answered Questions

[SOLVED] Include Mongodb "_id" field in projection with Mongoose

2 Answered Questions

[SOLVED] Node.js Mongoose .update with ArrayFilters

3 Answered Questions

[SOLVED] How to auto-increment id field of mongodb with golang mgo driver?

  • 2015-10-14 20:52:11
  • Vrushank Doshi
  • 4285 View
  • 3 Score
  • 3 Answer
  • Tags:   mongodb go mgo

2 Answered Questions

[SOLVED] How can i change _id field in MongoDB Collection to User_id?

1 Answered Questions

[SOLVED] Mongoose ODM: NumberInt is not defined

0 Answered Questions

mongoose auto increment multiple models

1 Answered Questions

Error when using $addToSet and $each, Mongo and Node JS

0 Answered Questions

Auto-Incrementing Sequence Field: Counters Collection vs Optimistic Loop

1 Answered Questions

Auto incrementing field in mongoose

Sponsored Content