Skip to content

fix(fastify): fix middleware route matching with params#16862

Open
pierluigilenoci wants to merge 1 commit intonestjs:masterfrom
pierluigilenoci:fix/fastify-middleware-route-matching
Open

fix(fastify): fix middleware route matching with params#16862
pierluigilenoci wants to merge 1 commit intonestjs:masterfrom
pierluigilenoci:fix/fastify-middleware-route-matching

Conversation

@pierluigilenoci
Copy link
Copy Markdown

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 createMiddlewareFactory always used end: true (exact match), which prevented prefix-based middleware from firing on sub-routes. This is inconsistent with Express's app.use('/prefix', ...) behavior.

Fix: Use the requestMethod parameter already available in createMiddlewareFactory to determine matching strategy:

  • When method is ALL or -1 (string routes from forRoutes('/prefix')): use end: false (prefix matching) — mirrors Express's app.use() semantics
  • When method is a specific HTTP verb (from forRoutes(Controller) or forRoutes({ path, method })): keep end: true (exact matching) — preserves the "execute middleware only once" guarantee

Closes #11802

Changes

  • packages/platform-fastify/adapters/fastify-adapter.ts: Use pathToRegexp(normalizedPath, { end: endMatch }) where endMatch depends 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 via forRoutes('/my-prefix') correctly matches sub-routes served by a Fastify plugin with that prefix

Test plan

  • All 30 existing middleware-fastify.spec.ts tests pass
  • "Execute middleware only once" tests pass (the issue that broke PR fix(platform-fastify): fix middleware route matching for parameterized paths #16841)
  • exclude-middleware-fastify.spec.ts passes (9 tests)
  • fastify-middleware-before-init.spec.ts passes (2 tests)
  • fastify-adapter.spec.ts passes (7 tests)
  • uri-versioning-fastify.spec.ts passes (50 tests)
  • platform-fastify unit tests pass (4 tests)
  • New test verifies prefix root and sub-route matching

Previous attempt

PR #16841 was closed because it used end: hasEndOfStringCharacter unconditionally, which set end: false for ALL non-wildcard routes (including controller routes). This caused the "execute only once" tests to fail because middleware for /a would incorrectly match /a/b/c.

This PR fixes the issue by using the requestMethod to distinguish prefix routes (method ALL → prefix match) from controller routes (specific method → exact match).

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>
@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 4

Coverage remained the same at 90.026%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 8522
Covered Lines: 7672
Line Coverage: 90.03%
Relevant Branches: 2881
Covered Branches: 2338
Branch Coverage: 81.15%
Branches in Coverage %: No
Coverage Strength: 56.42 hits per line

💛 - Coveralls

@Qodo-Free-For-OSS
Copy link
Copy Markdown

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:

Issue description

FastifyAdapter.createMiddlewareFactory() treats RequestMethod.ALL as prefix-matching (end: false), which changes semantics vs Express (app.all) and can apply middleware to sub-routes unexpectedly.

Issue Context

String routes from consumer.forRoutes('/prefix') are represented internally with method: -1 (RoutesMapper). That sentinel can be used to enable prefix behavior without changing RequestMethod.ALL.

Fix Focus Areas

  • packages/platform-fastify/adapters/fastify-adapter.ts[679-711]
  • packages/core/middleware/routes-mapper.ts[51-58]
  • packages/platform-express/adapters/express-adapter.ts[254-269]

What to change

  • Compute prefix-matching based only on (requestMethod as number) === -1.
  • Keep RequestMethod.ALL using end: true (exact match), unless there is an explicit compatibility decision to make Fastify differ from Express.
  • Adjust the inline comment accordingly and (optionally) add a regression test for forRoutes({ path: '/x', method: RequestMethod.ALL }) to ensure it does not match /x/sub.

Found by Qodo. Free code review for open-source maintainers.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into createMiddlewareFactory.
  • 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.

Comment on lines 687 to +711
@@ -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,
});
Comment on lines +833 to +855
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));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fastify middleware doesn't apply correctly when matched against routes registered via a Fastify plugin.

4 participants