DEV Community

Cover image for 54-Nodejs Course 2023: Request Middleware
Hasan Zohdy
Hasan Zohdy

Posted on

54-Nodejs Course 2023: Request Middleware

We're still in the middle of events, but we need to introduce a new concept first before we move on as we need it in our request events, The concept is called Middleware.

Middleware

Middleware is a function or list of functions that is executed before the request handler, it can be used to modify the request object, or to add some functionality to the request.

For example, we can use a middleware to check if the user is authenticated or not, or we can use it to check if the user has the required permissions to access the requested route, if not then we can stop the request and return a response error.

Request Middleware

Request middleware is a middleware that is executed before the request handler, it can be used to modify the request object, or to add some functionality to the request, or stop the request and return a response error/success based on certain conditions.

Request Middleware Implementation

Now let's start implementing the request middleware, if you recall, the router methods accepts two parameters, the first one is the route path, and the second one is the request handler, so we need to add a third parameter to the router methods, that one will be an options list, and we will add the middleware option to it.

Open src/core/router/types.ts and add let's update the Route type.

// src/core/router/types.ts
import { Request } from "core/http/request";

/**
 * Middleware method
 */
export type Middleware = (request: Request, response: any) => any;

export type RouteOptions = {
  /**
   * Route middleware
   */
  middleware?: Middleware[];
  /**
   * Route name
   */
  name?: string;
};

/**
 * Route Object
 */
export type Route = RouteOptions & {
  /**
   * Route method
   */
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
  /**
   * Route path
   */
  path: string;
  /**
   * Route handler
   */
  handler: any;
};
Enter fullscreen mode Exit fullscreen mode

We defined the Middleware type, and we added the middleware option to the RouteOptions type.

Any middleware will receive the request object and the response object as parameters, if the middleware returns a value, then it will be used as the response, and the request handler and all other middlewares will not be executed.

Also we added a new option to the RouteOptions type, that one is the name option, it will be used to give a name to the route, we will use it later in the request events.

Finally we merged the RouteOptions type with the Route type.

Now let's update our Router class.

// src/core/router/index.ts
import request from "core/http/request";
import { Route, RouteOptions } from "./types";

export class Router {
  /**
   * Routes list
   */
  private routes: Route[] = [];

  /**
   * Router Instance
   */
  private static instance: Router;

  /**
   * Get router instance
   */
  public static getInstance() {
    if (!Router.instance) {
      Router.instance = new Router();
    }

    return Router.instance;
  }

  private constructor() {
    //
  }

  /**
   * Add get request method
   */
  public get(path: string, handler: any, options: RouteOptions = {}) {
    this.routes.push({
      method: "GET",
      path,
      handler,
      ...options,
    });

    return this;
  }

  /**
   * Add post request method
   */
  public post(path: string, handler: any, options: RouteOptions = {}) {
    this.routes.push({
      method: "POST",
      path,
      handler,
      ...options,
    });

    return this;
  }

  /**
   * Add put request method
   */
  public put(path: string, handler: any, options: RouteOptions = {}) {
    this.routes.push({
      method: "PUT",
      path,
      handler,
      ...options,
    });

    return this;
  }

  /**
   * Add delete request method
   */
  public delete(path: string, handler: any, options: RouteOptions = {}) {
    this.routes.push({
      method: "DELETE",
      path,
      handler,
      ...options,
    });

    return this;
  }

  /**
   * Add patch request method
   */
  public patch(path: string, handler: any, options: RouteOptions = {}) {
    this.routes.push({
      method: "PATCH",
      path,
      handler,
      ...options,
    });

    return this;
  }

  /**
   * Get all routes list
   */
  public list() {
    return this.routes;
  }

  /**
   * Register routes to the server
   */
  public scan(server: any) {
    this.routes.forEach(route => {
      const requestMethod = route.method.toLowerCase();
      const requestMethodFunction = server[requestMethod].bind(server);

      requestMethodFunction(route.path, this.handleRoute(route));
    });
  }

  /**
   * Handle the given route
   */
  private handleRoute(route: Route) {
    return async (fastifyRequest: any, fastifyResponse: any) => {
      request
        .setRequest(fastifyRequest)
        .setResponse(fastifyResponse)
        .setHandler(route.handler);

      return await request.execute();
    };
  }
}

