{"resource":{"author":{"id":"h2so9H8jPMmgUKGghoNl","name":"Yomesh Gupta","username":"yomeshgupta"},"content":{"link":"https://devtools.tech/build-your-own-express-js-part-three/","difficulty":2,"domain":3,"type":1,"isInternal":true,"body":"This blog post is part of a series in which we are going to build a minimal, simple and yet powerful version of [Express.js](https://expressjs.com/), called [Minimal.js](https://github.com/yomeshgupta/minimaljs.git). This is the third part. You can check out part 1 [here](https://www.devtools.tech/blog/build-your-own-expressjs-or-part-1---rid---qoos1dgnByAcEaCp2rbl) and part 2 [here](https://www.devtools.tech/blog/build-your-own-expressjs-or-part-2---rid---negw30VulwpaVLRpxxMl).\n\nWe are learning on the go so if you find any mistake or any better way to do certain things or just want to share your feedback then I am all ears and open to collaboration. [Let me know your opinions here](https://www.twitter.com/yomeshgupta).\n\n## The Road so far\n\nSo, till now, we talked about HTTP, explored a couple of Node.js modules, built a simple server using native modules and then we took a deep dive into how we can implement methods like `use` and `middlewares` support to our framework.\n\n## Part 3\n\nThis part would revolve around how routing works under the hood and implementation details regarding standard HTTP verbs like `get`, `post` and more.\n\nI would recommend that you should code along. So, go ahead, clone the repo and check out the `part-2` branch. Create a new branch `part-3` from `part-2`.\n\n```\ngit clone https://github.com/yomeshgupta/minimaljs.git\ngit checkout part-2\ngit checkout -b part-3 part-2\n```\n\nComplete code for this part can be found [here](https://github.com/yomeshgupta/minimaljs/tree/part-3).\n\n## Concepts\n\nRouting System in `Express`, basically comprises of three major components:\n\n1. Router\n2. Layer\n3. Route\n\nLet's talk about each but before that take a look at the diagram below.\n\n![Routing](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-three/routing_nFAf_ORbWY-X.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1642876391261)\n\n#### Router\n\nRouter component is responsible for the entire routing of the application. It exposes various methods (HTTP verbs) like `get`, `post`, `put` and more; which are used by the application to bind handlers to particular paths. An instance of `Router` contains an array of `Layers`.\n\nThe relationship between `Router` and `Layer` is `one-to-many` i.e. an instance of a `Router` can have multiple `Layers` in it.\n\n#### Layer\n\nEach `Layer` consists of a `path` and a `Route` component which stores more specific information about that `path`.\n\n#### Route\n\nRoute Component is the one where we implement the HTTP verbs. It contains the `path` and an array of `Layers`.\n\nThe difference between the `Layer` in the `Router` component and the `Route` component is that in the `Route` component, a `Layer` contains a `method` and its `handler`.\n\nThe relationship between `path` and `method` can be `one-to-many` i.e. a path like `/login` can have multiple methods attached to it.\n\n```\nGET /login\nPOST /login\n```\n\n![route](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-three/route_vOqlyVo-m.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1642876390967)\n\nIn summary, a Router contains an array of layers where each layer represents a path and a route component. Each route component contains the same path along with all the method(s) handling associated with it. When a request is received, each Layer's path is matched against the current request path. If matched then we compare the current request method type with method handling provided at the time of setup, if it is also a match then the apt handler is executed else not found is returned.\n\n### Let's code\n\nCreate a folder `router` and create a file `layer.js` inside it.\n\n```\ncd ./src\nmkdir router\ncd ./router\ntouch layer.js\n```\n\nPaste the following code inside the newly created `layer.js`\n\n```js\nconst { matchPath } = require(\"../lib/helpers\");\n\nclass Layer {\n  /*\n    Setting up path and handler\n  */\n  constructor(path, handler) {\n    this.handler = handler;\n    this.name = handler.name || \"<anonymous>\";\n    this.path = path;\n  }\n\n  /*\n    If the current request path matches the layer's path\n    then handling for the current path\n  */\n  requestHandler(...args) {\n    const handler = this.handler;\n    handler ? handler(...args) : null;\n  }\n\n  /*\n    To match current request path with \n    the path provided at the time of setup\n\n    SETUP: app.get('/login', (req, res) => {})\n    CURRENT REQUEST: GET /login\n  */\n  match(path) {\n    return matchPath(this.path, path);\n  }\n}\n\nmodule.exports = Layer;\n```\n\nInside the router folder, create a `route.js` and paste the following code\n\n```\ntouch route.js\n```\n\n```js\nconst Layer = require(\"./layer.js\");\n\nclass Route {\n  constructor(path) {\n    this.path = path;\n    this.stack = [];\n    this.methods = {};\n  }\n\n  requestHandler(method) {\n    const name = method.toLowerCase();\n    return Boolean(this.methods[name]);\n  }\n\n  get(handler) {\n    const layer = new Layer(\"/\", handler);\n    layer.method = \"get\";\n\n    this.methods[\"get\"] = true;\n    this.stack.push(layer);\n    return this;\n  }\n\n  post(handler) {\n    const layer = new Layer(\"/\", handler);\n    layer.method = \"post\";\n\n    this.methods[\"post\"] = true;\n    this.stack.push(layer);\n    return this;\n  }\n\n  dispatch(req, res) {\n    const method = req.method.toLowerCase();\n\n    this.stack.forEach((item) => {\n      if (method === item.method) {\n        item.requestHandler(req, res);\n      }\n    });\n  }\n}\n\nmodule.exports = Route;\n```\n\nLet's understand the above code snippet\n\n1. We created a `Route` class that takes `path` as a parameter and maintains an internal array of all layers and a collection of methods attached to it.\n2. Whenever, you do something like\n\n```js\napp.get(\"/login\", (req, res) => {\n  /* some processing */\n});\n```\n\nThen a `route` is created with `path` `/login` and a layer is pushed inside its array of layers containing the handler provided with it. Route also stores the information about the current method in a collection. It is done to determine if a method of handling is available when a request comes in.\n\n3. `dispatch` method is called when a request comes in, it traverses all the layers and if the current request method matches with the layer's method then we execute the apt request handler.\n\n4. Currently, we are handling two HTTP verbs, `get` and `post`.\n\nNow, we have our `Layer` and `Route` components ready. Let's consolidate all this in `Router`.\n\nCreate a file `index.js` in the router folder and paste the following code.\n\n```\ntouch router.js\n```\n\n```js\nconst Layer = require(\"./layer.js\");\nconst Route = require(\"./route.js\");\n\nclass Router {\n  constructor() {\n    this.stack = [\n      new Layer(\"*\", (req, res) => {\n        res.statusCode = 404;\n        res.setHeader(\"Content-Type\", \"text/plain\");\n        res.end(`Cannot find ${req.url}`);\n      }),\n    ];\n  }\n\n  handle(req, res) {\n    const method = req.method;\n    let found = false;\n\n    this.stack.some((item, index) => {\n      if (index === 0) {\n        return false;\n      }\n      const { matched = false, params = {} } = item.match(req.pathname);\n      if (matched && item.route && item.route.requestHandler(method)) {\n        found = true;\n        req.params = params;\n        return item.requestHandler(req, res);\n      }\n    });\n\n    return found ? null : this.stack[0].requestHandler(req, res);\n  }\n\n  route(path) {\n    const route = new Route(path);\n    const layer = new Layer(path, (req, res) => route.dispatch(req, res));\n    layer.route = route;\n    this.stack.push(layer);\n\n    return route;\n  }\n\n  get(path, handler) {\n    const route = this.route(path);\n    route.get(handler);\n    return this;\n  }\n\n  post(path, handler) {\n    const route = this.route(path);\n    route.post(handler);\n    return this;\n  }\n}\n\nmodule.exports = Router;\n```\n\n1. We created a Router class that contains a stack (array of Layers). We are seeding the array with a default layer which will act as the default response if no handler for a path is found.\n2. We are handling `GET` and `POST` requests for now.\n3. Whenever we invoke any of the HTTP verb methods then a `Route` is created with the provided path, a `Layer` is created with the same path and the route's dispatch method is provided as the handler to the `Layer`.\n4. `handle` method goes through each layer in the stack looking for the layer whose setup path matches with the current request path and if that layer has a route and route handler with it then route handler is executed.\n\n### Updating Framework\n\nLet's update our framework to accommodate our routing system.\n\n```js{2,7,19,27,31-39,45,53-54}\n...\nconst Router = require('./router/index');\n...\n\nfunction Minimal() {\n  ...\n  const _router = new Router();\n  ...\n  function findNext(req, res) {\n    ...\n    const next = () => {\n      ...\n      if (matched) {\n        req.params = params;\n        middleware.handler(req, res, next);\n      } else if (current <= _middlewares.length) {\n        next();\n      } else {\n        req.handler(req, res);\n      }\n    };\n    ...\n  }\n\n  function handle(req, res, cb) {\n    ...\n    req.handler = cb;\n    ...\n  }\n\n  function get(...args) {\n    const { path, handler } = checkMiddlewareInputs(args);\n    return _router.get(path, handler);\n  }\n\n  function post(...args) {\n    const { path, handler } = checkMiddlewareInputs(args);\n    return _router.post(path, handler);\n  }\n\n  function listen(port = 8080, cb) {\n    return http\n      .createServer((req, res) => {\n        ...\n        handle(req, res, () => _router.handle(req, res));\n      })\n      ...\n  }\n\n  return {\n    use,\n    listen,\n    get,\n    post\n  }\n}\n```\n\n1. We imported our `Router` class into our framework and created an instance of it.\n2. We implemented and exposed `get` and `post` methods so that our users can use them to bind handlers to a specific path and method types.\n3. We are now sending a `callback` to the `handle` method which is assigned to a key `handler` on the request object.\n4. We have seen in part 2 that we initially execute all the middlewares sequentially. We have updated that to call our method request handler when the middleware execution list is exhausted as no more middleware is left to be executed.\n\n### Testing\n\nNow, we can test what we have built so far. Open `server.js` and make the following changes.\n\n```js{2-3,6,9,11}\n..\napp.use(cors());\napp.get('/about', (req, res) => {\n  res.send('I am the about page');\n});\napp.get('/', (req, res) => {\n  fs.readFile(path.resolve(__dirname, 'public', 'index.html'), (err, data) => {\n    if (err) {\n      return res.status(500).send('Error Occured');\n    }\n    return res.status(200).send(data);\n  });\n});\n...\n```\n\n1. `/about` route with `CORS` headers.\n\n![about](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-three/about_iA5jX-6Th.png?ik-sdk-version=javascript-1.4.3&updatedAt=1642876391764)\n\n2. `/` route.\n\n![home](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-three/home_-o9KJbKIbBa.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1642876390797)\n\n3. Not Found Route\n\n![not-found](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-three/not-found_xkWeFd4Ro4.png?ik-sdk-version=javascript-1.4.3&updatedAt=1642876391965)\n\nYaay!! Our framework is working now. I know we haven't tested the `post`</span> requests and there are some known issues. Let's see if you can find those out. In the next part, we will improve upon our current system, fix some issues and maybe build a simple REST API using our framework. Stay tuned.\n\nComplete code for this part can be found [here](https://github.com/yomeshgupta/minimaljs/tree/part-3).\n","languages":[],"editorConfig":{}},"stats":{"views":13233,"used":0,"likes":0},"description":"","published":true,"isActive":true,"tags":["node.js","express","rest","api","framework","backend","tools","devtools","framework development","build express"],"slug":"build-your-own-expressjs-or-part-3---rid---6mHfnu0fQoPw4RBpexRV","isPremium":false,"categories":[],"requires":[],"_id":"5f1dd74dcbec5f7ffc0c2fac","title":"Build your own expressjs | Part 3","resourceId":"6mHfnu0fQoPw4RBpexRV","createdAt":1595791181047,"modifiedAt":1643031747450},"currentUser":null,"isOwner":false,"recommendations":{"questions":[{"_id":"6393299b937c6f5491668cd6","content":{"languages":["react"],"difficulty":2},"tags":["javascript","code","programming","usescript","custom hooks","react","react custom hooks","advanced frontend","advanced javascript","advanced reactjs","ui","ux","interview questions","interview preparation","coding"],"slug":"how-to-create-usescript-hook-in-react-js-or-javascript-interview-question-or-frontend-problem-solving---qid---yYWeRxOmRJ0VhOVQdFnG","title":"How to create useScript hook in React.js? | JavaScript Interview Question | Frontend Problem Solving","questionId":"yYWeRxOmRJ0VhOVQdFnG"},{"_id":"64ecc241d5ab2876a4c336da","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","array","frontend","coding","interview","devtools tech","codedamn","frontend masters","interview question","lodash polyfill"],"slug":"implement-a-function-that-creates-an-array-of-values-not-included-in-other-array-or-lodash-difference-or-javascript-interview-question---qid---xjhgy8O1xkroU074X7EU","title":"Implement a function that creates an array of values not included in other array | Lodash Difference | JavaScript Interview Question  ","questionId":"xjhgy8O1xkroU074X7EU"},{"_id":"5edcb9c73cce8c36cd29251b","content":{"difficulty":2,"languages":"javascript"},"tags":["javascript","frontend","node.js","array","javascript engines","frontend fundamentals"],"slug":"what-would-be-the-output-based-on-array-prototype-foreach---qid---IwCGnXetLQdwzHkLpjis","title":"What would be the output? (Based on Array prototype forEach)","questionId":"IwCGnXetLQdwzHkLpjis"},{"_id":"652ac1efd5ab2876a4c9e7a1","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["frontend","object","comparison","ui","ux","deep equal","lodahs","frontend interview question","devtools tech","blog","javascript interview question","isEqual","deep comparison","recursion","objects","problem solving","lodash","polyfill"],"slug":"how-to-check-deep-equality-between-javascript-objects-or-zeta-frontend-interview-question---qid---1YjgIig2dBwqkpovXNrR","title":"How to check deep equality between JavaScript objects? | Zeta Frontend Interview Question","questionId":"1YjgIig2dBwqkpovXNrR"},{"_id":"61f82a28e12bce537863b05b","content":{"languages":["javascript","typescript"],"difficulty":4},"tags":["javascript","flipkart","debounce","web performance","programming","interview question","advanced js"],"slug":"implement-debounce-function-or-flipkart-ui-javascript-interview-questions---qid---CVSyd8uTdvVt1l2YcAcq","title":"Implement Debounce Function | Flipkart UI - JavaScript Interview Questions","questionId":"CVSyd8uTdvVt1l2YcAcq"}],"resources":[{"_id":"66a4ca5daac82a58634c2bfe","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":["undefined"]},"tags":["javascript","frontend","ui","ux","coding","atlassian","frontend coding challenge","feature flags","interactive shape"],"slug":"top-atlassian-frontend-interview-questions-and-process---rid---Bu7bJx9jNVoIsrH71Byb","title":"Top Atlassian Frontend Interview Questions and Process","resourceId":"Bu7bJx9jNVoIsrH71Byb"},{"_id":"6963e277132ce4e954b04414","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","devtools tech","coding","frontend performance","browser rendering cycle"],"slug":"mastering-critical-rendering-path---rid---VyhOczhtA0Ds1kp2s4TN","title":"Mastering Critical Rendering Path","resourceId":"VyhOczhtA0Ds1kp2s4TN"},{"_id":"63ce29da22480f26b3cdc905","content":{"difficulty":1,"domain":1,"type":2,"isInternal":false},"tags":["javascript","frontend","coding","programming","interview questions","ui","ux","coding practices","development","devtools tech"],"slug":"top-coding-practices-that-every-engineer-must-follow-or-coding-practices-with-examples---rid---26KOoxbSz5hGqasGaKta","title":"Top Coding Practices That Every Engineer Must Follow | Coding Practices With Examples","resourceId":"26KOoxbSz5hGqasGaKta"},{"_id":"692339ccbf1a48f85e0d1d38","content":{"difficulty":4,"domain":2,"type":2,"isInternal":false,"languages":[]},"tags":[""],"slug":"quick-way-to-fix-uncompressed-javascript/css-files-devtools-trick---rid---bz9xmaeXqGkkZaLgj3tL","title":"Quick way to fix Uncompressed JavaScript/CSS Files (DevTools Trick)","resourceId":"bz9xmaeXqGkkZaLgj3tL"},{"_id":"6731bf5cd34d7f4b8cccbdec","content":{"difficulty":4,"domain":1,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","frontend","coding","ui","ux","developer tools","youtube videos"],"slug":"how-to-become-a-better-software-engineer---rid---4lmB97aZrn9z5q00FKZz","title":"How to become a better Software Engineer?","resourceId":"4lmB97aZrn9z5q00FKZz"}]}}