Should one Express controller call another?

When you're working on an Express REST API, you may run into a situation where you need to make a call to fetch some data / do some things from your controller and then take that data and do some more things with it...

...and it just so happens you have another controller that returns that data you need / does those same things already.

So naturally the question arises, should you make that controller to controller call?

I mean, if it has what you need... why not, right?

(Spoiler alert: no you shouldn't, but let's look at it in more detail to examine why)

Different scenarios...

There could be a couple reasons why you've found yourself here:

  1. not leveraging services to contain your business logic
  2. you are leveraging services, but you have some leftover business logic in your controller
  3. both controllers have the exact same sequence of service calls (medium to show)

1. Not leveraging services

"Service" is the word you'll most commonly see, but we could call it whatever. What matters here is that a service provides the ability to separate your business logic from the orchestration logic.

Business logic (what goes in the service): logic that doesn’t (usually!) care about validating the request or handling anything framework-specific. It just handles algorithms/rules for processing data, storing of data, fetching data, formatting that data, etc. These rules are usually determined by business requirements.

Orchestration logic (what goes in the controller): takes the incoming HTTP request, does some checking/validation to figure out which service(s) the data from the request should be sent to, and orchestrates those service calls.

So if you currently have all your business logic in your controllers, refactor that out into services.

Back to our original question - once that code is in a service, your controller won't need to call that other controller at all. You can just call the service from the controller instead!

Imagine you have a controller that looks like this:

const registerUser = async (req, res, next) => {
  const {userName, userEmail} = req.body
  try {
    // business logic
    // add user to database
    const client = new Client(getConnection())
    await client.connect()
 
    await client.query(`INSERT INTO users (userName) VALUES ('${userName}');`)
    await client.end()
 
    // business logic
    // send registration confirmation email to user
    const ses = new aws.SES()
 
    const params = {...}
 
    await ses.sendEmail(params) 
 
    res.sendStatus(201)
    next()
  } catch(e) {
    console.log(e.message)
    res.sendStatus(500) && next(error)
  }
}

If you have another controller that also needs to add a user to the database - you might be tempted to call the function that belongs to this controller, from that other controller.

Not only would you have to deal with sending the Express req object through from one controller to the other, this results in very tight coupling. Not to mention, you might not need to send the email that the controller function above does... you might just want to add the user.

If we pulled out the code for adding a user to the database into a "User Service" file, and the code for sending an email into an "Email Service" file, we could reuse those services across whatever controllers we wanted.

And controllers wouldn't need to talk to each other at all!

2. Leftover business logic in the controller

Now that the previous scenario has been addressed, this one is easy. If you already are using services to separate out your business logic, then great!

If you are still running into a scenario where you are thinking of calling one controller from another - then maybe you just have some lingering business logic that should be moved out of your controller into a service that can easily be reused / called by many different controllers.

So move that business logic out into a service, call that service from your controller, and you should be good to go!

3. Same sequence of service calls

Finally, you might be considering calling one controller from another if they both share the same sequence of service / business logic.

In this case, consider if that sequence of business logic belongs in a "higher-level" service, especially if that sequence of service calls contains a lot of calls. If they are literally the same controller, that means the business logic may be able to be grouped together into a cohesive service.

Especially if it's data you're returning. That's a sign that you have a "domain object", and that should be reusable.

On the other hand, controllers are going to occasionally have some overlap with other controllers in the services that they call. If these two controllers in question are only calling a couple services, like adding a user to a database and sending a confirmation email, it's ok to "repeat yourself" between those controllers.

Yeah, I know - it might feel weird to repeat yourself when it's been drilled into every developer's head to be DRY...

But in this case it's fine.

Wrapping up

Figuring out:

  • what type of code goes where (should controllers call each other, for example)
  • how to architect your REST API
  • and how to structure it...

those are things it seems all Node developers struggle with at some point. Especially since Node and Express aren't very opinionated and don't enforce a strict set of conventions... leaving it up to you to figure out the best practices.

If you are still struggling with this, I have a standard template I use to structure all my REST API'S - sign up below to receive the template repo and - more importantly - a post explaining in detail what logic goes where within that structure. You'll also receive all my future posts directly to your inbox!

Subscribe for the repo and explanation!

No spam ever. Unsubscribe any time.