250

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 }
});
turivishal
  • 34,368
  • 7
  • 36
  • 59
chovy
  • 72,281
  • 52
  • 227
  • 295

22 Answers22

317

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();
});
Pavel Nikolov
  • 9,401
  • 5
  • 43
  • 55
  • 3
    How do you do that in Mongoose/node.js? – Zebra Propulsion Lab Sep 16 '13 at 20:44
  • But if i want to pass the create at date to the view, i would need to set special variable for this, or pass the id right? –  Nov 09 '15 at 20:04
  • That's very valuable information! but i have one worry, with this method (immutable data), the database, will grow big very fast! especially in an application where updates occur a lot! – Xsmael Jun 18 '16 at 12:47
  • @Pavel Nikolov what about a semi immutable data approach ? in the sense that for a year data can be updated, and after each year each update will rather be an insertion, so as to keep one version of the data per year, or month, depends... – Xsmael Jun 18 '16 at 13:15
  • @Xsmael why do you want to do that? If your data is being updated/deleted too often and you are worried of your DB growing rapidly maybe you should consider another architecture. I do not see any other reason not to use lambda architecture for all records. Append-only DB allows you to easily debug application flow or rollback changes should something go wrong. If you want to protect data from being updated after 1 year – you need to either update your application logic or move the data to read-only DB. Using two separate approaches with the same DB can be very confusing and hard to maintain. – Pavel Nikolov Jun 20 '16 at 12:21
  • @PavelNikolov Alright, but what do you call " lambda architecture" ? it is the immutable data ? do you mean that it is the best way to go for databases? – Xsmael Jun 21 '16 at 18:24
  • @Xsmael Description from Wikipedia: __Lambda architecture depends on a data model with an append-only, immutable data source that serves as a system of record. It is intended for ingesting and processing timestamped events that are appended to existing events rather than overwriting them. State is determined from the natural time-based ordering of the data.__ It is definitely not a silver bullet but I think that it is probably the best option for the majority of modern web/distributed applications. https://en.wikipedia.org/wiki/Lambda_architecture – Pavel Nikolov Jun 22 '16 at 01:58
  • 3
    I appreciate the initial answer but advocating for using lambda architecture also sounds like a great way to solve 5x levels of meta problems and possibly never deliver rather than building the CRUD app and keeping it simple unless the complexity of this approach is truly warranted. Remember you are posting about MongoDB which 'barely' scales out to begin with and you are advocating for a new record for every write which will quickly kill Mongo at any meaningful scale. Move this to the Cassandra section... – John Culviner Apr 28 '17 at 20:32
  • @JohnCulviner I agree with you. I do not use Lambda Architecture for every project, only where it makes sense, and I do not use Mongo where high scalability is required (in many cases it is not). However, I've used lambda architecture with Mongo and the added complexity has paid off every single time. Being able to easily trace all the changes of a record or rollback a change has been invaluable. In some cases, this additional complexity will not be needed and will actually slow the development down. – Pavel Nikolov May 02 '17 at 02:38
  • @PavelNikolov totally agree. I've been logging everything that comes through the server (non sensitive) with a couple line middleware to log it to elasticsearch. Still using mongo for writes and updating record but I have a full "event store" in elasticsearch should I need to rebuild the database (or find out how the hell something happened). Def agree that is useful to have! – John Culviner May 03 '17 at 15:56
  • Thanks ! This was the answer I was looking for! I wanted it to be automatically handled by driver as in other frameworks I used like RoR. I hope OP would consider changing accepted answer. – Kunok Sep 10 '17 at 20:53
  • 1
    I actually changed my backend design in order to perform soft deletes in place of real deletes, thanks to the information provided in this answer. It provides some deeper knowledge that is really useful. – Kenna Apr 07 '20 at 15:44
267

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

Binarytales
  • 9,468
  • 9
  • 32
  • 38
137

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();
});
user456584
  • 86,427
  • 15
  • 75
  • 107
chovy
  • 72,281
  • 52
  • 227
  • 295
  • 9
    1. Store the current time in a local var and assign it instead of each time calling new Date(), this will make sure that at first pass created_at and updated_at have the same excect value. 2. new Date => new Date() – Shay Erlichmen Oct 27 '13 at 09:22
  • 13
    Would just like to point out that if you use ObjectId then you can get the created_at from there....you do not need a separate field. Check out `getTimestamp()` – Xerri Nov 26 '13 at 15:58
  • 6
    Other option for the created_at could be to change the model to -> created_at: { type: Date, default: Date.now }, – OscarVGG Jan 19 '14 at 04:47
  • Can you add this to all models at a higher level? – ajbraus May 01 '15 at 15:46
  • 1
    @ajbraus Make a schema plugin – wlingke Jul 03 '15 at 16:48
  • like @wlingke you can use mongoose plugin to avoid redundancy, @ ajbraus check this: https://github.com/james075/mongoose-createdat-updatedat – james075 Aug 10 '15 at 19:50
  • 3
    also use `Date.now()` where possible instead of `new Date` its faster as it is a static method – karlkurzer Feb 05 '16 at 23:27
  • 1
    looks like this is outdated, as you can now simply set `timestamps: true` as an option and they will be created automatically. see @Binarytales' answer below – pedrotp Feb 11 '16 at 16:50