const router = Router.getInstance();

export default router;
Enter fullscreen mode Exit fullscreen mode

We added the options parameter to the router methods, and we merged it with the route object.

Now let's update the handleRoute method to receive the entire route object, and to call the execute method on the request object.

// src/core/router/index.ts
private handleRoute(route: Route) {
  return async (fastifyRequest: any, fastifyResponse: any) => {
    request
      .setRequest(fastifyRequest)
      .setResponse(fastifyResponse)
      .setRoute(route);

    return await request.execute();
  };
}
Enter fullscreen mode Exit fullscreen mode

Now let's update our Request class to receive the route object instead of the handler.

// src/core/http/request.ts
import { Route } from "core/router/types";

export class Request {
  // ...

  /**
   * Route Object
   * 
   * Notice the `!` after the `Route` type, it means that the route is required.
   */
  private route!: Route;
  // remove the handler property

  /**
   * Set route handler
   */
  public setRoute(route: Route) {
    this.route = route;

    return this;
  }

  // remove the setHandler method
Enter fullscreen mode Exit fullscreen mode

We replaced the handler property with the route property, and we added the setRoute method and removed the setHandler method.

Now let's update the execute method to call the middleware and the handler.

// src/core/http/request.ts

  /**
   * Execute the request
   */
  public async execute() {
    // check for middleware first
    if (this.route.middleware) {
      for (const middleware of this.route.middleware) {
        const output = await middleware(this, this.response);

        if (output !== undefined) {
          return output;
        }
      }
    }

    const handler = this.route.handler;

    // check for validation
    if (handler.validation) {
      if (handler.validation.rules) {
        const validator = new Validator(this, handler.validation.rules);

        try {
          await validator.scan(); // start scanning the rules
        } catch (error) {
          console.log(error);
        }

        if (validator.fails()) {
          const responseErrorsKey = config.get(
            "validation.keys.response",
            "errors",
          );
          const responseStatus = config.get("validation.responseStatus", 400);

          return this.response.status(responseStatus).send({
            [responseErrorsKey]: validator.errors(),
          });
        }
      }

      if (handler.validation.validate) {
        const result = await handler.validation.validate(this, this.response);

        if (result) {
          return result;
        }
      }
    }

    return await handler(this, this.response);
  }
Enter fullscreen mode Exit fullscreen mode

In the execute method, we started by checking if the route has middleware, and if it does, we loop through the middleware and call each one of them, and if the middleware returns a value, we return it.

Then we check if the route has validation, and if it does, we check if the route has rules, and if it does, we create a new validator instance and we scan the rules, and if the validator fails, we return the errors.

Then we check if the route has a validation function, and if it does, we call it, and if it returns a value, we return it.

Finally, we call the handler and return the result.

And that's it!

Usage

Now let's see how to use it in our application.

Open src/app/users/route.ts and update it to the following:

// src/app/users/route.ts
import router from "core/router";
import { Middleware } from "core/router/types";
import createUser from "./controllers/create-user";
import getUser from "./controllers/get-user";
import usersList from "./controllers/users-list";

const middleware1: Middleware = (request, response) => {
  console.log("middleware 1 is executed but not returning anything");
};

const middleware2: Middleware = (request, response) => {
  console.log(
    "middleware 2 is executed but returning a value that will stop the execution and return the value",
  );

  return {
    interrupted: true,
  };
};

router.get("/users", usersList);
router.get("/users/:id", getUser);
router.post("/users", createUser, {
  middleware: [middleware1, middleware2],
});
Enter fullscreen mode Exit fullscreen mode

Now try to make a POST request to /users, it will not execute even the validation, because the middleware returned a value.

Also you'll see something like this in your console:

Middleware

🎨 Conclusion

In this tutorial, we learned how to implement middleware in our application, and how to use it.

In our next article, we'll resume our events on requests.

🚀 Project Repository

You can find the latest updates of this project on Github

😍 Join our community

Join our community on Discord to get help and support (Node Js 2023 Channel).

🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.

📚 Bonus Content 📚

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)