18. Middleware and Error handler

·

14 min read

Note:- from now onwards to test different API routes i will be using postman

  • What happen if we send empty request handler like this:-

      const express = require("express");
      const app = express();
      const port = 3000;
      app.get("/user", (req, res) => {});
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    when above code with URL “GET …/user“ check in postman we the screen will be hangs(keep loading) without showing any response.

  • what happen if we cosole.log inside request body

      const express = require("express");
      const app = express();
      const port = 3000;
      app.get("/user", (req, res) => {
        console.log("hello without res");
      });
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    still postman hangs but we get console.log in out terminal

  • One route can have multiple route handler for example

      const express = require("express");
      const app = express();
      const port = 3000;
      app.get(
        "/user",
        (req, res) => {
      1    console.log("hello from route handler 1");
        },
        (req, res) => {
          console.log("hello from route handler 2");
        },
        (req, res) => {
          console.log("hello from route handler 3");
        }
      );
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    but as soon as we hit “GET …/user“ postman still hang and we get “hello from route handler 1“ in console

    Working:- when the js engine comes from top to bottom by reading code line by line when it comes to line 1 it console log the text but due to absence of res.send() or something from which we get output in postman body it hangs and not print other console.log.

    Then how to print all console.log in our console not matter postman hangs or give some error then we add third parameter “next“ in the req, res parameter and call it inside every route body like this

      const express = require("express");
      const app = express();
      const port = 3000;
      app.get(
        "/user",
        (req, res, next) => {
          console.log("hello from route handler 1");
          next();
        },
        (req, res, next) => {
          console.log("hello from route handler 2");
          next();
        },
        (req, res, next) => {
          console.log("hello from route handler 3");
          next();
        }
      );
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    Now in console we get

  • but in postman we get

    the code will produce console log correctly but we are seeing error in postman because GET endpoint does not send a response back to the client. Without res.send() or equivalent response handler the client(postman) will wait indefinitely for a response until it timed out resulting an error.

same reason as above for this code also