119

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

mcompeau
  • 1,221
  • 1
  • 9
  • 4
  • 2
    Requires mongoose version >= 4.0 – Jensen Oct 19 '16 at 13:23
  • 2
    I find the docs unclear - this adds the fields, but does it also make sure they're updated on every update? – Ludwik Nov 04 '16 at 09:34
  • generally, createdAt is always only stored once...when object is created. `updatedAt` is updated on each new save (when the obj is changed) – codyc4321 Dec 02 '16 at 01:05
  • 1
    I got this "2016-12-07T11:46:46.066Z".. Could please explain time stamp format, how change time zone?? is take mongoDB server time zone?? – Mahesh K Dec 07 '16 at 11:52
  • @mcompeau, Thank you for the answer! I know it's a long shot, but do you remember by any chance if fields created this way can be used as index? – manidos Aug 25 '17 at 18:31
35

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' }
Linh
  • 57,942
  • 23
  • 262
  • 279
30

If use update() or findOneAndUpdate()

with {upsert: true} option

you can use $setOnInsert

var update = {
  updatedAt: new Date(),
  $setOnInsert: {
    createdAt: new Date()
  }
};
Roman Rhrn Nesterov
  • 3,538
  • 1
  • 28
  • 16
17

In your model :

const User = Schema(
  {
    firstName: { type: String, required: true },
    lastName: { type: String, required: true },
    password: { type: String, required: true }
  },
  {
    timestamps: true
  }
);

And after that your model in collection would be like this :

{
    "_id" : ObjectId("5fca632621100c230ce1fb4b"),
    "firstName" : "first",
    "lastName" : "last",
    "password" : "$2a$15$Btns/B28lYIlSIcgEKl9eOjxOnRjJdTaU6U2vP8jrn3DOAyvT.6xm",
    "createdAt" : ISODate("2020-12-04T16:26:14.585Z"),
    "updatedAt" : ISODate("2020-12-04T16:26:14.585Z"),
}
Kaveh Naseri
  • 1,102
  • 2
  • 15
  • 24
15

For NestJs with Mongoose, use this

@Schema({timestamps: true})
aamitarya
  • 719
  • 6
  • 11
11

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.

rOOb85
  • 163
  • 3
  • 10
9

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}
);
turivishal
  • 34,368
  • 7
  • 36
  • 59
Pavneet Kaur
  • 729
  • 7
  • 5
  • What will this do? – chovy Dec 18 '19 at 01:02
  • Timestamp attribute will add created_at and updated_at fields in each document. Created_at field indicates the time of creation of document and updated_at field indicates the time of updation if any else the creation time of document. Both fields value is in ISO time format. Hope this clear your doubts Chovy. – Pavneet Kaur Dec 23 '19 at 19:57
  • Can I change timestamps createdAt and updatedAt format is like (YYYY-MM-DD HH:mm:ss) – Ravindra S. Patil Jun 25 '21 at 05:31
  • I would say use moment for this format. Else you can add attributes like createdAt: new Date() – Pavneet Kaur Jun 25 '21 at 14:33
8
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

5

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

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
4

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'
});
Orr
  • 4,740
  • 3
  • 28
  • 31
4

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);
Shaishab Roy
  • 16,335
  • 7
  • 50
  • 68
4

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.

M.R.Safari
  • 1,857
  • 3
  • 30
  • 47
3

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()
})
John Xiao
  • 1,680
  • 2
  • 18
  • 24
2
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

Raghu
  • 101
  • 1
  • 3
2

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

Rea Haas
  • 2,018
  • 1
  • 16
  • 18
1

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
      });
    });
shuk
  • 1,745
  • 1
  • 14
  • 25
0

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();
});
orourkedd
  • 6,201
  • 5
  • 43
  • 66
  • no need to wrap `Date.now()` in a function just do: `...default: Date.now()` – karlkurzer Feb 05 '16 at 23:23
  • 1
    I wrap it in a function so I can mock '.now()' in tests. Otherwise it's only run once during initialization and the value can't easily be changed. – orourkedd Feb 05 '16 at 23:28
  • 1
    Note that `default: Date.now()` would be wrong. If anything it's `default: Date.now`. Otherwise all your documents will have the same timestamp: The time when your application started ;) – Max Truxa Aug 09 '16 at 18:11
  • I like your `default: Date.now` strategy. much cleaner. – orourkedd Aug 10 '16 at 04:13
0

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.

Piyush Patel
  • 1,646
  • 1
  • 14
  • 26
-3

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;
});
zemirco
  • 16,171
  • 8
  • 62
  • 96
  • When would this actually get set? and will it persist? So 3 days from now, it would still have a date from 3 days ago? – chovy Oct 01 '12 at 09:21
  • the virtual will be called whenever you change the given property, in this case `name`. And yes, it should be persistent. – zemirco Oct 01 '12 at 09:29
  • would this work for all fields on Item object? I"m not sure this solution does what I want – chovy Oct 03 '12 at 09:41