Skip to content

🔥 feat: add SkipUnmatchedRoutes config option#4411

Open
muzzii255 wants to merge 5 commits into
gofiber:mainfrom
muzzii255:feature/skip-unmatched-routes
Open

🔥 feat: add SkipUnmatchedRoutes config option#4411
muzzii255 wants to merge 5 commits into
gofiber:mainfrom
muzzii255:feature/skip-unmatched-routes

Conversation

@muzzii255

Copy link
Copy Markdown

Description

Adds a SkipUnmatchedRoutes config option that short-circuits requests to unregistered paths — returning 404 immediately without running through the middleware chain. Useful for cutting unnecessary processing from bots, scanners, and bad URLs.

Fixes # (issue)
#4403

Changes introduced

SkipUnmatchedRoutes on app.Config, adds extra lookup costing 150-200ns on matched routes but saving same on unmatched routes

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
goos: linux
goarch: amd64
pkg: github.com/gofiber/fiber/v3
cpu: AMD Ryzen 7 9700X 8-Core Processor             
Benchmark_SkipUnmatchedRoutes_Unmatched/without_skip-16         	 4276250	       280.2 ns/op	       8 B/op	       1 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/without_skip-16         	 4258561	       281.4 ns/op	       8 B/op	       1 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/without_skip-16         	 4216581	       284.7 ns/op	       8 B/op	       1 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/without_skip-16         	 4141472	       281.5 ns/op	       8 B/op	       1 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/without_skip-16         	 4173716	       283.6 ns/op	       8 B/op	       1 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/with_skip-16            	20101112	        59.06 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/with_skip-16            	21355640	        56.42 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/with_skip-16            	19912690	        59.81 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/with_skip-16            	21089865	        56.41 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Unmatched/with_skip-16            	21032264	        56.59 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/without_skip-16           	 4840177	       246.3 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/without_skip-16           	 4927646	       243.0 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/without_skip-16           	 4730292	       255.3 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/without_skip-16           	 4752334	       251.9 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/without_skip-16           	 4898761	       242.6 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/with_skip-16              	 2848100	       422.5 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/with_skip-16              	 2911087	       413.6 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/with_skip-16              	 2911100	       406.0 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/with_skip-16              	 2843078	       416.9 ns/op	       0 B/op	       0 allocs/op
Benchmark_SkipUnmatchedRoutes_Matched/with_skip-16              	 2911432	       415.4 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/gofiber/fiber/v3	23.896s
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

@muzzii255 muzzii255 requested a review from a team as a code owner June 7, 2026 20:59
@welcome

welcome Bot commented Jun 7, 2026

Copy link
Copy Markdown

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2b751602-6e20-4f3a-96ec-e82de148475a

📥 Commits

Reviewing files that changed from the base of the PR and between ad3e6da and acc6187.

📒 Files selected for processing (1)
  • docs/api/fiber.md
✅ Files skipped from review due to trivial changes (1)
  • docs/api/fiber.md

Walkthrough

Adds SkipUnmatchedRoutes to fiber.Config; implements App.routeExists and integrates an early-404 short-circuit into both default and custom request handlers when enabled; updates docs and adds tests plus two benchmarks.

Changes

SkipUnmatchedRoutes Feature

Layer / File(s) Summary
Config field declaration
app.go, docs/api/fiber.md
Adds SkipUnmatchedRoutes bool to fiber.Config with JSON tag and documentation entry describing immediate 404 for unmatched paths when enabled.
Route detection helper and handler short-circuit
router.go
Adds internal App.routeExists and integrates early 404 short-circuit checks into defaultRequestHandler and customRequestHandler when SkipUnmatchedRoutes is true and no endpoint route matches.
Tests and benchmarks
router_test.go
Adds Test_App_SkipUnmatchedRoutes validating middleware skipping and matching behavior (including CaseSensitive/StrictRouting), plus two benchmarks comparing matched/unmatched handler costs with and without the option.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Handler as defaultRequestHandler/customRequestHandler
  participant Config as app.config.SkipUnmatchedRoutes
  participant RouteChecker as App.routeExists
  participant Middleware as middleware chain
  participant Endpoint as matched endpoint route

  Client->>Handler: incoming HTTP request
  Handler->>Config: read SkipUnmatchedRoutes
  alt SkipUnmatchedRoutes == true
    Handler->>RouteChecker: routeExists(method, path)
    alt routeExists == false
      Handler->>Handler: return 404 StatusNotFound
    else routeExists == true
      Handler->>Middleware: enter middleware chain
      Middleware->>Endpoint: execute matched endpoint
    end
  else SkipUnmatchedRoutes == false
    Handler->>Middleware: enter middleware chain
    Middleware->>Endpoint: scan & execute matched endpoint
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • gofiber/fiber#4233: Prior modifications to request handler selection and invocation paths used by this short-circuit integration.
  • gofiber/fiber#3261: Earlier refactor of default/custom request handlers that this change builds upon.
  • gofiber/fiber#3817: Related routing behavior changes affecting endpoint detection and 404 outcomes.

