Build your First Node.js Web Framework Step by Step
Tags: express, webframework, node
Published: 2023-09-12 11:56:12

---
title: Build your First Node.js Web Framework Step by Step
published: true
date: 2023-01-02 01:27:22 UTC
tags: expressjs,webframework,nodejs
canonical_url: https://levelup.gitconnected.com/build-your-first-node-js-web-framework-39b3310ed35a
---

We want to create a super simple web framework in order to get an idea of how web framework libraries are working.

![](https://cdn-images-1.medium.com/max/1024/1*MURGPNrxY97ghOi-ARrXEg.png)
_Build Your First Node.js Web Framework_

In the previous [article](https://p-shaddel.medium.com/expressjs-deep-dive-387f07c50d95?source=user_profile---------0----------------------------), we took a look at [Expressjs](https://github.com/expressjs/express) and tried to understand how this library works. In this article, we want to build something super simple in order to feel the challenges and feel comfortable when we want to take a look at the source code of other libraries.

Let’s get to coding without hesitation.

#### Handle Requests using Node.js HTTP Library

We are going to use the Node.js HTTP library which is one of the core libraries and you do not need to install anything.

```javascript
const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end('pong');
});

server.listen(8000);
```

Now if we send a request to any route in localhost:8000/anything/you/want we get pong as a result. Now let’s implement our features.

#### Our Strategy for Handling Requests

For handling a request in a normal application most of the time we are doing _totally different operations_ on the same incoming request. As an example, a request wants to update a user. And imagine this is how the request looks like:

```
Method: POST
URL: "http://ourAddress/users/userId"
JSON Body of request: { name: "something New" }
```

As a backend service I want to do different operations on this request:

- Authorization
- Parse Body of the request
- Log the operation and the person who did the operation
- Update the User in the Database
```javascript
function myHandler(req, res) {
    // Authorization
    const isAuthorizedUser = authorizeRequest(req);
    if (!isAuthorizedUser) {
        // return some bad error
        res.send(...);
        return;
    }

    // Parse Body of the Request
    bodyParser(req);

    // Log the error
    logger(req);


    // Update the user
    updateUser(req);

    res.end();
}
```

This code is totally OK. But imagine there are more than twenty different routes in the same application and all of them need almost the same operations. All of them need to log every action or authorize the request. So we need a way to reduce code duplications. One common strategy for solving this issue is the concept of **middleware**. ExpressJS uses this concept and we also want to implement something like this.

Now look at this code:

```javascript
const http = require('node:http');

const server = http.createServer((req, res) => {
    function authorizationMiddleware(req, res) {
        const isAuthorizedUser = authorizeRequest(req);
        if (!isAuthorizedUser) {
            // return some bad error
            res.send(...);
            // How to tell next functions that we already send the request to the user?
        } else {
            // What to do? should go to the next function
        }
    }
    function bodyParserMiddleware(req, res) {
        bodyParser(req);
    };
    function logger(req) {
        console.log(req);
    };
    function updateUser(req, res) {
        updateUser(req);
        res.end();
    }
    authorizationMiddleware(req, res);
    bodyParserMiddleware(req, res);
    logger(req, res);
    updateUser(req, res);
});

server.listen(8000);
```

In this case, we only have one route in our application and it updates the user.

#### Why Do You Need a Next Function?

There is a big difference between authorizationMiddleware and the other ones, if the user is unauthorized we are returning the response to the user and it does not make sense to send the request to other handlers like body-parser, logger, or update user. **So we need a mechanism for knowing when the request is sent and we should stop handing over the request and response object to the middlewares.**

For doing this the first thing we need to do is to store these middlewares somewhere. Then we can implement the _next function_ to pass the request to these middlewares. At the same time, we can solve the problem of having different handlers for different functions.

In Expressjs they keep middlewares(handler functions) in an array. Let’s do it kind of the same way:

```javascript
const http = require('node:http');

function FastFramework() {
    // An array for keeping the middlewares
    const middlewares = [];
    const server = http.createServer((req, res) => {
      let middlewareCounter = 0;
      function nextFunction() {
        while(middlewareCounter < middlewares.length) {
          console.log("Next Function While Loop- Current Counter:", middlewareCounter);
          if (match(req, middlewares[middlewareCounter])) {
            return middlewares[middlewareCounter++].handler(req, res, nextFunction);
          } else {
            middlewareCounter++
            }
          }
            // If we are here then no middleware matched!!!!
        }
        nextFunction();
    });
    // A method that we can add middelwares
    server.use = function use(url, method, handler) {
        middlewares.push({
          url,
          method,
          handler
        })
    }
    return server;
}

// if the request is a match for the middleware or not
function match(req, { url, method }) {
    if (req.method !== method) return false;
    if (req.url !== url) return false;
    return true;
}

module.exports = { FastFramework }
```

The function FastFrameWork is just for returning the HTTP instance.

In line 5 we see an array for keeping all the middlewares. After creating an HTTP server we defined a variable for keeping track of the current middleware that should be handled.

#### Next Function

In line 8 we implemented the nextFunction and later on, we called this function so it can start processing the request and response, and we intentionally put the variable middlewareCounter outside the next function. In line 10 we have a while loop, it loops over middlewares until it finds a match.

If it is a match in line 12 we are calling the handler function of the middleware. The most important thing happens here:

return middlewares[middlewareCounter++].handler(req, res, nextFunction)   
We are also passing the next function as an argument to the handler, so we can use this argument to call this function and let other middlewares handle the request. If we are sending the response in the implementation of our middleware handlers and we are not calling the next function which means that this is the last stop for the request. If do not call the next function and we do not return any response then the request will timeout after a while. Also, we are increasing the counter variable because we want the next middleware next time we are in this piece of code.

If it was not a match in line 14 we just increase the variable so the while loop will check the next middleware or if it was the last middleware it gets out of the loop and it does nothing.

#### Use Function

Like Express I named it use method. This function just adds the middleware to the array of middlewares. The arguments are url , method and handler should be a function like this: (req, res, next) => {...} .

#### Match Function

Now let’s implement something for this match function. We can check if the method and URL are the same or not.

```javascript
function match(req, { url, method }) {
    if (req.method !== method) return false;
    if (req.url !== url) return false;
    return true;
}
```
We know that right now it cannot handle URL patterns like this:

/api/users/:id but that is OK.

#### Usage

We can import this library like this:

```javascript
const { FastFramework } = require("./lib");

const app = FastFramework();

app.use('/users', 'GET', (req, res, next) => {
    console.log('authrized the user...');
    next();
});
app.use('/users', 'GET', (req, res, next) => {
    console.log('logged the user...');
    next();
});
app.use('/users', 'GET', (req, res, next) => {
    console.log('prepating the list of users...');
    res.end('some users...');
});
app.use('/otherRoute', 'GET', (req, res, next) => {
    console.log('other route');
    res.end('other info');
});
app.listen(8000);
```

If we send a request we must see some users...

![](https://cdn-images-1.medium.com/max/1024/1*HR-_u4d0UT1wJN65k9TOsg.png)
_Response to the request._

We expect to see logs of all the routes in order except the /otherRoute which does not match the request:

![](https://cdn-images-1.medium.com/max/1024/1*OM58ABMwIGQuy-h_VfMLBg.png)
_Log of response to the request: GET /users_

As expected first middleware that is handled is the authorization, then the log, and finally the users.

Now if we send a request to /otherRoute we should only see the log of otherRoute:

![](https://cdn-images-1.medium.com/max/1024/1*JdJ93Cm8Fv8i5wLpDaCHaw.png)
_Log of the request to otherRoute_

#### How to Handle 404

For handling not found requests we need a middleware that matches every request. And we put this middleware as the last middleware so if we know that this request did not match with any of the middlewares. Let’s do this small change to our framework. It is just your choice how you want to implement this:

```javascript
function match(req, { url, method }) {
    if(!url && !method) return true;
    if (req.method !== method) return false;
    if (req.url !== url) return false;
    return true;
}
```

Now if we do not pass URL and Method to the use function it returns true which means that it going to be a match.

Let’s add our _not found middleware_ as the last middleware.

```javascript
app.use(null, null, (req, res) => {
    res.end('NOT_FOUND');
});
```

Now if we send a request to an unknown URL:

![](https://cdn-images-1.medium.com/max/1024/1*6wQncu0xBXw_ta8xYZ6QGA.png)
_Request to a route that does not exist_

#### Implement res.json Method

We need to add this code after creating the server(where we have the request and response):

```javascript
res.json = function(data) {
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify(data));
}
```

Our FastFramework function looks like this after this change:

```javascript
function FastFramework() {
    // An array for keeping the middlewares
    const middlewares = [];
    const server = http.createServer((req, res) => {
        res.json = function(data) {
            res.setHeader('Content-Type', 'application/json');
            res.end(JSON.stringify(data));
        }
        let middlewareCounter = 0;
        function nextFunction() {
            while(middlewareCounter < middlewares.length) {
                console.log("Next Function While Loop- Current Counter:", middlewareCounter);
                if (match(req, middlewares[middlewareCounter])) {
                    return middlewares[middlewareCounter++].handler(req, res, nextFunction);
                } else {
                    middlewareCounter++
                }
            }
            // If we are here then no middleware matched!!!!
        }
        nextFunction();
    });
    // A method that we can add middelwares
    server.use = function use(url, method, handler) {
        middlewares.push({
          url,
          method,
          handler
        })
    }
    return server;
}
```

Let’s use this function in a simple middleware:

```javascript
app.use('/users', 'GET', (req, res, next) => {
    console.log('prepating the list of users...');
    res.json({ list: ['user1', 'user2'], count: 2 });
})
```

This is the result.

![](https://cdn-images-1.medium.com/max/1024/1*UOIzBLM-lrJAtae8q7AFww.png)

#### Body Parser

We can use the body-parser library under the Expressjs project:

npm i body-parser

It is usable because our Framework also has the next function:

```javascript
const bodyParser = require('body-parser');

const app = FastFramework();

app.use(null, null, bodyParser);
```

Note that we did not pass a URL or method body parser will be used for every request after this middleware.

Let’s see the request body before and after using body-parser.

```javscript
const { FastFramework } = require("./lib");
const bodyParser = require('body-parser');

const app = FastFramework();

app.use('/user', 'POST', (req, res, next) => {
    console.log("body before:", req.body);
    next();
});

app.use(null, null, bodyParser.json());

app.use('/user', 'POST', (req, res) => {
    console.log("body after:", req.body);
    res.json({});
});
```

The response is as expected an empty object and as we can see in the logs the body of the request is parsed correctly in it is in the req.body

![](https://cdn-images-1.medium.com/max/1024/1*Ij3gWhUnr6WxT7A3npemjg.png)
_Logs of request body before and after using body parser._

### Summary

As you saw the most difficult thing was to add a concept like middleware for reusing the logic. For that, we implemented the **_next function_** and we were able to handle requests and pass the request and response objects between these middlewares. Feel free to add more functionality to your own HTTP Library.

* * *