ChatGPT解决这个技术问题 Extra ChatGPT

add created_at and updated_at fields to mongoose schemas

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 }
});
I did exactly what you did and it worked. Using Mongoose 4.8.4. Might be a new thing?
this was from quite awhile ago.
Yeah, thought it was worth noting that the above now works.
how to update it if i have already some data?

P
Pavel Nikolov

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();
});

How do you do that in Mongoose/node.js?
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?
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!
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...
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.
B
Binarytales

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


Agreed, this is the best option in Mongoose 4.0.
In react-admin list, do I find this through via prop createdAt, created_at, timestamps.createdAt or timestamps.created_at?
Can I change timestamps createdAt and updatedAt format is like (YYYY-MM-DD HH:mm:ss.)
u
user456584

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();
});

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()
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()
Other option for the created_at could be to change the model to -> created_at: { type: Date, default: Date.now },
@ajbraus Make a schema plugin
also use Date.now() where possible instead of new Date its faster as it is a static method
m
mcompeau

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


Requires mongoose version >= 4.0
I find the docs unclear - this adds the fields, but does it also make sure they're updated on every update?
generally, createdAt is always only stored once...when object is created. updatedAt is updated on each new save (when the obj is changed)
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??
@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?
L
Linh

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: {} });

https://i.stack.imgur.com/pLmsj.png

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

Can I change timestamps createdAt and updatedAt format is like (YYYY-MM-DD HH:mm:ss)
R
Roman Rhrn Nesterov

If use update() or findOneAndUpdate()

with {upsert: true} option

you can use $setOnInsert

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

If you are using Mogo's default _id field, then you do not need a createdAt field. Check my answer below for more details.
... check my ORIGINAL ANSWER.
a
aamitarya

For NestJs with Mongoose, use this

@Schema({timestamps: true})

Thanks, I was looking for this.
K
Kaveh Naseri

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"),
}

This helped me solve my challenge.
r
rOOb85

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.


t
turivishal

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}
);

What will this do?
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.
Can I change timestamps createdAt and updatedAt format is like (YYYY-MM-DD HH:mm:ss)
I would say use moment for this format. Else you can add attributes like createdAt: new Date()
M
Mykola Riabchenko
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


How do you add Index on createdAt field?
How Can I change timestamps createdAt and updatedAt format is like (YYYY-MM-DD HH:mm:ss)
J
JohnnyHK

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


O
Orr

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'
});

S
Shaishab Roy

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);

M
M.R.Safari

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.


J
John Xiao

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()
})

R
Raghu
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


R
Rea Haas

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


s
shuk

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
      });
    });

o
orourkedd

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();
});

no need to wrap Date.now() in a function just do: ...default: Date.now()
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.
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 ;)
I like your default: Date.now strategy. much cleaner.
P
Piyush Patel

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.


z
zemirco

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;
});

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?
the virtual will be called whenever you change the given property, in this case name. And yes, it should be persistent.
would this work for all fields on Item object? I"m not sure this solution does what I want