Express Middlewares and Higher Order Functions, part 2

tirthaguha

Tirtha Guha

Posted on June 29, 2021

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;
Enter fullscreen mode Exit fullscreen mode

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({});
})
Enter fullscreen mode Exit fullscreen mode

We had a few unanswered questions

  1. Passing data between middlewares? Although we solved it in the first post, see the PS: section, but how can we make it work effortlessly.
  2. What if I'm writing auth or validation middlewares, that needs access to the request object?
  3. How will we handle the async middlewares?
  4. 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;
Enter fullscreen mode Exit fullscreen mode

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')
  }
})
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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')
  }
})
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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')
  }
})
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

That's all for now,
Thanks

💖 💪 🙅 🚩
tirthaguha
Tirtha Guha

Posted on June 29, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related