In both our movie and actor schemas, we used a plugin called generateId()
.
While MongoDB automatically generates ObjectID
for each document using the _id
field, we want to generate our own IDs that are more human readable and hence friendlier. We also would like to give the user the opportunity to select their own id of choice.
However, being able to choose an id can cause conflicts. If you were to choose an id that already exists, your POST
request would be rejected. We should autogenerate an ID if the user does not pass one explicitly.
Without this plugin, if either an actor or a movie is created without an explicit ID passed along by the user, the server would complain since the ID is required.
We can create middleware for Mongoose that assigns an id
before we persist the object as follows:
// /src/models/plugins/generateId.js module.exports = function() { return function generateId(schema){ schema.pre('validate',function(next, done) { var instance = this; var model = instance.model(instance.constructor.modelName); if( instance.id == null ) { model.findOne().sort("-id").exec(function(err,maxInstance) { if (err){ return done(err); } else { var maxId = maxInstance.id || 0; instance.id = maxId+1; done(); } }) } else { done(); } }) } };
There are a few important notes about this code.
See what we did to get the var
model? This makes the plugin generic so that it can be applied to multiple Mongoose schemas.
Notice that there are two callbacks available: next
and done
. The next
variable passes the code to the next pre-validation middleware. That's something you would usually put at the bottom of the function right after you make your asynchronous call. This is generally a good thing since one of the advantages of asynchronous calls is that you can have many things running at the same time.
However, in this case, we cannot call the next
variable because it would conflict with our model definition of id required. Thus, we just stick to using the done
variable when the logic is complete.
Another concern arises due to the fact that MongoDB doesn't support transactions, which means you may have to account for this function failing in some edge cases. For example, if two calls to POST
/actor
happen at the same time, they will both have their IDs auto incremented to the same value.
Now that we have the code for our generateId()
plugin, we require it in our actor and movie schema as follows:
var generateId = require('./plugins/generateId'), actorSchema.plugin(generateId());