diff --git a/integration/hello-world/e2e/middleware-run-match-route.ts b/integration/hello-world/e2e/middleware-run-match-route.ts new file mode 100644 index 00000000000..c3c8ed3cfae --- /dev/null +++ b/integration/hello-world/e2e/middleware-run-match-route.ts @@ -0,0 +1,103 @@ +import { + Controller, + Get, + INestApplication, + Injectable, + MiddlewareConsumer, + NestMiddleware, + Module, +} from '@nestjs/common'; +import { Test } from '../../../packages/testing'; +import * as request from 'supertest'; +import { expect } from 'chai'; + +/** + * Number of times that the middleware was executed. + */ +let triggerCounter = 0; +@Injectable() +class Middleware implements NestMiddleware { + use(req, res, next) { + triggerCounter++; + next(); + } +} + +@Controller() +class TestController { + @Get('/test') + testA() {} + + @Get('/:id') + testB() {} + + @Get('/static/route') + testC() {} + + @Get('/:id/:nested') + testD() {} +} + +@Module({ + controllers: [TestController], +}) +class TestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(Middleware).forRoutes(TestController); + } +} + +describe('Middleware (run on route match)', () => { + let app: INestApplication; + + beforeEach(async () => { + triggerCounter = 0; + app = ( + await Test.createTestingModule({ + imports: [TestModule], + }).compile() + ).createNestApplication(); + + await app.init(); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is equal match`, () => { + return request(app.getHttpServer()) + .get('/test') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is not equal match`, () => { + return request(app.getHttpServer()) + .get('/1') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is not of nested params`, () => { + return request(app.getHttpServer()) + .get('/static/route') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is of nested params`, () => { + return request(app.getHttpServer()) + .get('/1/abc') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/packages/core/middleware/builder.ts b/packages/core/middleware/builder.ts index d9d63f20f92..4aba35d3c46 100644 --- a/packages/core/middleware/builder.ts +++ b/packages/core/middleware/builder.ts @@ -59,7 +59,8 @@ export class MiddlewareBuilder implements MiddlewareConsumer { ): MiddlewareConsumer { const { middlewareCollection } = this.builder; - const forRoutes = this.getRoutesFlatList(routes); + const flattedRoutes = this.getRoutesFlatList(routes); + const forRoutes = this.removeOverlappedRoutes(flattedRoutes); const configuration = { middleware: filterMiddleware( this.middleware, @@ -82,5 +83,29 @@ export class MiddlewareBuilder implements MiddlewareConsumer { .flatten() .toArray(); } + + private removeOverlappedRoutes(routes: RouteInfo[]) { + const regexMatchParams = /(:[^\/]*)/g; + const wildcard = '([^/]*)'; + const routesWithRegex = routes + .filter(route => route.path.includes(':')) + .map(route => ({ + path: route.path, + regex: new RegExp( + '^(' + route.path.replace(regexMatchParams, wildcard) + ')$', + 'g', + ), + })); + return routes.filter(route => { + const isOverlapped = (v: { path: string; regex: RegExp }) => { + return route.path !== v.path && route.path.match(v.regex); + }; + const routeMatch = routesWithRegex.find(isOverlapped); + + if (routeMatch === undefined) { + return route; + } + }); + } }; }