I know that this question was asked many many times but i still can't find a solution
So this is my db
Product :
Ingredient :
Schemas :
ingredient.js
const mongoose = require("mongoose");
var IngredientSchema = new mongoose.Schema({
quantity: {
value: Number,
unit: String
},
comment: String,
product: { type: mongoose.Schema.Types.ObjectId, ref: "Product" }
});
module.exports = mongoose.model("IngredientSchema", IngredientSchema);
product.js
const mongoose = require("mongoose");
var ProductSchema = new mongoose.Schema({
name: {
type: String,
default: "",
trim: true,
required: "Name cannot be blank"
},
category: {
type: String,
default: "",
trim: true
},
convertion: [
{
unit_from: String,
unit_to: String,
value_from: Number,
value_to: Number
}
],
default_unit: String
});
const Product = (module.exports = mongoose.model(
"ProductSchema",
ProductSchema
));
this is the populate function :
ingredientRoutes.route("/:id").get(function(req, res) {
let id = req.params.id;
Ingredient.findById(id)
.populate("produit")
.exec()
.then(function(data) {
console.log(data.product, "***");
})
.catch(function(err) {
console.log(err);
});
});
this is the result that i'm getting :
just the id of the product without making the population
Any idea ?
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 }
});
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();
});
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
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();
});
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
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: {} });
Also you can change createdAt -> created_at by
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
If use update() or findOneAndUpdate()
with {upsert: true} option
you can use $setOnInsert
var update = {
updatedAt: new Date(),
$setOnInsert: {
createdAt: new Date()
}
};
For NestJs with Mongoose, use this
#Schema({timestamps: true})
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 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.
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}
);
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
You can use the timestamp plugin of mongoose-troop to add this behavior to any schema.
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'
});
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);
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.
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()
})
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
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
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
});
});
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();
});
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.
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;
});
I am trying to implement Auto increment in uisng mongoose.
But I am stuck.
Counter Schema
counter.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var counterSchema = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 1}
});
var Counter = module.exports = mongoose.model('Counter', counterSchema);
Product Schema
products.js
var productsSchema = new Schema({
productId: {type: String, require: false},
merchantId: {type: String, required: false}
)}
I have created counter collection and inserted one record inside it.
{
"_id" : "productId",
"sequence_value" : 1
}
Include method to increment the counter in the counter collection
//COUNTER COLLECTION
function getNextSequenceValue(sequenceName){
var sequenceDocument = Counters.findOneAndUpdate({
query:{_id: sequenceName },
update: {$inc:{sequence_value:1}},
new:true
});
return sequenceDocument.sequence_value;
}
Calling method to increment sequence number:
product.productId = getNextSequenceValue("productid");
But it's not working, nothing is getting saved in the products collection?
the next sequence should be
product.productId = getNextSequenceValue("productId"); // camelCase
in the counter collection you have added document with key productId (camelCase) but trying to get sequence with key productid (all lowercase)
mongo CLI
> function getNextSequenceValue(sequenceName){
...
... var sequenceDocument = db.counters.findOneAndUpdate(
... { "_id" : sequenceName },
... { $inc : { sequence_value : 1 } },
... { new : true }
... );
... return sequenceDocument.sequence_value;
... }
>
EDIT-2 with mongoose
var counterSchema = mongoose.Schema(
{
_id: { type: String, required: true },
sequence_value: { type: Number, default: 1 }
}
);
var Counters = mongoose.model('Counters', counterSchema);
var productsSchema = mongoose.Schema({
productId: {type: String, require: true},
merchantId: {type: String, required: false}
});
productsSchema.pre('save', function(next){
var doc = this;
Counters.findOneAndUpdate(
{ _id: 'productId' },
{ $inc : { sequence_value : 1 } },
{ new : true },
function(err, seq){
if(err) return next(err);
doc.productId = seq.sequence_value;
next();
}
);
}
);
var Product = mongoose.model('Product', productsSchema);
var testProduct = new Product({merchantId : 'test'})
testProduct.save(function (err, doc){
console.log('saved ' + doc )
})
output (with generated productId)
saravana#ubuntu:~/node-mongoose$ node app.js
`open()` is deprecated in mongoose >= 4.11.0, use `openUri()` instead, or set the `useMongoClient` option if using `connect()` or `createConnection()`. See http://mongoosejs.com/docs/connections.html#use-mongo-client
Mongoose: counters.findAndModify({ _id: 'productId' }, [], { '$inc': { sequence_value: 1 } }, { new: true, upsert: false, remove: false, fields: {} })
Mongoose: products.insert({ productId: '36', merchantId: 'test', _id: ObjectId("5a5b27b860716d24007df611"), __v: 0 })
saved { __v: 0,
productId: '36',
merchantId: 'test',
_id: 5a5b27b860716d24007df611 }
^C
saravana#ubuntu:~/node-mongoose$