208
votes

Is there a way to add created_at and updated_at fields to a mongoose schema, without having to pass them in everytime new MyModel() is called?

The created_at field would be a date and only added when a document is created. The updated_at field would be updated with new date whenever save() is called on a document.

I have tried this in my schema, but the field does not show up unless I explicitly add it:

var ItemSchema = new Schema({
    name    : { type: String, required: true, trim: true },
    created_at    : { type: Date, required: true, default: Date.now }
});
21
I did exactly what you did and it worked. Using Mongoose 4.8.4. Might be a new thing?martinedwards
this was from quite awhile ago.chovy
Yeah, thought it was worth noting that the above now works.martinedwards
how to update it if i have already some data?GvSharma

21 Answers

235
votes

As of Mongoose 4.0 you can now set a timestamps option on the Schema to have Mongoose handle this for you:

var thingSchema = new Schema({..}, { timestamps: true });

You can change the name of the fields used like so:

var thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });

http://mongoosejs.com/docs/guide.html#timestamps

270
votes

UPDATE: (5 years later)

Note: If you decide to use Kappa Architecture (Event Sourcing + CQRS), then you do not need updated date at all. Since your data is an immutable, append-only event log, you only ever need event created date. Similar to the Lambda Architecture, described below. Then your application state is a projection of the event log (derived data). If you receive a subsequent event about existing entity, then you'll use that event's created date as updated date for your entity. This is a commonly used (and commonly misunderstood) practice in miceroservice systems.

UPDATE: (4 years later)

If you use ObjectId as your _id field (which is usually the case), then all you need to do is:

let document = {
  updatedAt: new Date(),
}

Check my original answer below on how to get the created timestamp from the _id field. If you need to use IDs from external system, then check Roman Rhrn Nesterov's answer.

UPDATE: (2.5 years later)

You can now use the #timestamps option with mongoose version >= 4.0.

let ItemSchema = new Schema({
  name: { type: String, required: true, trim: true }
},
{
  timestamps: true
});

If set timestamps, mongoose assigns createdAt and updatedAt fields to your schema, the type assigned is Date.

You can also specify the timestamp fileds' names:

timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }

