Skip to content

Commit

Permalink
Merge pull request #9809 from rychardvale/fix-middleware-multiple-runs
Browse files Browse the repository at this point in the history
feat(core): middleware runs once for matching route
  • Loading branch information
kamilmysliwiec committed Jun 12, 2023
2 parents b1be7a3 + b0837af commit 8053f11
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 1 deletion.
103 changes: 103 additions & 0 deletions 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();
});
});
27 changes: 26 additions & 1 deletion packages/core/middleware/builder.ts
Expand Up @@ -69,7 +69,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,
Expand All @@ -92,5 +93,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;
}
});
}
};
}

0 comments on commit 8053f11

Please sign in to comment.