Build your First Node.js Web Framework Step by Step

Poorshad Shaddel
Level Up Coding
Published in
6 min readJan 2, 2023

--

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

Build Your First Node.js Web Framework

In the previous article, we took a look at Expressjs 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.

Node.js HTTP Library

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
Request Handler

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:

Middlewares

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:

Example of WebFramework

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.

Match Function

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:

Usage of our library

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

Response to the request.

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

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:

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:

New Match function

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.

Not Found Middleware

Now if we send a request to an unknown URL:

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):

Return JSON data

Our FastFramework function looks like this after this change:

Return JSON Data

Let’s use this function in a simple middleware:

Return JSON Data

This is the result.

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:

Body Parser

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.

Body Parser — Before and After

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

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.

Github Repository: https://github.com/pshaddel/simple-nodejs-webframework

Another Article that might be interesting for you:
Behind the Scenes of ExpressJS: A Deep Dive into Nodejs’ Most Popular Library

--

--

Full Stack Developer with more than 6 years of expericence. I spend partial part of the day working with Node.js and React.