Skip to content

fix(fastify): anchor middleware path regex for non-prefix routes#16862

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

fix(fastify): anchor middleware path regex for non-prefix routes#16862
pierluigilenoci wants to merge 1 commit into
nestjs:masterfrom
pierluigilenoci:fix/fastify-middleware-route-matching

Conversation

@pierluigilenoci
Copy link
Copy Markdown

@pierluigilenoci pierluigilenoci commented Apr 29, 2026

Summary

When middleware is registered with forRoutes({ path, method }) (any RequestMethod including ALL), pathToRegexp now uses end: true (exact match) so that middleware on /a does not accidentally fire on /a/b.

Only string routes from forRoutes('/prefix') — internally represented as method -1 — use prefix matching (end: false) to mirror Express's app.use() semantics for prefix-based middleware.

Changes

  • packages/platform-fastify/adapters/fastify-adapter.ts: Introduce isStringRoute check (requestMethod === -1) to select between prefix matching (end: false) and exact matching (end: true) when building the middleware path regex.
  • integration/hello-world/e2e/middleware-fastify.spec.ts: Add two new e2e test blocks — one verifying that prefix-based middleware (forRoutes('/my-prefix')) correctly matches sub-routes via Fastify plugins, and another verifying that RequestMethod.ALL uses exact matching and does NOT match sub-paths.

Status: Draft — CI iterating (rebase + squash). Not yet ready for review.

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 29, 2026

Coverage Report for CI Build 420

Coverage remained the same at 90.028%

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: 8624
Covered Lines: 7764
Line Coverage: 90.03%
Relevant Branches: 2943
Covered Branches: 2386
Branch Coverage: 81.07%
Branches in Coverage %: No
Coverage Strength: 56.72 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,
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in b19ccea. Renamed isMethodAllisStringRoute and tightened the predicate to only match (requestMethod as number) === -1. RequestMethod.ALL now uses exact matching (end: true); only string-route forRoutes('/prefix') (which uses the -1 sentinel) keeps prefix matching.

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));
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in b19ccea. The handler now sets req.raw.middlewareApplied = true and the assertion checks that flag, so the test actually verifies the middleware ran. Also added a new test covering that forRoutes({ path: '/a', method: RequestMethod.ALL }) does NOT match /a/b — confirming exact-match semantics for ALL.

@pierluigilenoci
Copy link
Copy Markdown
Author

Hi — friendly follow-up. CI is green and all checks pass. Would you be able to review when you get a chance? Thank you!

When middleware is registered with forRoutes({ path, method }) (any
RequestMethod including ALL), pathToRegexp now uses end: true (exact
match) so that middleware on '/a' does not accidentally fire on '/a/b'.

Only string routes from forRoutes('/prefix') — internally represented
as method -1 — use prefix matching (end: false) to mirror Express's
app.use() semantics for prefix-based middleware.

Signed-off-by: Pierluigi Lenoci <pierluigi.lenoci@gmail.com>
Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
@pierluigilenoci pierluigilenoci force-pushed the fix/fastify-middleware-route-matching branch from b19ccea to 58d0e60 Compare June 1, 2026 17:12
@pierluigilenoci pierluigilenoci changed the title fix(fastify): fix middleware route matching with params fix(fastify): anchor middleware path regex for non-prefix routes Jun 1, 2026
@kamilmysliwiec
Copy link
Copy Markdown
Member

im still investigating if this doesnt cause any unexpected regression that might not have been caught by our tests

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.

5 participants