Note: If you are working on a big application with critical data you should reconsider updating your documents. I would advise you to work with immutable, append-only data (lambda architecture). What this means is that you only ever allow inserts. Updates and deletes should not be allowed! If you would like to "delete" a record, you could easily insert a new version of the document with some timestamp/version filed and then set a deleted field to true. Similarly if you want to update a document – you create a new one with the appropriate fields updated and the rest of the fields copied over.Then in order to query this document you would get the one with the newest timestamp or the highest version which is not "deleted" (the deleted field is undefined or false`).

Data immutability ensures that your data is debuggable – you can trace the history of every document. You can also rollback to previous version of a document if something goes wrong. If you go with such an architecture ObjectId.getTimestamp() is all you need, and it is not Mongoose dependent.


ORIGINAL ANSWER:

If you are using ObjectId as your identity field you don't need created_at field. ObjectIds have a method called getTimestamp().

ObjectId("507c7f79bcf86cd7994f6c0e").getTimestamp()

This will return the following output:

ISODate("2012-10-15T21:26:17Z")

More info here How do I extract the created date out of a Mongo ObjectID

In order to add updated_at filed you need to use this:

var ArticleSchema = new Schema({
  updated_at: { type: Date }
  // rest of the fields go here
});

ArticleSchema.pre('save', function(next) {
  this.updated_at = Date.now();
  next();
});
135
votes

This is what I ended up doing:

var ItemSchema = new Schema({
    name    : { type: String, required: true, trim: true }
  , created_at    : { type: Date }
  , updated_at    : { type: Date }
});


ItemSchema.pre('save', function(next){
  now = new Date();
  this.updated_at = now;
  if ( !this.created_at ) {
    this.created_at = now;
  }
  next();
});
112
votes

Use the built-in timestamps option for your Schema.

var ItemSchema = new Schema({
    name: { type: String, required: true, trim: true }
},
{
    timestamps: true
});

This will automatically add createdAt and updatedAt fields to your schema.

http://mongoosejs.com/docs/guide.html#timestamps

29
votes

If use update() or findOneAndUpdate()

with {upsert: true} option

you can use $setOnInsert

var update = {
  updatedAt: new Date(),
  $setOnInsert: {
    createdAt: new Date()
  }
};
29
votes

Add timestamps to your Schema like this then createdAt and updatedAt will automatic generate for you

var UserSchema = new Schema({
    email: String,
    views: { type: Number, default: 0 },
    status: Boolean
}, { timestamps: {} });

enter image description here
Also you can change createdAt -> created_at by

timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
8
votes

This is how I achieved having created and updated.

Inside my schema I added the created and updated like so:

   /**
     * Article Schema
     */
    var ArticleSchema = new Schema({
        created: {
            type: Date,
            default: Date.now
        },
        updated: {
            type: Date,
            default: Date.now
        },
        title: {
            type: String,
            default: '',
            trim: true,
            required: 'Title cannot be blank'
        },
        content: {
            type: String,
            default: '',
            trim: true
        },
        user: {
            type: Schema.ObjectId,
            ref: 'User'
        }
    });

Then in my article update method inside the article controller I added:

/**
     * Update a article
     */
    exports.update = function(req, res) {
        var article = req.article;

        article = _.extend(article, req.body);
        article.set("updated", Date.now());

        article.save(function(err) {
            if (err) {
                return res.status(400).send({
                    message: errorHandler.getErrorMessage(err)
                });
            } else {
                res.json(article);
            }
        });
    };

The bold sections are the parts of interest.

7
votes
var ItemSchema = new Schema({
    name : { type: String, required: true, trim: true }
});

ItemSchema.set('timestamps', true); // this will add createdAt and updatedAt timestamps

Docs: https://mongoosejs.com/docs/guide.html#timestamps

6
votes

In your model schema, just add an attribute timestamps and assign value true to it as shown:-

var ItemSchema = new Schema({
   name :  { type: String, required: true, trim: true },
},{timestamps : true}
);
4
votes

You can use the timestamp plugin of mongoose-troop to add this behavior to any schema.

3
votes

You can use this plugin very easily. From the docs:

var timestamps = require('mongoose-timestamp');
var UserSchema = new Schema({
    username: String
});
UserSchema.plugin(timestamps);
mongoose.model('User', UserSchema);
var User = mongoose.model('User', UserSchema)

And also set the name of the fields if you wish:

mongoose.plugin(timestamps,  {
  createdAt: 'created_at', 
  updatedAt: 'updated_at'
});
3
votes

For NestJs with Mongoose, use this

@Schema({timestamps: true})
2
votes

we may can achieve this by using schema plugin also.

In helpers/schemaPlugin.js file

module.exports = function(schema) {

  var updateDate = function(next){
    var self = this;
    self.updated_at = new Date();
    if ( !self.created_at ) {
      self.created_at = now;
    }
    next()
  };
  // update date for bellow 4 methods
  schema.pre('save', updateDate)
    .pre('update', updateDate)
    .pre('findOneAndUpdate', updateDate)
    .pre('findByIdAndUpdate', updateDate);
};

and in models/ItemSchema.js file:

var mongoose = require('mongoose'),
  Schema   = mongoose.Schema,
  SchemaPlugin = require('../helpers/schemaPlugin');

var ItemSchema = new Schema({
  name    : { type: String, required: true, trim: true },
  created_at    : { type: Date },
  updated_at    : { type: Date }
});
ItemSchema.plugin(SchemaPlugin);
module.exports = mongoose.model('Item', ItemSchema);
1
votes
const mongoose = require('mongoose');
const config = require('config');
const util = require('util');

const Schema = mongoose.Schema;
const BaseSchema = function(obj, options) {
  if (typeof(options) == 'undefined') {
    options = {};
  }
  if (typeof(options['timestamps']) == 'undefined') {
    options['timestamps'] = true;
  }

  Schema.apply(this, [obj, options]);
};
util.inherits(BaseSchema, Schema);

var testSchema = new BaseSchema({
  jsonObject: { type: Object }
  , stringVar : { type: String }
});

Now you can use this, so that there is no need to include this option in every table

1
votes

My mongoose version is 4.10.2

Seems only the hook findOneAndUpdate is work

ModelSchema.pre('findOneAndUpdate', function(next) {
  // console.log('pre findOneAndUpdate ....')
  this.update({},{ $set: { updatedAt: new Date() } });
  next()
})
1
votes

Since mongo 3.6 you can use 'change stream': https://emptysqua.re/blog/driver-features-for-mongodb-3-6/#change-streams

To use it you need to create a change stream object by the 'watch' query, and for each change, you can do whatever you want...

python solution:

def update_at_by(change):
    update_fields = change["updateDescription"]["updatedFields"].keys()
    print("update_fields: {}".format(update_fields))

    collection = change["ns"]["coll"]
    db = change["ns"]["db"]
    key = change["documentKey"]

    if len(update_fields) == 1 and "update_at" in update_fields:
        pass  # to avoid recursion updates...
    else:
        client[db][collection].update(key, {"$set": {"update_at": datetime.now()}})


client = MongoClient("172.17.0.2")
db = client["Data"]

change_stream = db.watch()

for change in change_stream:
    print(change)
    update_ts_by(change)

Note, to use the change_stream object, your mongodb instance should run as 'replica set'. It can be done also as a 1-node replica set (almost no change then the standalone use):

Run mongo as a replica set: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/

Replica set configuration vs Standalone: Mongo DB - difference between standalone & 1-node replica set

0
votes

Use a function to return the computed default value:

var ItemSchema = new Schema({
    name: {
      type: String,
      required: true,
      trim: true
    },
    created_at: {
      type: Date,
      default: function(){
        return Date.now();
      }
    },
    updated_at: {
      type: Date,
      default: function(){
        return Date.now();
      }
    }
});

ItemSchema.pre('save', function(done) {
  this.updated_at = Date.now();
  done();
});
0
votes

I actually do this in the back

If all goes well with the updating:

 // All ifs passed successfully. Moving on the Model.save
    Model.lastUpdated = Date.now(); // <------ Now!
    Model.save(function (err, result) {
      if (err) {
        return res.status(500).json({
          title: 'An error occured',
          error: err
        });
      }
      res.status(200).json({
        message: 'Model Updated',
        obj: result
      });
    });
0
votes

Use machinepack-datetime to format the datetime.

tutorialSchema.virtual('createdOn').get(function () {
    const DateTime = require('machinepack-datetime');
    let timeAgoString = "";
    try {
        timeAgoString = DateTime.timeFrom({
            toWhen: DateTime.parse({
                datetime: this.createdAt
            }).execSync(),
            fromWhen: new Date().getTime()
        }).execSync();
    } catch(err) {
        console.log('error getting createdon', err);
    }
    return timeAgoString; // a second ago
});

Machine pack is great with clear API unlike express or general Javascript world.

0
votes

if you'r using nestjs and @Schema decorator you can achieve this like:

@Schema({
  timestamps: true,
})

The timestamps option tells mongoose to assign createdAt and updatedAt fields to your schema. The type assigned is Date.

By default, the names of the fields are createdAt and updatedAt.

Customize the field names by setting timestamps.createdAt and timestamps.updatedAt.

-2
votes

You can use middleware and virtuals. Here is an example for your updated_at field:

ItemSchema.virtual('name').set(function (name) {
  this.updated_at = Date.now;
  return name;
});