top of page

Separate routing from business logic in node.js | Central response generation in node.js

Today we will learn how we can efficiently code request handling in a node js application server with many APIs. We will also learn how we can keep the code that executes the business logic separate from the request routing. The separation helps code modularity and enhances separation of concerns. In my post “What are routes in node.js ? How routes help writing modularized code ?” I explained how we can use routers to handle multiple APIs, each API family can be separated using a separate router. You can easily imagine when the actual business logic is implemented the inline-function in the routers that handles the request will be much bigger and complicated.

Firstly, as the number of APIs increase, keeping the routing logic and the business logic to process the requests in the same file is not going to scale and becomes cumbersome. We would like to separate the business logic from the routing logic. The router layer just should contain the routing logic and the business layer contains the business logic, the routing layer should use the business layer functions.

Secondly, in each function when we perform HTTP response generation we are repeating some of the logic necessary for response formulation. We should have a central method for request processing, used by all functions for all routers.

 

Content

 

How to separate routing logic from business logic?

Let’s update the code written in the post “What are routes in node.js ? How routes help writing modularized code ?” and address these issues.

No change needed in the package.json

{
  "name": "Test-App",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.17.1"
  },
  "engines": {
    "node": "^12.18.0",
    "npm": "^6.14.4"
  }
}

The Application Server

The file containing the application server has no change. The node-app-server.js is shown below

const express = require('express');
const app = express();
const port = 5000;
const userRouter = require('./routes/user.js')();
const orderRouter = require('./routes/order.js')();
app.use(userRouter);
app.use(orderRouter);
 
app.listen(port, () => {
  console.log(`My app listening at http://localhost:${port}`);
});

The JS files containing business logic

Next, we create a separate folder named “model” and create two files Order.js and User.js that contain the business logic for orders and users.

The model/Order.js

function Order() {
 
  return Object.freeze({
    fetch, create
  });
 
  async function fetch(request) {
    let response = {};
    response.body = { 'message': 'Hello World From Order Get ' +
      request.params.id };
    response.type = 'application/json';
    return response;
  }
   async function create(request) {
      return 'Hello World From Order Post ' + request.params.id;
   }
}
module.exports = Order;
 

The model/User.js

function User() {
 
  return Object.freeze({
    fetch, create
  });
 
  async function fetch(request) {
     return 'Hello World From User Get ' + request.params.id;
  }
 
  async function create(request) {
    return 'Hello World From User Post ' + request.params.id;
  }
}
module.exports = User;


The Routers

Now we will use these files from the model folder to the corresponding routers.

The routes/order.js

const router = require('express').Router();
const processResponse = require('../responseHandler')();
const Order = require('../model/Order')();
 
module.exports = function () {
  router.get('/order/:id', processResponse(Order.fetch));
  router.post('/order/:id', processResponse(Order.create));
  return router;
}

The routes/user.js

const router = require('express').Router();
const processResponse = require('../responseHandler')();
const User = require('../model/User')();
 
module.exports = function () {
  router.get('/user/:id', processResponse(User.fetch));
  router.post('/user/:id', processResponse(User.create));
  return router;
}

You can see the routers became very small and do not contain any business logic. You have seen I am using a processResponse method in the routers. This method will perform the response handling centrally. This is explained below.


How to perform Central response handling?

We write a new javascript file responseHandler.js that performs the request handling.

function parseResponse(response, defaultStatusCode = 200, defaultType = 'application/json') {
  const res = {
    body: response.body || response,
    statusCode: response.statusCode || defaultStatusCode,
    type: response.type || defaultType,
    headers: response.headers || undefined
  };
 
  if (!isNaN(res.body)) {
    res.body += '';
  }
 
  return res;
}
 
function processResponse() {
  return callback => process(callback);
}
 
function process(callback) {
  return async(request, response, next) => {
    try {
      const resp = parseResponse(await callback(request));
      response.set(resp.headers).status(resp.statusCode).type(resp.type)
                     .send(resp.body);
    } catch (error) {
      next(error);
    }
  }
}
 
module.exports = processResponse;

We are using this response handler in the routers to formulate the HTTP responses centrally in a unified way. For this reason each model does not have to formulate the HTTP responses. The models only contain business logic and do not have to code for generating suitable HTTP responses.


So, in this post today we have learned how we can separate business logic from routers in node.js and how to write a central response handling function.


Recent Posts

See All
bottom of page