Geek Culture
Published in

Geek Culture

Node:CRUD examples to demonstrate using a reusable helper function to execute promises in sequence

While working on a Node application, I came across multiple scenarios where tasks had to be executed in sequence. I would like to categorize them as follows:

  1. Task-1 and Task-2 execute in sequence with no dependency on each other with respect to data i.e Task-2 does not require result produced from execution of Task-1 to execute.
  2. Task-1 and Task-2 execute in sequence but Task-2 can execute only after the result is produced from the Task-1 execution.

In the first case, I prefer going for a common helper function that will execute the tasks in sequence and reduce the lines of code in the controller.

In the second case, the common helper function is of no use. We need to go for then-catch blocks OR await-async methods.

We shall explore both scenarios with an example.

This is the structure of the Node application. The Node application communicates with a NoSQL database MongoDB for all CRUD operations.

  1. The routes folder contains route files for each collection. Each file contains a router which receives HTTP requests from the client.
  2. The HTTP request is forwarded to the appropriate controller inside the controllers folder.
  3. The controller calls appropriate service inside the services folder to execute any query. The response is returned by the service to the controller.

The purpose of using a service is for reusing the query across multiple controllers. This ensures that 2 controllers don’t talk to each other and increase complexity.

4. The controller returns the response back to the client(eg:Angular,React etc) which updates the view.

Lets begin with the userRouter.js. This file actually contains many routes but for simplicity, I have just included 1 route in the below gist to demonstrate the 2 scenarios.

We have 2 collections: Users and Posts. A user can create a post,retrieve all posts created by the user and also delete all posts created by the user.

These actions are handled under the http://localhost:3000/user/:userId/posts route. Under this route we have defined the controller methods that will be called for each HTTP request type i.e GET,PUT,POST and DELETE.

The structure of the collection is not important here but it could give a better understanding of what the tasks will do in each scenario.

Below is a screenshot of a document in the Posts collection.

Document in the Posts collection

Below is the screenshot of a document in the Users collection.

Document in the Users collection

I. Scenario-1: Task-1 and Task-2 execute independently of each other but in sequence

The DELETE method is a example of this scenario. We need to perform 2 steps to delete all the posts created by the user.

=>Delete all the posts related to the user from the Posts collection.

=>We are storing the references of all the posts created by the user in a field(column in sql) called posts in the Users collection. You can see this field in the screenshot of the document in the Users collection.

Thus posts field is an array of references of all the posts created by the user. We need to set this posts field in the Users collection to [].

The userController.js is the controller file that handles all HTTP requests from all routes in the userRouter.js. As we have seen in the router file, deleteAllPostsOfUser() is the controller method that handles the DELETE request for the route.

There is a lot of code in the deleteAllPostsOfUser() controller method but most of it are out of scope of this story.

Below I have captured only the relevant portion of the controller method deleteAllPostsOfUser(). We have used a common helper function executeTasksInSequence() to execute the 2 tasks in order. These 2 tasks are actually methods defined in the UserService.js file and are of no concern to us.

try{
let result=await executeTasksInSequence([ removeAllPostsOfUser(req.params.userId,opts), updateUser(req.params.userId,{“posts”:[]},opts)
]);
}
catch(e){
console.log("Some error in the transaction.Aborting it");
return next(e);
}

Let’s get to the helper function defined in utility.js.

This method accepts an array of tasks to be executed in order. Each task returns a promise. The method uses the reduce method to execute them in order and concats the response of each task into an array. This array containing the responses of all the tasks in order is returned back to the controller for further action.

Below is the screenshot from Postman of how the response would like.

Any error incurred during the task execution will be caught in the catch block. The catch block will throw the error again. This error will be finally caught in the catch block of the controller method and returned back to the client.If one of the tasks fail, then the next task wouldn’t execute.

II.Scenario 2: Task-2 execution depends on the result of Task-1 execution.

The POST method is an example of this scenario, where the user creates a post. The 2 steps in this task are:

=>Create a post document in the Posts collection.

=>Update the posts field of the user document in the User’s collection with the created post’s reference.

Step-2 requires Step-1 to complete. In order to update the user document with created post’s reference, we first require the post to be created.

As mentioned in the userRouter.js, the POST request is handled by the createPost() controller method.

Again, this method has lot of code which is out of scope of this story.

Capturing only the relevant parts of the code. We have used await and async methods to execute the 2 tasks in sequence.

await executeTransaction(async(session)=>{
try{
let newPost=await createPost(payload,opts);
let updatedUser=await updateUserArray({“_id”:req.params.userId},{$push:{“posts”:newPost[0]._id}},opts);
}
catch(e){
console.log(“Caught in the controller catch block”);
return next(e)
}
})

newPost is the post document created in the Posts collection. We are using the newPost to push the reference of the post document into the posts field of the user document.

Creating this node application was a great learning experience for me.

I hope you found this story useful too.

You can check the entire code below.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
AngularEnthusiast

AngularEnthusiast

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.