Repository: nestjs/nest
Pull request #10801 (2023-01-03 15:51:44)

fix(core): fix global prefix not working
PR https://github.com/nestjs/nest/pull/9332 fixes https://github.com/nestjs/nest/issues/9124, but will introduce a bug - the global prefix will not work if middleware register in `'*'` route. For example, run the code below and visit `http://localhost:3000/test` should not get `{hello: 'hi'}` response, but actually will get. This means `setGlobalPrefix` not work.

```ts
import { NestFactory } from '@nestjs/core';
import { Injectable, MiddlewareConsumer, Module, NestMiddleware, NestModule } from "@nestjs/common";
import type { Request, Response, NextFunction } from 'express';

@Injectable()
export class EngineMiddleware implements NestMiddleware {
    use(_req: Request, res: Response, _next: NextFunction) {
        res.json({hello: 'hi'})
    }
}

@Module({})
export class AppModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(EngineMiddleware)
          .forRoutes('*');
      }
}

async function bootstrap(): Promise<void> {
    const app = await NestFactory.create(AppModule);
    app.setGlobalPrefix('/gp')
    app.listen(3000);
}

bootstrap()
```

This PR reverts https://github.com/nestjs/nest/pull/9332 and fixes https://github.com/nestjs/nest/issues/9124 and https://github.com/nestjs/nest/issues/9776.

## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
- [x] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: https://github.com/nestjs/nest/issues/9124 and https://github.com/nestjs/nest/issues/9776


## What is the new behavior?
Fix them.

## Does this PR introduce a breaking change?
- [ ] Yes
- [x] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information

ZanMinKian: BTW, people who face the same problem just like the code above, have to change `forRoutes('*')` into `forRoutes('*\')`, which is workaround.

Patch:
diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts
index 777f58964..7dfd68631 100644
--- a/packages/core/middleware/middleware-module.ts
+++ b/packages/core/middleware/middleware-module.ts
@@ -275,10 +275,10 @@ export class MiddlewareModule {
   ) {
     const prefix = this.config.getGlobalPrefix();
     const excludedRoutes = this.config.getGlobalPrefixOptions().exclude;
+    const useExcluded = ['*', '/*', '(.*)', '/(.*)'].includes(path);
     if (
-      (Array.isArray(excludedRoutes) &&
-        isRouteExcluded(excludedRoutes, path, method)) ||
-      ['*', '/*', '(.*)', '/(.*)'].includes(path)
+      Array.isArray(excludedRoutes) &&
+      isRouteExcluded(excludedRoutes, path, method)
     ) {
       path = addLeadingSlash(path);
     } else {
@@ -302,20 +302,23 @@ export class MiddlewareModule {
     const isMethodAll = isRequestMethodAll(method);
     const requestMethod = RequestMethod[method];
     const router = await applicationRef.createMiddlewareFactory(method);
-    router(
-      path,
-      isMethodAll
-        ? proxy
-        : <TRequest, TResponse>(
-            req: TRequest,
-            res: TResponse,
-            next: () => void,
-          ) => {
-            if (applicationRef.getRequestMethod(req) === requestMethod) {
-              return proxy(req, res, next);
-            }
-            return next();
-          },
-    );
+    const mw = isMethodAll
+      ? proxy
+      : <TRequest, TResponse>(
+          req: TRequest,
+          res: TResponse,
+          next: () => void,
+        ) => {
+          if (applicationRef.getRequestMethod(req) === requestMethod) {
+            return proxy(req, res, next);
+          }
+          return next();
+        };
+    if (useExcluded && Array.isArray(excludedRoutes)) {
+      excludedRoutes.forEach(route => {
+        router(route.path, mw);
+      });
+    }
+    router(path, mw);
   }
 }
diff --git a/packages/core/middleware/utils.ts b/packages/core/middleware/utils.ts
index 19418afc8..d0aad4849 100644
--- a/packages/core/middleware/utils.ts
+++ b/packages/core/middleware/utils.ts
@@ -13,6 +13,7 @@ export const mapToExcludeRoute = (
   return routes.map(({ path, method }) => ({
     pathRegex: pathToRegexp(path),
     requestMethod: method,
+    path,
   }));
 };
 
diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts
index a06ee4c98..75ebe2492 100644
--- a/packages/core/nest-application.ts
+++ b/packages/core/nest-application.ts
@@ -342,11 +342,13 @@ export class NestApplication
             return {
               requestMethod: RequestMethod.ALL,
               pathRegex: pathToRegexp(addLeadingSlash(route)),
+              path: route,
             };
           }
           return {
             requestMethod: route.method,
             pathRegex: pathToRegexp(addLeadingSlash(route.path)),
+            path: route.path,
           };
         },
       );
diff --git a/packages/core/router/interfaces/exclude-route-metadata.interface.ts b/packages/core/router/interfaces/exclude-route-metadata.interface.ts
index 1c143512f..dfa143041 100644
--- a/packages/core/router/interfaces/exclude-route-metadata.interface.ts
+++ b/packages/core/router/interfaces/exclude-route-metadata.interface.ts
@@ -1,6 +1,7 @@
 import { RequestMethod } from '@nestjs/common';
 
 export interface ExcludeRouteMetadata {
+  path: string;
   /**
    * Regular expression representing the route path.
    */


Test patch:
diff --git a/packages/core/test/application-config.spec.ts b/packages/core/test/application-config.spec.ts
index 9418b13db..9bf573bb9 100644
--- a/packages/core/test/application-config.spec.ts
+++ b/packages/core/test/application-config.spec.ts
@@ -23,7 +23,11 @@ describe('ApplicationConfig', () => {
     it('should set global path options', () => {
       const options: GlobalPrefixOptions<ExcludeRouteMetadata> = {
         exclude: [
-          { pathRegex: new RegExp(/health/), requestMethod: RequestMethod.GET },
+          {
+            pathRegex: new RegExp(/health/),
+            requestMethod: RequestMethod.GET,
+            path: 'health',
+          },
         ],
       };
       appConfig.setGlobalPrefixOptions(options);
diff --git a/packages/core/test/router/route-path-factory.spec.ts b/packages/core/test/router/route-path-factory.spec.ts
index 84b9275a7..4bf952295 100644
--- a/packages/core/test/router/route-path-factory.spec.ts
+++ b/packages/core/test/router/route-path-factory.spec.ts
@@ -249,6 +249,7 @@ describe('RoutePathFactory', () => {
               {
                 pathRegex: pathToRegexp('/random'),
                 requestMethod: RequestMethod.ALL,
+                path: '/random',
               },
             ],
           });
@@ -267,6 +268,7 @@ describe('RoutePathFactory', () => {
               {
                 pathRegex: pathToRegexp('/cats'),
                 requestMethod: RequestMethod.ALL,
+                path: '/cats',
               },
             ],
           });
@@ -285,6 +287,7 @@ describe('RoutePathFactory', () => {
                 {
                   pathRegex: pathToRegexp('/cats'),
                   requestMethod: RequestMethod.GET,
+                  path: '/cats',
                 },
               ],
             });