Possibly related issues

Suggested labels

⚡️ Performance

Suggested reviewers

  • sixcolors
  • efectn
  • gaby

Poem

🐰 I hopped through routes with whiskers keen,
I checked each path for places unseen.
When none replied, I paused, then fled—
A tiny 404, then off to my bed.
🥕 Hop, code, hop — the router's clean.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the purpose and includes completed benchmarks with detailed performance metrics. However, several required sections remain unchecked: Documentation Update, Changelog/What's New, Migration Guide, API Alignment with Express, API Longevity, and Examples. Complete the remaining checklist items, particularly documenting the feature examples and confirming API alignment with Express.js conventions and longevity considerations.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature being added: a new SkipUnmatchedRoutes config option. It accurately reflects the primary change across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ReneWerner87 ReneWerner87 added this to v3 Jun 7, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jun 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
router_test.go (1)

2570-2665: ⚡ Quick win

Add a custom-context variant for SkipUnmatchedRoutes to cover both handler paths.

These tests validate the default handler path well, but they don’t explicitly assert behavior through newCustomApp()/custom request handling. Adding one matched + one unmatched subtest there would guard against regressions in customRequestHandler too.

Based on learnings from provided context: SkipUnmatchedRoutes short-circuit logic is present in both default and custom handlers (router.go:449-502).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router_test.go` around lines 2570 - 2665, Add companion subtests to
Test_App_SkipUnmatchedRoutes that exercise the custom-context path by creating
an app via newCustomApp() (or the equivalent factory used in custom request
handling) and invoking its Test helper with both a matched route ("/users") and
an unmatched route ("/notfound" or case/strict variants) while
SkipUnmatchedRoutes is true; assert middlewareCalled semantics match the
existing default-app cases so the short-circuit logic in customRequestHandler
(and related code handling SkipUnmatchedRoutes) is covered and won’t regress.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@router_test.go`:
- Around line 2570-2665: Add companion subtests to Test_App_SkipUnmatchedRoutes
that exercise the custom-context path by creating an app via newCustomApp() (or
the equivalent factory used in custom request handling) and invoking its Test
helper with both a matched route ("/users") and an unmatched route ("/notfound"
or case/strict variants) while SkipUnmatchedRoutes is true; assert
middlewareCalled semantics match the existing default-app cases so the
short-circuit logic in customRequestHandler (and related code handling
SkipUnmatchedRoutes) is covered and won’t regress.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f733da5b-bbe5-4e94-bbd5-fc8bf48a7b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 5c45bce and e94a5b9.

📒 Files selected for processing (3)
  • app.go
  • router.go
  • router_test.go

@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.34%. Comparing base (5fe6c52) to head (666d5ca).
⚠️ Report is 37 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4411   +/-   ##
=======================================
  Coverage   91.33%   91.34%           
=======================================
  Files         132      133    +1     
  Lines       13193    13250   +57     
=======================================
+ Hits        12050    12103   +53     
- Misses        724      727    +3     
- Partials      419      420    +1     
Flag Coverage Δ
unittests 91.34% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ReneWerner87

Copy link
Copy Markdown
Member

@muzzii255 pls check linter errors

@ReneWerner87

Copy link
Copy Markdown
Member

@muzzii255 pls add something in the markdown docs for this feature

@muzzii255

Copy link
Copy Markdown
Author

@ReneWerner87 done

@ReneWerner87

Copy link
Copy Markdown
Member

@gaby pls check

