Skip to content

Commit 0894ee4

Browse files
fix(fastify): fix middleware route matching with params
When middleware is registered via forRoutes('/prefix') (method ALL), use prefix matching (end: false) for the path-to-regexp regex to allow sub-routes under the given path to trigger the middleware. This mirrors Express's app.use() behavior for prefix-based middleware. For specific HTTP methods (from forRoutes(Controller) or explicit route info objects), keep exact matching (end: true) to maintain the "execute only once" guarantee when multiple routes are registered. Closes #11802 Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
1 parent 21a2d0a commit 0894ee4

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

integration/hello-world/e2e/middleware-fastify.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,4 +793,78 @@ describe('Middleware (FastifyAdapter)', () => {
793793
});
794794
});
795795
});
796+
797+
describe('should apply middleware to routes registered via Fastify plugin with prefix', () => {
798+
const MIDDLEWARE_VALUE = 'middleware_applied';
799+
800+
@Controller()
801+
class PluginPrefixController {
802+
@Get('other')
803+
other() {
804+
return 'other_route';
805+
}
806+
}
807+
808+
@Module({
809+
controllers: [PluginPrefixController],
810+
})
811+
class PluginPrefixModule implements NestModule {
812+
configure(consumer: MiddlewareConsumer) {
813+
consumer
814+
.apply((req, res, next) => {
815+
req.middlewareApplied = true;
816+
next();
817+
})
818+
.forRoutes('/my-prefix');
819+
}
820+
}
821+
822+
beforeEach(async () => {
823+
const fastifyAdapter = new FastifyAdapter();
824+
825+
app = (
826+
await Test.createTestingModule({
827+
imports: [PluginPrefixModule],
828+
}).compile()
829+
).createNestApplication<NestFastifyApplication>(fastifyAdapter);
830+
831+
// Register a Fastify plugin with a prefix (simulating third-party plugin)
832+
fastifyAdapter.getInstance().register(
833+
async instance => {
834+
instance.get('/', async () => MIDDLEWARE_VALUE);
835+
instance.get('/sub-route', async req => {
836+
return (req.raw as any).middlewareApplied
837+
? MIDDLEWARE_VALUE
838+
: 'no_middleware';
839+
});
840+
},
841+
{ prefix: '/my-prefix' },
842+
);
843+
844+
await app.init();
845+
await app.getHttpAdapter().getInstance().ready();
846+
});
847+
848+
it(`forRoutes('/my-prefix') should match the prefix root`, () => {
849+
return app
850+
.inject({
851+
method: 'GET',
852+
url: '/my-prefix',
853+
})
854+
.then(({ payload }) => expect(payload).to.be.eql(MIDDLEWARE_VALUE));
855+
});
856+
857+
it(`forRoutes('/my-prefix') should match sub-routes under the prefix`, () => {
858+
return app
859+
.inject({
860+
method: 'GET',
861+
url: '/my-prefix/sub-route',
862+
})
863+
.then(({ payload }) => expect(payload).to.be.eql(MIDDLEWARE_VALUE));
864+
});
865+
866+
afterEach(async () => {
867+
await app.close();
868+
});
869+
});
796870
});

packages/platform-fastify/adapters/fastify-adapter.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,18 @@ export class FastifyAdapter<
675675
if (!this.isMiddieRegistered) {
676676
await this.registerMiddie();
677677
}
678+
679+
// When the request method is "ALL" (or -1 for string routes from
680+
// forRoutes('/prefix')), use prefix matching (end: false) to match
681+
// sub-routes under the given path — this mirrors Express's app.use()
682+
// behavior for prefix-based middleware. For specific HTTP methods
683+
// (from forRoutes(Controller) or forRoutes({ path, method })), use
684+
// exact matching (end: true) to prevent middleware from firing on
685+
// sub-routes when multiple routes are registered for the same
686+
// middleware (the "execute only once" guarantee).
687+
const isMethodAll =
688+
requestMethod === RequestMethod.ALL || (requestMethod as number) === -1;
689+
678690
return (path: string, callback: Function) => {
679691
const hasEndOfStringCharacter = path.endsWith('$');
680692
path = hasEndOfStringCharacter ? path.slice(0, -1) : path;
@@ -693,10 +705,10 @@ export class FastifyAdapter<
693705
}
694706

695707
try {
696-
let { regexp: re } = pathToRegexp(normalizedPath);
697-
re = hasEndOfStringCharacter
698-
? new RegExp(re.source + '$', re.flags)
699-
: re;
708+
const endMatch = hasEndOfStringCharacter || !isMethodAll;
709+
const { regexp: re } = pathToRegexp(normalizedPath, {
710+
end: endMatch,
711+
});
700712

701713
// The following type assertion is valid as we use import('@fastify/middie') rather than require('@fastify/middie')
702714
// ref https://github.com/fastify/middie/pull/55

0 commit comments

Comments
 (0)