Docs
/
Cohort 2
/
Week 3
/
Middlewares

Middlewares

Lets understand the concept of middlewares with an example of Hospital Management System lets say during the COVID pandemic. Before we reach the doctor we have to undergo a series of formality and checks like aadhar, insurance checks, medical history, temperature check, etc. These series of requests are waiting in the callback queue to be processed by the server. Now lets talk in terms of code and see how we can implement this.

const express = require("express");

const app = express();

app.get("/health-checkup", function (req, res) {
  // health checkups here
  // if they pass then you proceed
  res.send("You are healthy");
});

Common Questions

  1. Auth checks? (does the user have enough funds to visit the doc?)
  2. Ensure input by the user is valid (BP/Blood tests)

Add more constraints to our health checkup route

  1. User needs to send a kidneyId as a query param which should be a number from 1-2 (humans only has 2 kidneys)
  2. User should send a username and password in headers
app.get("/health-checkup", function (req, res) {
  // health checkups here
  const kidneyId = req.query.kidneyId;
  const username = req.headers.username;
  const password = req.headers.password;

  if (username != "admin" && password != "root") {
    // 403 forbidden
    res.status(403).json({
      message: "Invalid credentials",
    });
    // early return as we dont want to execute the rest of the code
    // since the user authentication failed
    return;
  }

  if (kidneyId != 1 && kidneyId != 2) {
    // 411 Length required
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  // do something with kidney here

  // if they pass then you proceed
  res.send("You are healthy");
});

NOTE : We are using if (username != "admin" && password != "root") to validate if the user is not the admin if yes then we are returning a 403 status code. Another patter is to negate the condition and return early. Like if (!(username == "admin" || password == "root")) which is same as above condition just more readable.

  • What if we are asked to add a new route that does kidney replacement and the inputs need to be same as the health checkup route?

Ugly Solution : create new route and repeat the same code

app.get("/health-checkup", function (req, res) {
  // health checkups here
  const kidneyId = req.query.kidneyId;
  const username = req.headers.username;
  const password = req.headers.password;

  if (username != "admin" && password != "root") {
    // 403 forbidden
    res.status(403).json({
      message: "Invalid credentials",
    });
    // early return as we dont want to execute the rest of the code
    // since the user authentication failed
    return;
  }

  if (kidneyId != 1 && kidneyId != 2) {
    // 411 Length required
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  // do something with kidney here

  // if they pass then you proceed
  res.send("You are healthy");
});

app.get("/replace-kidney", function (req, res) {
  // health checkups here
  const kidneyId = req.query.kidneyId;
  const username = req.headers.username;
  const password = req.headers.password;

  if (username != "admin" && password != "root") {
    // 403 forbidden
    res.status(403).json({
      message: "Invalid credentials",
    });
    // early return as we dont want to execute the rest of the code
    // since the user authentication failed
    return;
  }

  if (kidneyId != 1 && kidneyId != 2) {
    // 411 Length required
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  // kidney replacement logic here

  // if they pass then you proceed
  res.send("Your kidney has been replaced, you are healthy now");
});

Slightly Better Solution : create helper function and call it in both routes

function usernameValidator(username, password) {
  if (username != "admin" && password != "root") {
    return false;
  }
  return true;
}

function kidneyValidator(kidneyId) {
  if (kidneyId != 1 && kidneyId != 2) {
    return false;
  }
  return true;
}

app.get("/health-checkup", function (req, res) {
  // health checkups here
  const kidneyId = req.query.kidneyId;
  const username = req.headers.username;
  const password = req.headers.password;

  if (!usernameValidator(username, password)) {
    res.status(403).json({
      message: "Invalid credentials",
    });
    return;
  }

  if (!kidneyValidator(kidneyId)) {
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  // do something with kidney here

  res.send("You are healthy");
});

app.get("/replace-kidney", function (req, res) {
  // health checkups here
  const kidneyId = req.query.kidneyId;
  const username = req.headers.username;
  const password = req.headers.password;

  if (!usernameValidator(username, password)) {
    res.status(403).json({
      message: "Invalid credentials",
    });
    return;
  }

  if (!kidneyValidator(kidneyId)) {
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  // kidney replacement logic here

  res.send("Your kidney has been replaced, you are healthy now");
});

Best Solution : create a middleware and use it in both routes

function userMiddleware(req, res, next) {
  const username = req.headers.username;
  const password = req.headers.password;

  if (username != "admin" && password != "root") {
    res.status(403).json({
      message: "Invalid credentials",
    });
    return;
  }
  next();
}

function kidneyMiddleware(req, res, next) {
  const kidneyId = req.query.kidneyId;

  if (kidneyId != 1 && kidneyId != 2) {
    res.status(411).json({
      message: "Wrong inputs",
    });
    return;
  }
  next();
}

app.get(
  "/health-checkup",
  userMiddleware,
  kidneyMiddleware,
  function (req, res) {
    // do something with kidney here

    res.send("You are healthy");
  }
);

app.get(
  "/replace-kidney",
  userMiddleware,
  kidneyMiddleware,
  function (req, res) {
    // kidney replacement logic here

    res.send("Your kidney has been replaced, you are healthy now");
  }
);

// can consume the middleware in other routes as well
app.get("/heart-check", userMiddleware, function (req, res) {
  // do something with user here
  res.send("Your heart is healthy");
});

NOTE : The order of the middlewares matter. If you put the userMiddleware after the kidneyMiddleware then the kidneyMiddleware will not be executed. Well the code here is quite dumb but you get the point.

app.use

app.use is a special function that can be used to apply a middleware to all the routes.

// a middleware that helps us to parse the body of the request as a json
// generally used to parse the body of POST requests
app.use(express.json());
// a middleware that helps us to parse the body of the request as a url encoded string
app.use(express.urlencoded({ extended: true }));

// add more middlewares to all the routes
app.use(userMiddleware);
app.use(kidneyMiddleware);
  • this will apply the middleware to all the routes

app.use with path

We can add a path to the app.use function to apply the middleware to only those routes that match the path. Lets apply the middleware to all the routes that start with /health-checkup

app.use("/health-checkup", userMiddleware);

Assignments

  1. Count the number of requests
const express = require("express");

const app = express();

let count = 0;
function countRequest(req, res, next) {
  count++;
  console.log(`Total requests : ${count}`);
  next();
}

app.use(countRequest);

Lets also make a countVisits middleware that counts the number of times a user has visited the website on specific route.

let page_visits = {};
let visits = function (req, res, next) {
  let counter = page_visits[req.originalUrl];
  if (counter || counter === 0) {
    page_visits[req.originalUrl] = counter + 1;
  } else {
    page_visits[req.originalUrl] = 1;
  }
  console.log(req.originalUrl, counter);
  next();
};

app.use(visits);

Source : Stackoverflow

  1. Find the average time your server is taking to handle requests
const express = require("express");

const app = express();

function timeMiddleware(req, res, next) {
  const start = Date.now();
  res.on("finish", () => {
    const end = Date.now();
    console.log(`Time taken : ${end - start} ms`);
  });
  next();
}

app.use(timeMiddleware);

Why do we need input validations?

What if user sends an unexpected input to our server? Lets say the user sends a string instead of a number for kidneyId. Our server will crash. We need to handle these cases and send a proper response to the user.

const express = require("express");

const app = express();

app.post("/health-checkup", function (req, res) {
  // do something with kidney here
  const kidneys = req.body.kidneys;
  const kidneyLength = kidneys.length;

  res.send(`You have ${kidneyLength} kidneys`);
});

Global catches

This is the middleware that gets executed when an error occurs in the server. Also known as, Error Handling Middleware a special type of function in Express that has four arguments (err, req, res, next). Express recognizes the error handling function by the fact that it has four arguments instead of three.

app.use(function (err, req, res, next) {
  console.error(err);
  console.error(err.stack);
  res.status(500).send("Internal server error!");
});
Last updated on February 12, 2024