What is the difference between Controllers and Services in Node REST API's?
If you've built out a REST API in Node (or other languages, for that matter), you've likely used the concept of "controllers" to help organize your application. Maybe you put your calls to your database or models there, called some other endpoints, and added some business logic to process the returns.
That controller is something that probably looks like this:
const registerUser = async (req, res, next) => {
const {userName, userEmail} = req.body
try {
// add user to database
const client = new Client(getConnection())
await client.connect()
await client.query(`INSERT INTO users (userName) VALUES ('${userName}');`)
await client.end()
// send registration confirmation email to user
const ses = new aws.SES()
const params = {
Source: sender,
Destination: {
ToAddresses: [
`${userEmail}`
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
}
await ses.sendEmail(params)
res.sendStatus(201)
next()
} catch(e) {
console.log(e.message)
res.sendStatus(500) && next(error)
}
}
But what you might not have used as much, or even heard of, is the concept of "services". Or maybe you have heard of the concept and heard that you should be using them, but are questioning what logic goes there compared to what goes in your controllers.
Using services in API's is something I don't often see in Node-land, but is such a powerful addition to the structure of your API that will make it much easier for testing, code organization, and code re-use.
So if they are such a helpful way of structuring your API, what exactly are services?
In order to answer this question, we'll go into what the differences between controllers and services are and what goes where so you can more properly structure your Node API's.
A Manager / Worker analogy
One of the most helpful ways I can think of to explain the differences between the two is by using an analogy from the business world - the "manager" / "worker" dichotomy. We'll be using simplified stereotypes of what a manager does and what a worker does - I'm in no way saying that all managers have one type of role and workers have another!
In our analogy, the controller is the manager, while the service is the worker.
If you think about what the manager's role is, he/she typically:
- manages the incoming work requests
- decides which worker should do the work
- splits up the work into sizable units
- passes that work off
- if the work requires multiple people working on multiple things, orchestrates the work
- but does not do the work himself/herself (again, using a basic stereotype here!)
And, a worker typically:
- receives the request from the manager
- figures out the individual details involved in completing the request
- is generally only concerned with the tasks he/she has to complete
- not responsible for making decisions about the "bigger" picture
- does the actual work necessary to complete the tasks/request
- returns the completed work to the manager
The overarching theme here is that the manager/controller receives the work, decides who should do it, then passes off the request to be completed. While the worker/service is the one that takes that request and actually completes it. And you maybe have multiple workers working on different requests/tasks that complete the bigger picture, which the manager joins together so it makes sense.
What logic goes where?
Using this analogy, let's look at controllers vs. service from a technical perspective:
A controller:
- manages the incoming work HTTP requests
- decides which worker what service should do the work
- splits up the work into sizable units
- passes that work the necessary data from the HTTP requests off to the service(s)
- if the work requires multiple people services working on multiple things, orchestrates the work those service calls
- but does not do the work himself/herself (again, using a basic stereotype here!) (not a stereotype here, the controller shouldn't be doing the work)
To sum up the above, the controller takes what it needs from Express (or whatever framework you're using), does some checking/validation to figure out to which service(s) should the data from the request be sent to, and orchestrates those service calls.
So there is some logic in the controller, but it is not the business logic/algorithms/database calls/etc that the services take care of. Again, the controller is a manager/supervisor.
And a service:
- receives the request data it needs from the manager in order to perform its tasks
- figures out the individual details algorithms/business logic/database calls/etc involved in completing the request
- is generally only concerned with the tasks he/she has to complete
- not responsible for making decisions about the "bigger" picture orchestrating the different service calls
- does the actual work necessary to complete the tasks/request
- returns the completed work a response to the manager
Now summing up the service, the service is responsible for getting the work done and returning it to the controller. It contains the business logic that is necessary to actually meet the requirements and return what the consumer of the API is requesting.
A note on what is meant by "business logic"
I like to think of business logic as the more "pure" form of logic. It is 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.
For example, if you had an API that returned how many users had been registered on your platform within the last X amount of days, the business logic here would be querying the database and doing any formatting of that data before it returned it to the controller.
Example of controller and service separation
Let's refactor our original controller-only code to look at an example of what this separation of concerns between controllers and services might look like:
First we'll pull out the logic for adding the user into a service.
Registration service:
const addUser = async (userName) => {
const client = new Client(getConnection())
await client.connect()
await client.query(`INSERT INTO users (userName) VALUES ('${userName}');`)
await client.end()
}
module.exports = {
addUser
}
Next we'll pull out the logic for sending a registration email to the user.
Email service:
const ses = new aws.SES()
const sendEmail = async (userEmail) => {
const params = {
Source: sender,
Destination: {
ToAddresses: [
`${userEmail}`
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
}
}
await ses.sendEmail(params)
}
module.exports = {
sendEmail
}
And finally, we'll greatly simplify the controller to simply make these two service calls:
const {addUser} = require('./registration-service')
const {sendEmail} = require('./email-service')
const registerUser = async (req, res, next) => {
const {userName, userEmail} = req.body
try {
// add user to database
await addUser(userName)
// send registration confirmation email to user
await sendEmail(userEmail)
res.sendStatus(201)
next()
} catch(e) {
console.log(e.message)
res.sendStatus(500) && next(error)
}
}
module.exports = {
registerUser
}
In summary
That about wraps it up. Hopefully now you have a better understanding of what logic goes in a controller vs. what goes in the service. The easy way of remembering it is:
- controller: managers/orchestrates the work
- service: executes the work
Separating like this becomes a powerful tool for code reuse and code organization. Try it out with the next REST API you're building and I think you'll find it helps a lot.
Want an Express REST API structure template that makes it clear where your different types of logic in addition to controllers and services should go? Sign up below to receive that template, plus a post explaining how that structure works / why it's setup that way so you don't have to waste time wondering where your code should go. You'll also receive all my new posts directly to your inbox!
Subscribe for the template!
No spam ever. Unsubscribe any time.