fix(fastify): fix middleware route matching with params#16862
fix(fastify): fix middleware route matching with params#16862pierluigilenoci wants to merge 1 commit intonestjs:masterfrom
Conversation
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 nestjs#11802
Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
Coverage Report for CI Build 4Coverage remained the same at 90.026%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
|
Hi, FastifyAdapter.createMiddlewareFactory now uses prefix matching (end:false) when requestMethod is RequestMethod.ALL, which can cause middleware registered for a specific path/method=ALL to also run on sub-routes. This diverges from ExpressAdapter behavior (RequestMethod.ALL maps to app.all => exact-path match) and can cause middleware to execute on unintended endpoints. Severity: action required | Category: correctness How to fix: Prefix-match only for -1 Agent prompt to fix - you can give this to your LLM of choice:
Found by Qodo. Free code review for open-source maintainers. |
There was a problem hiding this comment.
Pull request overview
This PR fixes NestJS Fastify middleware route matching so that middleware registered with a prefix string (e.g., forRoutes('/my-prefix')) also applies to sub-routes provided by Fastify plugins registered under that prefix (e.g., instance.register(plugin, { prefix: '/my-prefix' })), aligning behavior more closely with Express-style prefix middleware.
Changes:
- Update Fastify adapter middleware route regex generation to optionally use prefix matching (
pathToRegexp(..., { end: false })) depending on the route “method” passed intocreateMiddlewareFactory. - Add an e2e regression test covering Fastify plugin routes registered with a prefix and middleware registered via
forRoutes('/my-prefix').
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/platform-fastify/adapters/fastify-adapter.ts | Adjusts path-to-regexp matching strategy (exact vs prefix) when registering middie middleware. |
| integration/hello-world/e2e/middleware-fastify.spec.ts | Adds an e2e scenario to validate middleware matching for prefixed Fastify plugin routes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -693,10 +705,10 @@ export class FastifyAdapter< | |||
| } | |||
|
|
|||
| try { | |||
| let { regexp: re } = pathToRegexp(normalizedPath); | |||
| re = hasEndOfStringCharacter | |||
| ? new RegExp(re.source + '$', re.flags) | |||
| : re; | |||
| const endMatch = hasEndOfStringCharacter || !isMethodAll; | |||
| const { regexp: re } = pathToRegexp(normalizedPath, { | |||
| end: endMatch, | |||
| }); | |||
| async instance => { | ||
| instance.get('/', async () => MIDDLEWARE_VALUE); | ||
| instance.get('/sub-route', async req => { | ||
| return (req.raw as any).middlewareApplied | ||
| ? MIDDLEWARE_VALUE | ||
| : 'no_middleware'; | ||
| }); | ||
| }, | ||
| { prefix: '/my-prefix' }, | ||
| ); | ||
|
|
||
| await app.init(); | ||
| await app.getHttpAdapter().getInstance().ready(); | ||
| }); | ||
|
|
||
| it(`forRoutes('/my-prefix') should match the prefix root`, () => { | ||
| return app | ||
| .inject({ | ||
| method: 'GET', | ||
| url: '/my-prefix', | ||
| }) | ||
| .then(({ payload }) => expect(payload).to.be.eql(MIDDLEWARE_VALUE)); | ||
| }); |
Summary
Fixes Fastify middleware not matching routes registered via Fastify plugins with a prefix (e.g.,
instance.register(plugin, { prefix: '/my-prefix' })).Root cause: The regex used in
createMiddlewareFactoryalways usedend: true(exact match), which prevented prefix-based middleware from firing on sub-routes. This is inconsistent with Express'sapp.use('/prefix', ...)behavior.Fix: Use the
requestMethodparameter already available increateMiddlewareFactoryto determine matching strategy:ALLor-1(string routes fromforRoutes('/prefix')): useend: false(prefix matching) — mirrors Express'sapp.use()semanticsforRoutes(Controller)orforRoutes({ path, method })): keepend: true(exact matching) — preserves the "execute middleware only once" guaranteeCloses #11802
Changes
packages/platform-fastify/adapters/fastify-adapter.ts: UsepathToRegexp(normalizedPath, { end: endMatch })whereendMatchdepends on whether the request method is ALL (prefix match) or specific (exact match)integration/hello-world/e2e/middleware-fastify.spec.ts: Added test case verifying middleware registered viaforRoutes('/my-prefix')correctly matches sub-routes served by a Fastify plugin with that prefixTest plan
middleware-fastify.spec.tstests passexclude-middleware-fastify.spec.tspasses (9 tests)fastify-middleware-before-init.spec.tspasses (2 tests)fastify-adapter.spec.tspasses (7 tests)uri-versioning-fastify.spec.tspasses (50 tests)platform-fastifyunit tests pass (4 tests)Previous attempt
PR #16841 was closed because it used
end: hasEndOfStringCharacterunconditionally, which setend: falsefor ALL non-wildcard routes (including controller routes). This caused the "execute only once" tests to fail because middleware for/awould incorrectly match/a/b/c.This PR fixes the issue by using the
requestMethodto distinguish prefix routes (method ALL → prefix match) from controller routes (specific method → exact match).