Skip to main content
  1. Today I Learned/

Refactoring with routing-controllers: 1. Middleware

·714 words·4 mins
Maybe this article will be close to TID(Today I Did)🤔.
Check details code on here

refactoring with routing-controllers: 1. Middleware

Introduction #

Today, I will tell you how I refactored with routing-controllers.

routing-controllers supports @UseBefore and @UseAfter decorator for using existing middleware.
but, i will change the existing middleware to class-based middleware that implemented interfaces provided by routing-controllers.

  • ExpressMiddlewareInterface: interface for middleware
  • ExpressErrorMiddlewareInterface: interface for error middleware

Middleware #

Let’s look at the changes to the authentication middleware together.

Authentication Middleware can authenticate in different ways for different APIs, such as Local, JWT, Ldap, … etc.


Before #

using callback function object for each auth type.

auth.middleware.ts #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
type TAuthType = 'local' | 'jwt' | 'jwt-refresh';
type TVerifyCallback = {
  [K in TAuthType]: (req: Request, resolve: any, reject: any) => (err: any, user: Model<User>, info: object) => void;
};

const verifyCallback: TVerifyCallback = {
  local:
    (req: Request, resolve: any, reject: any) =>
    (err: any, user: Model<User>, info: any): void => {
      if (err || !user) {
        const error = new ApiError(httpStatus.UNAUTHORIZED, info?.message || err.message);
        reject(error);
      }

      req.user = user;
      resolve();
    },
  jwt:
    (req: Request, resolve: any, reject: any) =>
    (err: any, user: Model<User>, info: any): void => {
      ...
    },
};

const authMiddlewareFn = (req: Request, res: Response, next: NextFunction, authType: TAuthType) => {
  return new Promise((resolve, reject) => {
    passport.authenticate(authType, { session: false }, verifyCallback[authType](req, resolve, reject))(req, res, next);
  });
};

const auth = (authType: TAuthType): RequestHandler =>
  async (req: Request, res: Response, next: NextFunction): Promise<any> => {
    await authMiddlewareFn(req, res, next, authType)
      .then(() => {
        next();
      })
      .catch((err) => {
        return http.sendErrorResponse(res, err.statusCode, err);
      });
  };
using auth middleware, we can check the user’s authentication status. there are kinds of auth type, so we need to use the passport.authenticate function with the verifyCallback function.
in line 11, throw an error generated by the ApiError class if the user is not authenticated.


After #

Extending ExpressMiddlewareInterface interface of routing-controllers, create IAuthMiddleware interface for auth middleware.
Then build each auth method class you want.

middleware.interface.ts #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { NextFunction, Request, Response } from 'express';
import { ExpressMiddlewareInterface } from 'routing-controllers';

export type TAuthType = 'local' | 'jwt' | 'jwt-refresh';

export interface IAuthMiddleware<TUser> extends ExpressMiddlewareInterface {
  authType: TAuthType;
  verifyCallback: (req: Request, resolve: any, reject: any) => (err: any, user: TUser, info: object) => void;
  authenticate: (req: Request, res: Response, next: NextFunction) => void;
  use: (req: Request, res: Response, next: NextFunction) => void;
}
in the middleware.interface.ts file, we define the interface of some middleware functions.
TAuthType: type of authentication.
IAuthMiddleware: interface of the authentication middleware.

auth.middleware.ts #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @class JWTAuthMiddleware
* @description Middleware to verify JWT-refresh token
* @implements IAuthMiddleware
*/
@Service()
export class JWTRefreshAuthMiddleware implements IAuthMiddleware<Model<User>> {
  public readonly authType: TAuthType;
  constructor() {
    this.authType = 'jwt-refresh';
  }

  public readonly verifyCallback =
    (req: Request, res: Response, next: NextFunction) => (err: any, user: Model<User>, info: any) => {
      if (err || !user) {
        const error = new HttpError(httpStatus.UNAUTHORIZED, info?.message || err.message);
        next(error);
      }
      req.user = user;
      next();
    };

  public readonly authenticate = (callback: any) => passport.authenticate(this.authType, { session: false }, callback);

  public use(req: Request, res: Response, next: NextFunction): void {
    this.authenticate(this.verifyCallback(req, res, next))(req, res, next);
  }
}
generate auth class for each auth type, and implement the IAuthMiddleware interface.
In line 10, we define the authType property, and in line 16, we throw an error generated by the prepared HttpError class from routing-controllers if the user is not authenticated.


Conclusion #

So far, we’ve been comparing the before and after of middleware logic.

I think it’s great for removing unnecessary utilization, interfaces, middleware, and separating logic.

It was easy to use a variety of open-source middleware that already exists for routing-controllers and also it was clear and simple to create and use custom middleware for other feature the way this library does.


Reference #