const express = require("express");
const app = express();
const port = 3000;
app.get(
  "/user",
  (req, res, next) => {
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 2");
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

here we get “hello from route handler 2“ in console and error in postman

Now lets see different cases by changing position of next(), response and console.log with multiple handler and try to clear different concepts of response handler

Case 1:-

const express = require("express");
const app = express();
const port = 3000;
app.get(
  "/user",
  (req, res, next) => {
    console.log("hello from response handler 1");
    res.send("response handler 1 success");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 2");
    res.send("response handler 2 success");
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get “response handler 1 success“ in console we get

the error “cannot set headers after they are sent to client” is because once a response is taken from postman another response cannot be entered as per server-client req-res model.

Case 2:-

1. const express = require("express");
2. const app = express();
3.const port = 3000;
4.app.get(
 5. "/user",
6  (req, res, next) => {
7    console.log("hello from response handler 1");
8   next();
9    res.send("response handler 1 success");
10  },
11  (req, res, next) => {
 12   console.log("hello from route handler 2");
  13  res.send("response handler 2 success");
14  }
15);
16 app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

In postman we get “response handler 2 success“ and in console we get

because of reason we have mentioned above top of initial of Case 1. First of all line 7 execute then when js engine come to line 8 control pass to next response handler which is line 12 and it will execute then line 13 send response to postman but as soon as it again detect the line 9 response now error send “can’t set headers after they are sent to the client“ this happen because of internal working of JS engine and how it execute code in execution context.

Case 3:-

1 const express = require("express");
2 const app = express();
3 const port = 3000;
4 app.get(
5  "/user",
6  (req, res, next) => {
7    console.log("hello from response handler 1");
8    next();
9  },
10  (req, res, next) => {
11    console.log("hello from route handler 2");
12   res.send("response handler 2 success");
13  },
14  (req, res) => {
15   console.log("hello from route handler 3");
16    res.send("response handler 3 success");
17  }
18 );
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get “response handler 2 success“ and in console we get

now lets know how

first of all line 7 execute and print the console then it comes to line 8 and found next then it go to next response handler then line 11 consoled then response send to postman and from line 13 to line 18 will never execute.

Case 4:-

const express = require("express");
const app = express();
const port = 3000;
app.get(
  "/user",
  (req, res, next) => {
    console.log("hello from response handler 1");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 2");
    // res.send("response handler 2 success");
    next();
  },
  (req, res) => {
    console.log("hello from route handler 3");
    res.send("response handler 3 success");
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get “response handler 3 success“ and in console we get

and working same as case 3 but keep in mind when next() encountered control passed to next subsequent handler.

Case 5:-

const express = require("express");
const app = express();
const port = 3000;
app.get(
  "/user",
  (req, res, next) => {
    console.log("hello from response handler 1");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 2");
    // res.send("response handler 2 success");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 3");
    next();
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get

and in console we get

Case 6:-

const express = require("express");
const app = express();
const port = 3000;
app.get(
  "/user",
  (req, res, next) => {
    console.log("hello from response handler 1");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 2");
    // res.send("response handler 2 success");
    next();
  },
  (req, res, next) => {
    console.log("hello from route handler 3");
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

postman get hand because there is no response handler and in console we get

Case 7:-

we can also send array of route handler which work exactly same as above all cases(1-6)

example just to know how to write

const express = require("express");
const app = express();
const port = 3000;
app.get("/user", [
  (req, res) => {
    console.log("request handler 1");
  },
  (req, res) => {
    console.log("request handler 2");
  },
  (req, res) => {
    console.log("request handler 3");
  },
]);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

Case 8:-

we can also add n number of route handler which also same as above case 7

const express = require("express");
const app = express();
const port = 3000;
app.use("/user", () => { }, [() => { }, () => { }, () => { }]);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

Case 9:-

to write simple and effective code write all handlers separately

const express = require("express");
const app = express();
const port = 3000;
app.get("/user", (req, res, next) => {
  console.log("handling route handler 1");
  next();
});
app.get("/alpha", (req, res, next) => {
  console.log("handling route handler 2");
  res.send("good one");
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

Case 10:-

const express = require("express");
const app = express();
const port = 3000;
app.get("/user", (req, res, next) => {
  console.log("handling route handler 1");
  res.send("handling route handler 1");
});
app.get("/user", (req, res, next) => {
  console.log("handling route handler 2");
  next();
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in this code second request handler will never execute in postman we get “handling route handler 1“ and in console we get “handling route handler 1“

Case 11:-

const express = require("express");
const app = express();
const port = 3000;
app.get("/user", (req, res, next) => {
  console.log("handling route handler 1");
  next();
});
app.get("/user", (req, res, next) => {
  console.log("handling route handler 2");
  next();
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get

in console we get

Middleware

  • Now we must see that why even we use next() and have multiple route handler when work can be done using a single route handler which is know as middleware they are named so because they are called in middle of request chain

  • In simple language what we writing in “app.use()“ are all middleware. lets understand the working of middleware with different cases: -

Case 1: -

const express = require("express");
const app = express();
const port = 3000;
app.use("/", (req, res, next) => {
  next();
});
app.get(
  "/user",
  (req, res, next) => {
    console.log("handling / route");
    next();
  },
  (req, res, next) => {
    next();
  },
  (req, res) => {
    res.send("2nd route handler");
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman we get “2nd route handler“ and in console we get “handling / route“ when we hit URL “GET …/user“ or more accurately “http://localhost:3000/user‘ in postman, as soon as we do “…/user“ it form a middleware chain which keep on checking each handler one by one until it reaches a function to send the response back if the client not hand in postman.

Case 2:-

basic work of express is to take request and give response back.

const express = require("express");
const app = express();
const port = 3000;
app.use("/", (req, res, next) => {
  next(); //this is middleware
});
app.get(
  "/user",
  (req, res, next) => {
    console.log("handling /user route"); //this is middleware
    next();
  },
  (req, res, next) => {
    res.send("1st route handler"); //this is response handler
    console.log("1st route handler");
  },
  (req, res) => {
    res.send("2nd route handler"); //this is response handler
  }
);
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman when we hit “GET …/user“ we get “1st route handler“ and in console log we get “handling /user route“

HTTPS status code

it is a response made by the server to the client request. These are three digit code, some of major HTTPS status code are:-

200 :- Success/ok

400 :- Bad request

401 :- unauthorized error

403 :- forbidden

404 :- Not found

500 :- internal server error

501 :- not implemented ….and many more

  • lets suppose a scenario where we want to perform checking if request is authorized or not before fetching data

      const express = require("express");
      const app = express();
      const port = 3000;
      1 app.get("/admin/getAllData", (req, res) => {
      2  const token = "xyz";
      3  const isAdmin = token === "xyz";
      4  if (isAdmin) {
      5    res.send("all data sent");
      6  } else {
      7    res.status(401).send("unauthorized request");
      8  }
      9 });
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    for how many routes for example for GET, POST, DELETE, POST, we will write logic like from line 1 -9 hence we need a single middleware which can be used or called in all routes wherever required.

  • Now problem arises how to handle authorization middleware for all request (get, post, delete, post), suppose we want to write middleware for “…/admin“ like “…/admin/login“ or “…/admin/deleteuser“ etc.

Notes:— app.use() Vs app.all():-

app.use() will fire for regardless of method mostly use as middleware, whereas app.all() only fires for parser supported methods or for only specific path which is specified.

app.all('/test', fn1);     // route 1
app.use('/test', fn2);     // route 2

http://yourhost.com/test         // both route1 and route2 will match
http://yourhost.com/test/1       // only route2 will match

Middleware in Code

Case 1:-

const express = require("express");
const app = express();
const port = 3000;
//below is middleware with app.use()
app.use("/admin", (req, res, next) => {
  console.log("Admin auth is getting checked");
  const token = "xyz";
  const isAdmin = token === "xyz";
  if (!isAdmin) {
    res.status(401).send("unauthorized request");
  } else {
    next();
  }
});
app.get("/user", (req, res) => {
  res.send("user data sent");
});
app.get("/admin/getAllData", (req, res) => {
  res.send("all data sent");
});
app.get("/admin/deleteUser", (req, res) => {
  res.send("deleted user");
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

when we hit GET “http://localhost:3000/admin/getAllData“ in postman we get “all data sent “ in postman and in console we get “Admin auth is getting checked“ this is because every time we hit different route like “../admin/getAllData“ or “…/admin/deleteUser“ each time all route will pass through auth middleware to transfer data securely to other mentioned routes. Similarly if we hit get “http://localhost:3000/admin/deleteUser“ in this also in postman we get “deleted user“ and in console we get “admin auth is getting checked“. but if we do get “http://localhost:3000/user“ then in postman we get “all data sent“ here no middleware check as , it is written for “…/admin“ routes means route which contain “route“ string.

  • But in real world code bases we don’t write code like above because as line of code increases it is difficult to write middleware and handle them. With the help of auth middleware we can perform check on each req/res handler to provide data in safe manner.

  • we make a separate folder inside “src“ and make separate folder as “middleware“ and inside it for example we want to make authorization middleware we write it inside “auth.js file“ and then export that logic using module.exports. like this

      //auth.js
      const adminAuth = (req, res, next) => {
        console.log("admin auth is getting checked");
        const token = "xyz";
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.status(401).send("unauthorized access");
        } else {
          next();
        }
      };
      module.exports = {
        adminAuth,
      };
      //app.js
      const express = require("express");
      const app = express();
      const port = 3000;
      const { adminAuth } = require("./middlewares/auth");
      app.use("/admin", adminAuth);
      app.get("/admin/getAllData", (req, res) => {
        const token = req.body?.token;
        console.log(token); //undefined
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.send("All data sent");
        }
      });
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    in postman we get “all data sent “ and in console we get “admin auth is getting checked“ and undefined

  • In same auth.js we can add multiple middleware

      //auth.js
      const adminAuth = (req, res, next) => {
        console.log("admin auth is getting checked");
        const token = "xyz";
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.status(401).send("unauthorized access");
        } else {
          next();
        }
      };
      const userAuth = (req, res, next) => {
        console.log("user auth is getting checked");
        const token = "xyz";
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.status(401).send("unauthorized access");
        } else {
          next();
        }
      };
      module.exports = {
        adminAuth,
        userAuth,
      };
      //app.js
      const express = require("express");
      const app = express();
      const port = 3000;
      const { adminAuth } = require("./middlewares/auth");
      const { userAuth } = require("./middlewares/auth");
      app.use("/admin", adminAuth);
      app.use("/user", userAuth);
      app.get("/admin/getAllData", (req, res) => {
        const token = req.body?.token;
        console.log(token); //undefined
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.send("All data sent");
        }
      });
      app.get("/user/getAllData", (req, res) => {
        const token = req.body?.token;
        console.log(token); //undefined
        const isAdmin = token === "xyz";
        if (!isAdmin) {
          res.send("All data sent");
        }
      });
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    in postman when we hit get “http://localhost:3000/user/getAllData“ we get “All data sent“ and in console we get “user auth is getting checked" and undefined.

Handling Errors

  • Generally we do this to show error

      const express = require("express");
      const app = express();
      const port = 3000;
      app.get("/user", (req, res) => {
        throw new Error("abcd...");
        res.send("user data sent");
      });
      app.listen(port, () => {
        console.log(`Listening on port ${port}`);
      });
    

    this is not good way as it exposes different code related issue to public

    When “GET …/user“ hit in postman

we mostly use these type of error handling methods:-

i) using try…catch block

ii) using “err“ parameter inside request handler.

using try…catch block

const express = require("express");
const app = express();
const port = 3000;
app.get("/user", (req, res) => {
  try {
    throw new Error("abcd...");
    res.send("user data sent");
  } catch (err) {
    res.status(500).send("some error occured");//customized error 
  }
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

when we hit “GET …/user“ in postman we get “some error occurred”. This is one of the most widely used method to show error

using “err“ parameter in request handler

while writing “err” parameter inside request handler this should be the first parameter, and it should be in mind that order matter a lot while writing code in backend.

const express = require("express");
const app = express();
const port = 3000;
app.use("/", (err, req, res, next) => {
  if (err) {
    //log your error here
    res.status(500).send("error");
  }
});
app.get("/user", (req, res) => {
  try {
    throw new Error("abcd...");
    res.send("user data sent");
  } catch (err) {
    res.status(500).send("some error occured"); //customized error
  }
});
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

in postman when we hit “GET …/user“ we get “some error occurred“.