Express Middlewares and Higher Order Functions, part 2

Warning! We're going to see some Functional Programming concepts like pure functions, side effects etc. and obviously higher order functions.

Recap
In the last post, we saw how we can create a middleware wrapper that can wrap any function and make it an express middleware.
//withMiddleware.js

//Higher Order Function
const withMiddleware = (func) => {
  return (req, res, next) => {
    func(); //func has the EXECUTION LOGIC
    next();
  }    
}

module.export = withMiddleware;
And we can use it in this way
const withMiddleware = require('./withMiddleware');

const funcOne = () => {
  //YOUR BUSINESS LOGIC
}

const funcTwo = () => {
  //YOUR ANOTHER BUSINESS LOGIC
}

const middlewareOne = withMiddleware(funcOne);
const middlewareTwo = withMiddleware(funcTwo);

//use this middlewares in a standard way.
app.get("/path", middlewareOne, middlewareTwo, (req, res)=>{
  //further execution, if any
  res.send({});
})
We had a few unanswered questions
  • Passing data between middlewares? Although we solved it in the first post, see the PS: section, but how can we make it work effortlessly.
  • What if I'm writing auth or validation middlewares, that needs access to the request object?
  • How will we handle the async middlewares?
  • Logging. The generic activities of the middlewares needs to be logged. Like, in case of async middleware, that fetches data over another API, or fetches data from a DB, the time taken for the async operation needs to be logged.
  • Let's get started...
    1. Passing data between middlewares
    The most effortless way of passing data between middlewares is using the res.locals object. We're going to use the same.
    //withMiddleware.js
    
    //Higher Order Function
    const withMiddleware = (func) => {
      return (req, res, next) => {
        // changed to this
        const response = func();
        if (response) {
          res.locals[`${func.name}Response`] = response; //Magic
        }
        next();
      }    
    }
    
    module.export = withMiddleware;
    and when we finally use it
    const withMiddleware = require('./withMiddleware');
    
    const funcOne = () => {
      //YOUR BUSINESS LOGIC
      return true; //Functions returning now
    }
    
    const funcTwo = () => {
      //YOUR ANOTHER BUSINESS LOGIC
      return true; // Functions returning now
    }
    
    const middlewareOne = withMiddleware(funcOne);
    const middlewareTwo = withMiddleware(funcTwo);
    
    //use this middlewares in a standard way.
    app.get("/path", middlewareOne, middlewareTwo, (req, res)=>{
      //further execution, if any
      const {funcOneResponse, funcTwoResponse} = res.locals;
      if(funcOneResponse && funcTwoResponse){
        res.send("Aal izz well");
      } else {
        res.status(400).send('Bad Request')
      }
    })
    2. Access to request and response object
    Ok, now we need to give access to the request object. Lets make one more modification to our Higher order function.
    //withMiddleware.js
    
    //Higher Order Function
    const withMiddleware = (func) => {
      return (req, res, next) => {
        // changed to this
        const response = func(req, res); //Pass the req, res, but not next
        if (response) {
          res.locals[`${func.name}Response`] = response;
        }
        next();
      }    
    }
    
    module.export = withMiddleware;
    And how are we going to use it?
    const withMiddleware = require('./withMiddleware');
    
    //Function now has access to req and res
    const funcOne = (req, res) => {
      if(req.body.username && req.body.password){
        return true; 
      } else {
        return false;  
      }
    }
    
    // Function may chose to ignore req and res
    const funcTwo = () => {
      //YOUR ANOTHER BUSINESS LOGIC
      return true; // Functions returning now
    }
    
    const middlewareOne = withMiddleware(funcOne);
    const middlewareTwo = withMiddleware(funcTwo);
    
    //use this middlewares in a standard way.
    app.post("/path", middlewareOne, middlewareTwo, (req, res)=>{
      //further execution, if any
      const {funcOneResponse, funcTwoResponse} = res.locals;
      if(funcOneResponse && funcTwoResponse){
        res.send("Aal izz well");
      } else {
        res.status(400).send('Bad Request')
      }
    })
    What if my function is an async function
    Well, there is no easy answer to this. We need a different Higher Order Function to handle such cases
    //withMiddleware.js
    
    //Higher Order Function
    const withMiddleware = (func) => {
      return (req, res, next) => {
        const response = func(req, res); 
        if (response) {
          res.locals[`${func.name}Response`] = response;
        }
        next();
      }    
    }
    
    //NEW Higher Order Function
    const withAsyncMiddleware = (func) = {
      // Return an async middleware
      return async (req, res, next) => {
        const response = await func(req, res); 
        if (response) {
          res.locals[`${func.name}Response`] = response; // the response will be available as res.locals.${func.name}Response
        }
        next();
      } 
    }
    
    //We have to export both the functions now.
    module.export = { withMiddleware, withAsyncMiddleware };
    And we can use this in the following manner
    // Have to change the import first
    const { withMiddleware, withAsyncMiddleware } = require('./withMiddleware');
    
    const funcOne = (req, res) => {
      if(req.body.username && req.body.password){
        return true; 
      } else {
        return false;  
      }
    }
    
    // FuncTwo is async, as it is going to make an API request.
    const funcTwo = async () => {
      const data = await apiResponse(); // Here is the side-effect, and its making the API request.
      return data; // the async function returned the data;
    }
    
    const middlewareOne = withMiddleware(funcOne);
    const middlewareTwo = withAsyncMiddleware(funcTwo); // wrapping this with async middleware
    
    //use this middlewares in a standard way.
    app.post("/path", middlewareOne, middlewareTwo, (req, res)=>{
      //further execution, if any
      const {funcOneResponse, funcTwoResponse} = res.locals;
      if(funcOneResponse && funcTwoResponse){
        res.send(funcTwoResponse); // Pure Magic
      } else {
        res.status(400).send('Bad Request')
      }
    })
    4. Now the easiest part. how we log the time for the async function?
    Simple
    //withMiddleware.js
    
    //Higher Order Function
    const withMiddleware = (func) => {
      return (req, res, next) => {
        const response = func(req, res); 
        if (response) {
          res.locals[`${func.name}Response`] = response;
        }
        next();
      }    
    }
    
    const withAsyncMiddleware = (func) = {
      return async (req, res, next) => {
        const t1 = Date.now();
        const response = await func(req, res); 
        const t2 = Date.now();
        console.log(`Time Taken to execute async ${func.name} is ${t2 - t1}`);
        if (response) {
          res.locals[`${func.name}Response`] = response; // the response will be available as res.locals.${func.name}Response
        }
        next();
      } 
    }
    
    module.export = { withMiddleware, withAsyncMiddleware };
    That's all for now,
    Thanks

    30

    This website collects cookies to deliver better user experience

    Express Middlewares and Higher Order Functions, part 2