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
- Auth checks? (does the user have enough funds to visit the doc?)
- Ensure input by the user is valid (BP/Blood tests)
Add more constraints to our health checkup route
- User needs to send a kidneyId as a query param which should be a number from 1-2 (humans only has 2 kidneys)
- 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
- 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
- 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!");
});