{"resource":{"author":{"id":"h2so9H8jPMmgUKGghoNl","name":"Yomesh Gupta","username":"yomeshgupta"},"content":{"link":"https://devtools.tech/build-your-own-express-js-part-two/","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 second part. You can check out part 1 [here](https://www.devtools.tech/blog/build-your-own-expressjs-or-part-1---rid---qoos1dgnByAcEaCp2rbl).\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\nIn part 1, we talked about HTTP, explored a couple of Node.js modules and built a simple server using native modules.\n\n## Part 2\n\nThis part would revolve around shaping our framework, exposing APIs and talking about middlewares. Complete code for this part can be found [here](https://github.com/yomeshgupta/minimaljs/tree/part-2).\n\nI would recommend that you should code along. So, go ahead, clone the repo and check out the `part-1` branch. Create a new branch `part-2` from `part-1`.\n\n```\ngit clone https://github.com/yomeshgupta/minimaljs.git\ngit checkout part-1\ngit checkout -b part-2 part-1\n```\n\nNow, create a folder `src` and inside that folder create two files, `index.js` and `minimal.js`.\n\n```\nmkdir src\ncd ./src\ntouch index.js\ntouch minimal.js\n```\n\n## Shaping our framework\n\nIn Express, we require the module in our file and it exposes methods like `listen`, `use`, `get`, `post` and likes.\n\n```js\nconst express = require(\"express\");\nconst app = express();\n\napp.use(\"/path\", (req, res) => {});\napp.get(\"/path\", (req, res) => {});\n\napp.listen(8080, () => console.log(\"Server running\"));\n```\n\nWe are going to create a similar structure. Now, inside our `minimal.js`, we are going to create a function that will act as an entry point to our framework.\n\n```js\nfunction Minimal() {\n  return {};\n}\n\nmodule.exports = Minimal;\n```\n\n### Adding methods\n\nFirst, we are going to implement the `listen` method which will take `port` and `callback` as arguments and returns an `http.Server` instance.\n\n```js\nconst http = require('http');\n\nfunction Minimal() {\n  function listen(port = 8080, cb) {\n    return http\n      .createServer((req, res) => {})\n      .listen({ port }, cb);\n  }\n\n  return {\n    listen\n  };\n}\n...\n```\n\nLet's move our `requestListener` implementation from part-1 into our newly created `listen` method along with some `listen` method validations.\n\n```js{2-3,9-17,19-26}\n...\nconst fs = require('fs');\nconst path = require('path');\n\nfunction Minimal() {\n  function listen(port = 8080, cb) {\n    return http\n      .createServer((req, res) => {\n        fs.readFile(path.resolve(__dirname, 'public', 'index.html'), (err, data) => {\n          res.setHeader('Content-Type', 'text/html');\n          if (err) {\n            res.writeHead(500);\n            return res.end('Some error occured');\n          }\n          res.writeHead(200);\n          return res.end(data);\n        });\n      })\n      .listen({ port }, () => {\n        if (cb) {\n          if (typeof cb === 'function') {\n            return cb();\n          }\n          throw new Error('Listen callback needs to be a function');\n        }\n      });\n  }\n  ...\n}\n...\n```\n\nYou can make changes to `server.js` and take this for a spin!\n\n```js\nconst minimal = require(\"./src/minimal\");\nconst CONFIG = require(\"./config\");\n\nconst app = minimal();\nconst server = app.listen(CONFIG.PORT, () =>\n  console.log(`Server running on ${CONFIG.PORT}`)\n);\n```\n\n### Extending Request\n\nExpress provides us with additional data on the request object. We can access properties like `req.pathname`, `req.path`, `req.queryParams` directly from our `request` object. We are going to write a simple utility function that will extend our `request` object.\n\nNow, create a new file `request.js` in the `src` folder\n\n```\ncd ./src\ntouch request.js\n```\n\nWe are going to parse the incoming request using the Node.js in-build `url` module and add properties to our `request` object.\n\n```js\nconst url = require(\"url\");\n\nfunction request(req) {\n  const parsedUrl = url.parse(`${req.headers.host}${req.url}`, true);\n  const keys = Object.keys(parsedUrl);\n  keys.forEach((key) => (req[key] = parsedUrl[key]));\n}\n\nmodule.exports = request;\n```\n\n### Extending Response\n\nJust like we did for the `request` object, we are going extend the `response` object and add methods like `send`, `json`, `redirect`.\n\n```\ntouch response.js\n```\n\nAdd the following code to the file\n\n```js\nfunction response(res) {\n  function end(content) {\n    res.setHeader(\"Content-Length\", content.length);\n    res.status();\n    res.end(content);\n    return res;\n  }\n\n  res.status = (code) => {\n    res.statusCode = code || res.statusCode;\n    return res;\n  };\n\n  res.send = (content) => {\n    res.setHeader(\"Content-Type\", \"text/html\");\n    return end(content);\n  };\n\n  res.json = (content) => {\n    try {\n      content = JSON.stringify(content);\n    } catch (err) {\n      throw err;\n    }\n    res.setHeader(\"Content-Type\", \"application/json\");\n    return end(content);\n  };\n\n  res.redirect = (url) => {\n    res.setHeader(\"Location\", url);\n    res.status(301);\n    res.end();\n    return res;\n  };\n}\n\nmodule.exports = response;\n```\n\n### Updating our framework\n\nNow, let's update our framework and use the functionalities we just created. In `minimal.js`, make the following changes\n\n```js{2-3,10-11,13-16}\n...\nconst request = require('./request');\nconst response = require('./response');\n\nfunction Minimal() {\n  ...\n    function listen(port = 8080, cb) {\n    return http\n      .createServer((req, res) => {\n        request(req);\n        response(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\n```\n\n### Middlewares\n\nLet's first see what Express docs say about them\n\n> Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.\n\nSo, when we request a server then it takes in the request and returns a response. During this cycle, multiple functions can transform the request or response object, execute some code, end the request altogether or simply pass on the request to the next function in the array. These functions which execute during the request-response lifecycle are called `Middlewares`.\n\n#### Usage\n\nAll APIs exposed by Express i.e. `use`, `post`, `put`, `get` and others are based on a middleware system. However, in this series, we are going to focus on the `use` method. Middlewares can be global or bound to a path.\n\n```js\nconst app = express();\n\n// Function will execute every time the app receives a request\napp.use((req, res) => {\n  /* some processing */\n});\n\n// Function will execute only when a request is made to path /about\napp.use(\"/about\", (req, res) => {\n  /* some processing */\n});\n```\n\nTime to make changes to our `minimal.js` to accommodate this functionality.\n\n```js\n...\nfunction Minimal() {\n  const _middlewares = [];\n\n  function use(...args) {\n    let path = '*';\n    let handler = null;\n\n    if (args.length === 2) [path, handler] = args;\n    else handler = args[0];\n\n    if (typeof path !== 'string') throw new Error('Path needs to be a string');\n    else if (typeof handler !== 'function') throw new Error('Middleware needs to be a function');\n\n    _middlewares.push({\n      path,\n      handler\n    });\n  }\n  ...\n  return {\n    use,\n    listen\n  }\n}\n```\n\nIn the above code snippet:\n\n1. We are exposing a new method `use`, which primarily takes two parameters -- `path` and its `handler`.\n2. We created an array called `\\_middlewares` which will be an array of objects where each object contain a path and its handler. So, whenever an instance of our app invokes the `use` method then arguments provided will be pushed into our middleware array.\n3. We added some validations that our `path` needs to be a string and `handler` needs to be a function only.\n4. We are currently not supporting regex in `path`.\n\n#### Middleware Handling\n\nSo far, we have extended our `request`, `response` and added the `use` method to our framework. Now, when the method is invoked and middleware is pushed to our array then we need to handle it as in executing the function on all matching paths. Unlike, our life problems from which we run away, this one we need to handle.\n\nLet's start by refactoring our `use` method a bit. We will extract the path and handler determination logic and move it to a helper file.\n\n```\ncd ./src\nmkdir lib\ncd ./lib\ntouch helpers.js\n```\n\nAdd the following code to the `helpers.js` file\n\n```js\nfunction checkMiddlewareInputs(args) {\n  let path = \"*\";\n  let handler = null;\n\n  if (args.length === 2) [path, handler] = args;\n  else handler = args[0];\n\n  if (typeof path !== \"string\")\n    throw new Error(\"Path needs to be either a string\");\n  else if (typeof handler !== \"function\")\n    throw new Error(\"Middleware needs to be a function\");\n\n  return {\n    path,\n    handler,\n  };\n}\n\nmodule.exports = { checkMiddlewareInputs };\n```\n\nUsing this in our `minimal.js`\n\n```js\n...\nconst { checkMiddlewareInputs } = require('./lib/helpers');\n\nfunction Minimal() {\n  ...\n  function use(...args) {\n    const { path, handler } = checkMiddlewareInputs(args);\n    _middlewares.push({\n      path,\n      handler\n    });\n  }\n  ...\n}\n```\n\nWe need to modify our `listen` method because as we have seen earlier all requests go through the `requestListener` function handler which we provide to `createServer`. We are going to create a `handle` method that would be responsible for executing all our middlewares sequentially on each request.\n\n```js{2-4,10}\n...\n  function handle(req, res) {\n    /* Will do middleware handling here*/\n  }\n  function listen(port = 8080, cb) {\n    return http\n      .createServer((req, res) => {\n        request(req);\n        response(res);\n        handle(req, res);\n      })\n    ...\n  }\n...\n```\n\n#### Execution Handling\n\nEvery middleware takes 3 arguments: `request` object, `response` object and the `next` function. Consider, `next` here as a way of telling the framework that current execution is over and you can move on to the next middleware in the array. If a middleware doesn't call the `next` then our request-response cycle will be stuck. So, middleware must call `next`!\n\nFrom our array of middlewares, we are going to find the next middleware and execute it. To do so, we are going to modify the `handle` method and going to add the `findNext` method which is responsible for returning the next function.\n\n```js\n...\nconst { matchPath } = require('./lib/helpers');\n\n...\nfunction findNext(req, res) {\n  let current = -1;\n  const next = () => {\n    current += 1;\n    const middleware = _middlewares[current];\n    const { matched = false, params = {} } = middleware ? matchPath(middleware.path, req.pathname) : {};\n\n    if (matched) {\n      req.params = params;\n      middleware.handler(req, res, next);\n    } else if (current <= _middlewares.length) {\n      next();\n    }\n  };\n  return next;\n}\n\nfunction handle(req, res) {\n  const next = findNext(req, res);\n  next();\n}\n...\n```\n\nBreaking the above code snippet step by step\n\n1. Our `findNext` method returns a function called `next` which tracks the current middleware as in the one which is going to be executed, by maintaining the counter which updates on every call.\n\n   - Initially, the current will be -1 and on the first `next` call, it will be updated to 0 and then the first middleware in the array will be returned and so on.\n   - Returned value can be a middleware or undefined (If the array is empty or we just executed the last element in the array).\n   - Then we match the path provided at the time of `use` invocation to the current request path. If matched then execute else move on.\n\n2. `matchPath` is a utility function in our `helpers.js`. Add the following code to the helper.js.\n\n```js\n...\nfunction matchPath(setupPath, currentPath) {\n  const setupPathArray = setupPath.split('/');\n  const currentPathArray = currentPath.split('/');\n  const setupArrayLength = setupPathArray.length;\n\n  let match = true;\n  let params = {};\n\n  for (let i = 0; i < setupArrayLength; i++) {\n    var route = setupPathArray[i];\n    var path = currentPathArray[i];\n    if (route[0] === ':') {\n      params[route.substr(1)] = path;\n    } else if (route === '*') {\n      break;\n    } else if (route !== path) {\n      match = false;\n      break;\n    }\n  }\n\n  return match ? { matched: true, params } : { matched: false };\n}\n\nmodule.exports = { checkMiddlewareInputs, matchPath };\n```\n\nIt split the paths provided at the time of middleware setup and current request handling into arrays. Each ith element of both arrays is matched to determine if both paths are the same. However, we make two exceptions here:\n\n- If we encounter the `:` character at the start of the array element then we consider it as a `param` and add it to our `params` object. This is done so to accommodate paths like\n\n```js\napp.use(\"/user/:userId\", () => {});\n```\n\n- If the array element is `*` character then we break the loop because it will be a catch-all route.\n\n```js\napp.use(\"/*\", () => {});\n```\n\nNow, after the processing, we return an object which may contain two properties, `matched` and `params`. We add the params to the request object so that any middleware handler can use those params.\n\n### Testing\n\nLet's test out what we have built so far. We will install an npm package that allows making CORS requests.\n\n```bash\nnpm i cors --save\n```\n\nThen replace the contents of `server.js` with the following\n\n```js\nconst cors = require(\"cors\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\nconst minimal = require(\"./src/minimal\");\nconst CONFIG = require(\"./config\");\n\nconst app = minimal();\n\napp.use(\"/about\", cors());\napp.use(\"/about\", (req, res, next) => {\n  res.send(\"I am the about page\");\n  next();\n});\n\napp.use(\"/\", (req, res, next) => {\n  fs.readFile(path.resolve(__dirname, \"public\", \"index.html\"), (err, data) => {\n    if (err) {\n      res.status(500).send(\"Error Occured\");\n      return next();\n    }\n    res.status(200).send(data);\n    return next();\n  });\n});\n\nconst server = app.listen(CONFIG.PORT, () =>\n  console.log(`Server running on ${CONFIG.PORT}`)\n);\n```\n\nNow, run the server\n\n```\nnpm run start\n```\n\nIf you visit the `http://localhost:8080` then you will see our good old HTML as before. However, if you visit `http://localhost:8080/about` and open the devtools (no pun intended :P) then you will see a new `response` header `Access-Control-Allow-Origin: \\*` which is set by our `CORS` package.\n\n![Testing](https://ik.imagekit.io/devtoolstech/build-your-own-express-js-part-two/testing_BHHVl2HdosD.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1642879188459)\n\nYayy!! Our framework is working!! :P In the next part, we are going to talk about Routing. Stay tuned!\n\nComplete code for this part can be found [here](https://github.com/yomeshgupta/minimaljs/tree/part-2).\n","languages":[],"editorConfig":{}},"stats":{"views":16937,"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-2---rid---negw30VulwpaVLRpxxMl","isPremium":false,"categories":[],"requires":[],"_id":"5f1dd731cbec5f7ffc0c2fab","title":"Build your own expressjs | Part 2","resourceId":"negw30VulwpaVLRpxxMl","createdAt":1595791153876,"modifiedAt":1643031768236},"currentUser":null,"isOwner":false,"recommendations":{"questions":[{"_id":"67e91a08fdd10aa2af462f37","content":{"languages":["javascript","typescript"],"difficulty":4},"tags":["javascript","ui","ux","interview question","frontend","coding","compression","microsoft"],"slug":"string-compression-and-decompression---qid---H4BQSlugnCg464F86rCi","title":"String Compression and Decompression","questionId":"H4BQSlugnCg464F86rCi"},{"_id":"6058cbbbdd90b43b9e8dee08","content":{"difficulty":4,"languages":"javascript"},"tags":["javascript","hoisting","interview","js fundamentals","frontend fundamentals","scoping"],"slug":"what-would-be-the-output-of-the-following-code-scoping-in-javascript---qid---sE7fxd7MMAoOO0t9yXwD","title":"What would be the output of the following code? [Scoping in JavaScript]","questionId":"sE7fxd7MMAoOO0t9yXwD"},{"_id":"6307a2a177f9961d5b7cbf53","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["","javascript","problem solving","oops","inhertiance","javascript prototype","methods","javascript interview questions","vanilla js","beginner frontend questions"],"slug":"how-to-implement-a-developer-builder-interface-or-frontend-problem-solving-or-javascript-interview-question---qid---CjH1hGv2r19ouztiDck2","title":"How to implement a Developer Builder Interface? | Frontend Problem Solving | JavaScript Interview Question ","questionId":"CjH1hGv2r19ouztiDck2"},{"_id":"5e8ce95cb2cdcd5697fea7bf","content":{"languages":["javascript"],"difficulty":1},"tags":["node.js","javascript","frontend","code","logic","variables","block scope","scoping","hoisting"],"title":"What is the output of the following code snippet? | JavaScript Output Based Question | JS Variables","questionId":"fKtjCwgbBqNJSPj4c6gX","slug":"what-is-the-output-of-the-following-code-snippet-or-javascript-output-based-question-or-js-variables---qid---fKtjCwgbBqNJSPj4c6gX"},{"_id":"686bc18450393ab5506e5d0c","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","frontend","interview questions","ui","ux","devtools tech","frontend coding","coding challenge"],"slug":"compare-semantic-versions---qid---MpUr21sj14SgVcWFVs2L","title":"Compare Semantic Versions","questionId":"MpUr21sj14SgVcWFVs2L"}],"resources":[{"_id":"698b2bde895fc3bf01415e40","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","coding","frontend","devtools tech","frontend interview experience"],"slug":"zomato-frontend-interview-experience-or-sde-2---rid---7hUoqLgUfYf13XaOXRlq","title":"Zomato Frontend Interview Experience | SDE 2","resourceId":"7hUoqLgUfYf13XaOXRlq"},{"_id":"69234db2bf1a48f85e0d254f","content":{"difficulty":1,"domain":1,"type":2,"isInternal":false,"languages":[]},"tags":["javascript","ui","ux","csr","ssr","rendering","frontend system design"],"slug":"mastering-rendering-techniques-csr-ssr-streaming-with-selective-hydration---rid---Gw29cwskE3GjZbvpAcql","title":"Mastering Rendering Techniques: CSR, SSR, Streaming with Selective Hydration","resourceId":"Gw29cwskE3GjZbvpAcql"},{"_id":"694fda82e4605484c24c29de","content":{"difficulty":4,"domain":1,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","urls","ui","ux","frontend system design","questions","resources","tooling"],"slug":"urls-and-routing-in-frontend-applications---rid---n9dDAOaX0nmLvjnDn0uU","title":"URLs and Routing in Frontend Applications","resourceId":"n9dDAOaX0nmLvjnDn0uU"},{"_id":"5fb4d1456d3cda64e470c40c","content":{"difficulty":2,"domain":2,"type":2},"tags":["javascript","frontend","node.js","dom","frontend fundamentals","interview questions"],"slug":"dom-api-programming-question-or-frontend-interview-questions---rid---AA4msQuDtbwicYNMLOt3","title":"DOM API Programming Question | Frontend Interview Questions","resourceId":"AA4msQuDtbwicYNMLOt3"},{"_id":"5f216ec9cbec5f7ffc0c2fbf","content":{"difficulty":2,"domain":17,"type":2},"tags":["edureka","youtube","ethical hacking","hacking","course","free tutorials","coding","programming"],"slug":"ethical-hacking-full-course-learn-ethical-hacking-in-10-hours-or-ethical-hacking-tutorial-or-edureka---rid---rI7YdO22ISG77yRk7STP","title":"Ethical Hacking Full Course - Learn Ethical Hacking in 10 Hours | Ethical Hacking Tutorial | Edureka","resourceId":"rI7YdO22ISG77yRk7STP"}]}}