Services in NodeJS

In this blog, I’m taking you back to my early Node.js days. I had this small app that simply displayed a list of users from a JSON file. The controller was neat and tidy—reading the file, filtering data, and handling basic sorting.

But as the project grew, so did the requirements. Suddenly, I needed to filter by age, sort by name, and add a search feature. What was once clean code turned into a tangled mess of logic. Debugging became a challenge, testing was a headache, and reusing any part of that controller felt impossible.

This experience taught me a valuable lesson in code organization, leading me to separate business logic from controllers—a game changer for maintainability. Today, I’ll show you why that change mattered and how to make it happen.


Scene 1: The Messy Controller

“Let’s see what that initial controller might have looked like, all in one place.”

// controllers/userController.js (Before Refactoring)

const fs = require(‘fs’);

const path = require(‘path’);

const getUsers = (req, res) => {

  // Reading from a JSON file (our simple “database”)

  const filePath = path.join(__dirname, ‘../data/users.json’);

  const data = fs.readFileSync(filePath, ‘utf-8’);

  let users = JSON.parse(data);

  // Suppose we have a query to filter users by age

  if (req.query.age) {

    users = users.filter(user => user.age === parseInt(req.query.age));

  }

  // And another query to sort users by their username alphabetically

  if (req.query.sortBy === ‘username’) {

    users.sort((a, b) => a.username.localeCompare(b.username));

  }

  // Send back the list of users

  res.json(users);

};

module.exports = { getUsers };

“In this controller, you see everything is happening here: reading data, filtering, sorting, and returning the response. As the app grows, imagine adding more functionality (like pagination, additional filters, etc.) to this same controller— things can quickly turn messy.”


Scene 2: What’s the Problem?

“Having all this logic in the controller means:

  • Harder to Test: You can’t easily test the logic for filtering or sorting without making an HTTP request.
  • Less Reusable: If you need similar logic elsewhere, you’d have to copy this code.
  • Cluttered Code: The controller should simply connect HTTP requests to functions that perform specific business logic, not handle all the operations directly.”

Scene 3: Enter the Service

“To solve this, I decided to extract the filtering and sorting logic into its own file—a service. By doing so, the controller becomes focused only on handling the request and sending the response, while the service takes care of processing the data.”


Creating the Service File

// services/userService.js

const fs = require(‘fs’);

const path = require(‘path’);

const filePath = path.join(__dirname, ‘../data/users.json’);

const getAllUsers = () => {

  const data = fs.readFileSync(filePath, ‘utf-8’);

  return JSON.parse(data);

};

const filterAndSortUsers = (users, query) => {

  // Filtering logic (e.g., by age)

  if (query.age) {

    users = users.filter(user => user.age === parseInt(query.age));

  }

  // Sorting logic (e.g., by username)

  if (query.sortBy === ‘username’) {

    users.sort((a, b) => a.username.localeCompare(b.username));

  }

  return users;

};

module.exports = {

  getAllUsers,

  filterAndSortUsers

};

“In our userService.js file, we have two functions:

  • getAllUsers() reads and parses the JSON file.
  • filterAndSortUsers() applies filtering and sorting based on the query parameters.

This separation means we can now test and modify our business logic independently from our HTTP handling.”


Scene 4: A Clean Controller

“With the logic extracted, our controller is now simpler and easier to understand.”

// controllers/userController.js (After Refactoring)

const userService = require(‘../services/userService’);

const getUsers = (req, res) => {

  // Get the full list of users from our service

  let users = userService.getAllUsers();

  // Use the service to apply filtering and sorting based on the query parameters

  users = userService.filterAndSortUsers(users, req.query);

  // Send back the filtered and sorted list of users

  res.json(users);

};

module.exports = { getUsers };

“Notice how the controller now only focuses on:

  • Receiving the HTTP request
  • Delegating work to the service
  • Sending back the HTTP response

This separation of concerns makes your code cleaner, more testable, and more maintainable.”


Wrapping Up the Story

“So, to recap:

  1. The Problem: A controller loaded with business logic (reading, filtering, sorting) gets cluttered and hard to maintain.
  2. The Service: We move that logic into its own file, where it can be reused, easily tested, and managed independently.
  3. The Clean Controller: Now the controller just coordinates the request and response while the service does the heavy lifting.

This approach is a stepping stone toward writing cleaner and more modular Node.js applications, even with basic projects.”


Conclusion

“Today, we’ve looked at how splitting the business logic from the controller into a service can make your code simpler and more scalable. In future lessons, we can build on this by introducing asynchronous operations and a real database.”

Feel free to ask if you need any further details or a deeper dive into any of these concepts!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *