70-Nodejs Course 2023: Super Middlewares
Tags: node, typescript, mongodb, fastify
Published: 2022-11-25 05:17:22

---
title: 70-Nodejs Course 2023: Super Middlewares
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obxeilykwx9dy4qazbjz.png
# Use a ratio of 100:42 for best results.
---


Lately, we've introduced a new concept called `Global Middlewares (i'll call it GM)`, which is a very useful concept, it allows us to apply one or more middlewares to all the routes, now let's see how to make it more specific, how to apply middlewares to specific routes.

## Specific middlewares for specific routes

Now we've implemented `GM` in our app, but we want to expand it more.

We can split the middleware into three types:

* `all` - applies to all routes
* `only` - applies to specific routes
* `except` - applies to all routes except specific routes

Let's see how to implement it.

### Updating Http Configurations

We have created the `http` config file and added `middleware` property to it, we will now split it into three properties:

```ts
// src/config/http.ts
const httpConfigurations = {
  middleware: {
    // apply the middleware to all routes
    all: [],
    // apply the middleware to specific routes
    only: {
      routes: [],
      middleware: [],
    },
    // exclude the middleware from specific routes
    except: {
      routes: [],
      middleware: [],
    },
  },
};

export default httpConfigurations;
```

Here we updated the middleware to be an object, and we added three properties to it:

* `all` - applies to all routes, so we just pass list of middlewares to it.
* `only` - applies to specific routes, so we pass the list of routes and the list of middlewares to it.
* `except` - applies to all routes except specific routes, so we pass the list of routes and the list of middlewares to it.

## Named Routes

Remember, we added long time ago `name` property to route options and i told you we'll use it later, now its time to use it.

### Purpose of Named Routes

We can use the `name` property to identify the route, and we can use it to apply middlewares to specific routes.

So if we wanted later to change the route path, we can do it easily, and we don't need to change the middleware configuration.

So an example of usage might be something like:

```ts
// src/config/http.ts
const httpConfigurations = {
  middleware: {
    // apply the middleware to all routes
    all: [],
    // apply the middleware to specific routes
    only: {
      routes: ['auth.login', 'auth.register'],
      middleware: [],
    },
    // exclude the middleware from specific routes
    except: {
      routes: ['auth.logout', 'auth.refresh'],
      middleware: [],
    },
  },
};
```

And so on, you know what, actually i just thought why not add both features, `routes` and `namedRoutes`, so we can use both of them.

```ts
// src/config/http.ts
const httpConfigurations = {
  middleware: {
    // apply the middleware to all routes
    all: [],
    // apply the middleware to specific routes
    only: {
      routes: ['login', '/register'],
      namedRoutes: ['auth.login', 'auth.register'],
      middleware: [],
    },
    // exclude the middleware from specific routes
    except: {
      routes: ['/logout', '/refresh-token'],
      namedRoutes: ['auth.logout', 'auth.refresh'],
      middleware: [],
    },
  },
};
```

Now we've a lot of work to do, so let's get started quickly.

## Upgrading Middlewares

Now let's move our middleware to `V2`, earlier we had made it just an array of middlewares, now le'ts update it to match our new cases

## Upgrade workflow

So how this is going to work? let's see

We've three cases here:

* The `all` which is the simplest, we'll just merge all middlewares in that array with current route middlewares.
* `only` which we'll check for the route if exists in the  `routes` array or if the route has a `name` we'll check if its name exists in the `namedRoutes` array.
* `except` in this one, we'll see if our current request route **not exists** in the `routes` list nor in `namedRoutes` in that case we'll merge with current route middlewares, otherwise skip its middlewares.

## Let's implement it

This is the current `executeMiddleware` method:

```ts
// src/core/http/request.ts
// ..
  /**
   * Execute middleware list of current route
   */
  protected async executeMiddleware() {
    // get route middlewares
    const routeMiddlewares = this.route.middleware || [];
    // get global middlewares
    const globalMiddlewares = config.get("http.middleware", []);

    // merge global and route middlewares
    const middlewares = [...globalMiddlewares, ...routeMiddlewares];

    // check if there are no middlewares, then return
    if (middlewares.length === 0) return;

    // trigger the executingMiddleware event
    this.trigger("executingMiddleware", middlewares, this.route);

    for (const middleware of middlewares) {
      const output = await middleware(this, this.response);

      if (output !== undefined) {
        this.trigger("executedMiddleware");
        return output;
      }
    }

    // trigger the executedMiddleware event
    this.trigger("executedMiddleware", middlewares, this.route);
  }
```

Instead of making the method bigger, we can actually make another method just to collect middlewares list for current route, let's call it `collectMiddlewares`

```ts
// src/core/http/request.ts
// ...

  /**
   * Collect middlewares for current route
   */
  protected collectMiddlewares(): Middleware[] {
    const middlewaresList: Middleware[] = [];

    return middlewaresList;
  }
```

We just defined the method and set its return type to return list of middlewares, inside it i only defined an empty array and returned it.

Now let's write our steps for collecting these middlewares, we also need to make it `ordered` so priority will work accordingly.

> Lower priority means it will be executed lastly, for example `all` middlewares will have highest priority thus it will be executed before any other middlewares

We will collect middlewares from high to priority.

1. Collect middlewares from `all` list.
2. Collect middlewares from `only` list.
3. Collect middlewares from `except` list.
4. Collect middlewares from `current route`, this will have the least priority

So the order for execution will go from `1` to `4` in the previous list, let's see it.

```ts
// src/core/http/request.ts
// ...

  /**
   * Collect middlewares for current route
   */
  protected collectMiddlewares(): Middleware[] {
    // we'll collect middlewares from 4 places
    // We'll collect from http configurations under `http.middleware` config
    // it has 3 middlewares types, `all` `only` and `except`
    // and the final one will be the middlewares in the route itself
    // so the order of collecting and executing will be: `all` `only` `except` and `route`
    const middlewaresList: Middleware[] = [];

    // 1- collect all middlewares as they will be executed first
    const allMiddlewaresConfigurations = config.get("http.middleware.only");

    // check if it has middleware list
    if (allMiddlewaresConfigurations?.middleware) {
      // now just push everything there
      middlewaresList.push(...allMiddlewaresConfigurations.middleware);
    }

    // 2- check if there is `only` property
    const onlyMiddlewaresConfigurations = config.get("http.middleware.only");

    if (onlyMiddlewaresConfigurations?.middleware) {
      // check if current route exists in the `routes` property
      // or the route has a name and exists in `namedRoutes` property
      if (
        onlyMiddlewaresConfigurations.routes?.includes(this.route.path) ||
        (this.route.name &&
          onlyMiddlewaresConfigurations.namedRoutes?.includes(this.route.name))
      ) {
        middlewaresList.push(...onlyMiddlewaresConfigurations.middleware);
      }
    }

    // 3- collect routes from except middlewares
    const exceptMiddlewaresConfigurations = config.get(
      "http.middleware.except",
    );

    if (exceptMiddlewaresConfigurations?.middleware) {
      // first check if there is `routes` property and route path is not listed there
      // then check if route has name and that name is not listed in `namedRoutes` property
      if (
        !exceptMiddlewaresConfigurations.routes?.includes(this.route.path) &&
        this.route.name &&
        !exceptMiddlewaresConfigurations.namedRoutes?.includes(this.route.name)
      ) {
        middlewaresList.push(...exceptMiddlewaresConfigurations.middleware);
      }
    }

    // 4- collect routes from route middlewares
    if (this.route.middleware) {
      middlewaresList.push(...this.route.middleware);
    }

    return middlewaresList;
  }
```

The code is pretty much self explained, but let me focus on multiple things here:

Firstly, let's note that is used `optional chaining` feature in mostly every check, this saves me writing the same code, for example i wrote `onlyMiddlewaresConfigurations?.middleware` this is equivalent to the following

```ts
if (onlyMiddlewaresConfigurations?.middleware) {
  //
}

  // is the same as 
if (onlyMiddlewaresConfigurations && onlyMiddlewaresConfigurations.middleware) {
  //
}
```

Secondly, as the method is a little complex and more ambiguous, i wrote multiple inline comments so if if forgot what is going there i can remember from my comments, or even better, if your mate or boss is reading your code, he/she will be so **delightful**.

Furthermore, i continued writing comments above every middleware collector so i know what i'm going to do in that if statement.

Now let's update our `executeMiddleware` method to use `collectMiddlewares` instead of just the route middlewares.

```ts
// src/core/http/request.ts
// ...
  /**
   * Execute middleware list of current route
   */
  protected async executeMiddleware() {
    // collect all middlewares for current route
    const middlewares = this.collectMiddlewares();

    // check if there are no middlewares, then return
    if (middlewares.length === 0) return;

    // trigger the executingMiddleware event
    this.trigger("executingMiddleware", middlewares, this.route);

    for (const middleware of middlewares) {
      const output = await middleware(this, this.response);

      if (output !== undefined) {
        this.trigger("executedMiddleware");
        return output;
      }
    }

    // trigger the executedMiddleware event
    this.trigger("executedMiddleware", middlewares, this.route);
  }
```

Now let's try our `named` routes as we didn't even try it when we added that feature.

```ts
// src/app/users/routes.ts
// ...
router.get("/users", usersList, {
  // add the route name
  name: "users.list",
});

```

I removed the middleware from the users list and added the route name, i prefer to use the module name as a prefix then the state of the route, or what is it going to do, so i named it `users.list`.

Now let's use it with `only` for example

```ts
// src/config/http.ts
import { authMiddleware } from "core/auth/auth-middleware";

const httpConfigurations = {
  middleware: {
    // apply the middleware to all routes
    all: [],
    // apply the middleware to specific routes
    only: {
      routes: [],
      namedRoutes: ["users.list"],
      middleware: [authMiddleware("user")],
    },
    // exclude the middleware from specific routes
    except: {
      routes: [],
      namedRoutes: [],
      middleware: [],
    },
  },
};

export default httpConfigurations;
```

Now if we tried the request in postman, it should only work if the current user is logged in.

If you want to make sure this works fine, change the `user` to ``guest` argument in `authMiddleware` function, you should see then ` "error": "You are not allowed to access this resource"`

## 🎨 Conclusion

We made a good progress in `middlewares` we added multiple ways to define middlewares either locally and globally, in our next article, we're going to introduce our new route feature, the `group` method.

## ☕♨️ Buy me a Coffee ♨️☕

If you enjoy my articles and see it useful to you, you may [buy me a coffee](https://www.paypal.com/paypalme/hassanzohdy), it will help me to keep going and keep creating more content.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) 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](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), 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

* [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
* [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
* [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

* [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
* [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
* [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

* [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

* [OOP In JS And TS From The Very Beginning](https://dev.to/hassanzohdy/jsts-oop-course-1-introduction-2ld)
* [ES6++: The Ultimate Guide to ES6 and Beyond](https://dev.to/hassanzohdy/es6-1-introduction-h3e)
* [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)