@gaby gaby changed the title 🔥 Feature: add SkipUnmatchedRoutes config option 🔥 feat: add SkipUnmatchedRoutes config option Jun 8, 2026

@gaby gaby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we need to do the changes in next(), else we are checking the whole router twice for every requests. That defeats the purpose of this feature.

Comment thread router.go
}

// routeExists checks if a non-middleware route matches the given method and path.
func (app *App) routeExists(methodInt, treeHash int, detectionPath, path string) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't need this function, it is going through the whole tree for every request.

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.

yes, but i does not trigger the middlewares, its an extra check to save db and cache queries, on unmatched routes it saves the 1/alloc and faster, but adds same amount if on matched routes, its an opt in feature tho

Comment thread router.go
}

// routeExists checks if a non-middleware route matches the given method and path.
func (app *App) routeExists(methodInt, treeHash int, detectionPath, path string) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The way this is implemented, when SkipUnmatchedRoutes is enabled we are calling route.match twice. Here and in next() for every single request.

@muzzii255 muzzii255 Jun 8, 2026

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.

The tree walk before middleware is the whole point of SkipUnmatchedRoutes — without it, any middleware registered via Group() fires on requests that never match a route, which is exactly the behavior this flag is meant to prevent. Moving the logic into route.match doesn't change that: by the time next() gets to the per-route comparison, the group middleware has already run.

i tried caching this extra tree walk, but the params paths still creates the overhead and saves like 5-10ns on total benchmark not worth it.

@gaby

gaby commented Jun 8, 2026

Copy link
Copy Markdown
Member

@gaby pls check

When SkipUnmatchedRoutes the router matching logic runs twice for every request. This is not ideal.

@muzzii255

Copy link
Copy Markdown
Author

@gaby @ReneWerner87 i tried multiple approach like defering the middlewares when SkipUnmatchedRoutes to prevent extra lookup or using the routeExists to match the route and send the route to next() which will run the middlewares, none of them worked really well. the only thing left is to rewrite the tree which i am not supporting as it might create more bugs in the future. since its an optional feature, user will know the cost of using it.

@muzzii255

Copy link
Copy Markdown
Author

any approach will add an extra lookup on the tree except rewriting the tree, right now i am using a middleware from the PR #4406 on my codebase, it will be easier for me to just use the fiber's internal tree.

@gaby

gaby commented Jun 10, 2026

Copy link
Copy Markdown
Member

@muzzii255 I think in next() we can check. IsMatched() and if false call c.SendString(not found)

@muzzii255

Copy link
Copy Markdown
Author

@muzzii255 I think in next() we can check. IsMatched() and if false call c.SendString(not found)

let me test it

@muzzii255

Copy link
Copy Markdown
Author

@muzzii255 I think in next() we can check. IsMatched() and if false call c.SendString(not found)

in next() function this loop executes the middlewares before isMatched() is called, i can try defering the middleware execute by appending the handler to a slice, iterate and execute the middleware after if c.isMatched check

	// Loop over the route stack starting from previous index
	for indexRoute < lenr {
		// Increment route index
		indexRoute++

		// Get *Route
		route := tree[indexRoute]

		if route.mount {
			continue
		}

		// Check if it matches the request path
		if !route.match(detectionPath, path, &c.values) {
			continue
		}

		if c.shouldSkipNonUseRoutes && !route.use {
			continue
		}

		// Pass route reference and param values
		c.route = route
		// Non use handler matched
		if !route.use {
			c.isMatched = true
		}
		// Execute first handler of route
		if len(route.Handlers) > 0 {
			c.indexHandler = 0
			c.indexRoute = indexRoute
			return true, route.Handlers[0](c)
		}

		return true, nil // Stop scanning the stack
	}
	
	// If c.Next() does not match, return 404
	// If no match, scan stack again if other methods match the request
	// Moved from app.handler because middleware may break the route chain
	if c.isMatched {
		return false, ErrNotFound
	}

@muzzii255

Copy link
Copy Markdown
Author

@muzzii255 I think in next() we can check. IsMatched() and if false call c.SendString(not found)

@gaby Do you mean inside next() do the endpoint scan first (before the main loop runs any middleware), and return ErrNotFound if nothing matches? Because checking IsMatched after the loop is too late — middleware has already executed by then, which defeats the feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants