This post is originally written here with code snippets and images => https://easyontheweb.com/pre-and-post-hooks-in-mongoose/
In this article we’ll discuss about one of my favourite pieces of middleware in any framework or library – the “pre” and “post” middleware hooks in mongoose.
To be able to grasp whatever is in this article I hope you know the workings of MongoDB and mongoose as an ORM layer for NodeJS.
The pre and post hooks are something that amazed me a lot when I first discovered them in Ruby On Rails as active_record callbacks and I’ve been in love with them ever since. Recently I was implementing some password hashing for a project of mine where I used a pre hook and that is what inspired me to share this article today!
So, let us dive into what these middleware hooks are, how they are used, what are the best places to use them and also how you can benefit from them as opposed to implementing some other strategy for the same purpose.
What are pre and post hooks ?
According to the official mongoose documentation here – Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. Middleware is specified on the schema level and is useful for writing plugins.
Now, this isn’t something I would tell to someone if they asked me about pre or post hooks. What I would rather explain them as are functions that are executed before or after a certain action that you specify. Say, for example, you want to run a function everytime before you save a document in the DB, we would you a pre-hook for that. If you want something to be executed afterwards – we would use the post hook.
There are different kinds of middleware in mongoose but the two we will be focusing on to demonstrate the working of pre and post hooks is document middleware and query middleware. The other two just for your knowledge are aggregate and model middleware.
The document middleware works for a single document and mongoose methods like save, validate, updateOne, deleteOne and remove. Inside a hook that you write for these methods the this object would refer to the document. I think it is pretty evident from the name and the methods that document middleware is used when you want to do some kind of an action on just a single document in mongoDB before or after the methods mentioned above in italics.
When it comes to query middleware – the hooks that we’ll write for them would operate in bulk on all the documents that satisfy the query we write. The methods that we can add hooks to here are find , findOne, deleteMany, count, findOneAndUpdate, remove, update , updateMany.
There are methods like update,remove etc. that are both document and query middleware and are document middleware by default. (we’ll learn how we can specify them as query later)
The main crux of these hooks is that they ‘hook’ onto some other method and like a lifecycle method get executed on the documents just before (pre) or just after (post) the method that they are hooked to is called.
Also to remember is the fact that they are written on the schema level.
Using the pre-hook
As the name suggests, the pre-hook gets executed just before some other method is going to be executed on some document/documents .
There may be many use cases for pre-hooks, maybe you have a field called archived in your schema and you want to ignore all the archived documents in every find call. So, what would you do ? Will you include the filter for archived: false everywhere there is a find for that model ? What if you forget to do that some place ? This is a great use case of a pre-hook as a query middleware. Let us see how we can do this easily :-
pre-hook example in mongoose find
This is a very very simple hook on the find method that just modifies the query and adds an additional filter. This additional filter will be added to each and every find query that is run on this model. Super useful, right?
Another use case where I often see pre-hook used is removing dependent documents. For eg – a user is being deleted so in the pre-hook for remove on the user model we might want to archive or delete all the posts that the user made ! Same way you can think of a lot of different use cases for the pre-hook just by knowing that it will be executed before the method it is hooked onto.
Another thing that is worth noting is that if by any chance there is an error in the pre-hook, the subsequent method that was supposed to run will not be run. You can throw an error in many ways in the pre-hook or even just return a Promise rejected. Both these things will stop the execution and even the method the pre-hook was hooked onto will not be executed.
Using the post-hook
What do you think, when do the post-hooks get called ? I guess it’s not difficult to figure out that the post hooks are called once all the pre-hooks have been executed, then the original method has been executed.
pre-hooks -> method -> post-hooks
This is the order of execution when it comes to mongoose. To be honest there is nothing new to tell in post hooks as they work similarly to pre-hooks and the syntax and small nuances are described by the official docs much better than I can.
The only thing I can re-iterate on is the fact that pre, then the method and then the post hooks. Getting confused about the order of execution can lead to unwanted surprises. And we do not like unwanted surprises as developers, do we ?
NOTE : Both pre and post hooks need to be added to the schema before registering the model in the file.
As mentioned before there are certain actions which come in the category of both document and query middleware. ‘remove’ is such an example. The thing to remember is that the middleware (with naming conflicts ) are document by default and to use them as query hooks we need to pass an options object.
example from the official docs
Here, we see we have explicitly mentioned how we want to use the pre-hook we have written by passing an additional options object with the type of middleware as true.
The passing of this additional options object helps mongoose in hooking our custom written hook to the correct method, i.e, Document#remove or Query#remove in this case. Again, that will decide the value of this inside the hook – which is basically what we work with when we write hooks.
I hope in this article you understood when and how we can use the powerful mechanism of pre and post hooks in mongoose and how using them is way more beneficial than having to write a certain piece of logic everywhere when we do some kind of an action on a document of a collection.
Therefore, using these hooks we write less code and therefore reduce the surface area of potential bugs. Also, we free ourselves from the mental burden of not forgetting a particular logic to execute before or after a method every time.
Personally, I’m a big fan of hooks and I think they are great and have multiple use cases based on your application and I’m sure you’ll be now able to figure out some use cases for your app.
If you want to join a growing Facebook group for web developers including me -> https://www.facebook.com/groups/503230450489995 .
Also, if you want to learn more about MongoDB and NodeJs please check the dedication section here => https://easyontheweb.com/category/mongo/