Open-source project
delvedor/find-my-way avatar
delvedor/find-my-way

find-my-way refuses catastrophic regexes before they reach your routes

A crazy fast HTTP router

1,636 stars159 forksJavaScriptMIT

At a glance

What is it?
find-my-way is an MIT licensed radix tree HTTP router for Node 20 and later, with only three runtime dependencies, and it throws at registration time on a regex route that could blow up exponentially unless you pass allowUnsafeRegex. It suits people building a framework or a server where routing throughput is measured, and it is too low level for an application with a handful of endpoints.
Who is it for?
Adopt find-my-way if you are writing a server, a framework or an internal service in Node 20 or later and need matching throughput and constraints plus versioned routes, since the same router backs Fastify and Restify and the package carries only fast-deep-equal, fast-querystring and safe-regex2 at runtime.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • delvedor/find-my-way1,636 ★ · +5
Star history
Repository2026-10-162026-11-07
delvedor/find-my-way1.6k1.6k

5 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A radix tree with three runtime dependencies and a Node 20 floor

find-my-way describes itself as a crazy fast HTTP router built internally on a Radix Tree, also known as a compact Prefix Tree, supporting route params, wildcards, and framework independent operation. That is the whole idea: paths are stored in a prefix tree rather than compared against an array of patterns, so shared prefixes are matched once.

The dependency list is the clearest signal of how small this is. There are three runtime dependencies: `fast-deep-equal`, `fast-querystring` and `safe-regex2`. Nothing else. A routing layer that sits under every request in a production server is exactly the kind of place where transitive dependencies matter, and this one keeps almost none.

The engine floor is `>=20`, which is worth checking before you add it to a service still on Node 18. The package is CommonJS, with `index.js` as `main` and `index.d.ts` as `types`, and the repository root is correspondingly small: `index.js`, `lib/`, `index.d.ts`, `example.js`, `test/`, `benchmark/`, an ESLint flat config and a `LICENSE`. Tests are lint, a `borp` runner and `tstyche` for the TypeScript definitions.

The adoption evidence in the README is specific rather than a benchmark table: The README points to Fastify and Restify as real-world examples and links to a separate `router-benchmark` repository for comparisons with commonly used routers.

Version cadence is steady and frequent. v9.8.0 landed on 2026-08-14, v9.9.0 on 2026-08-21 and v9.10.0 on 2026-10-10, the same day as the last push.

Registering a route is where the safety checks live

The basic usage is deliberately minimal. You create a router, register routes with `on`, and mount a `lookup` on an http server.

js
const http = require('http')
const router = require('find-my-way')()

router.on('GET', '/', (req, res, params) => {
  res.end('{"message":"hello world"}')
})

const server = http.createServer((req, res) => {
  router.lookup(req, res)
})

The handler signature carries `params`, and the API surface for a matched route extends to `store` and `searchParams`. Registration accepts a single method or an array of methods, an options object, a handler and an optional store, which is how versioned routes and per-route metadata are attached.

The part worth attention is that this is where a bad regex is caught. If you register a regex based route and the router detects a potentially catastrophic exponential-time regular expression, it throws. It uses `safe-regex2` internally for that detection. If you genuinely need such a pattern, `allowUnsafeRegex` disables the check:

js
const router = require('find-my-way')({
  allowUnsafeRegex: true
})

That default is a decision worth taking seriously. A catastrophic regex in a route table is a denial of service reachable by an unauthenticated request, and a router that refuses to register it at startup is doing something most routers do not.

Runtime removal and introspection are first class too: `off()` takes methods and a path with optional constraints, `findRoute()` and `hasRoute()` check existence, `find()` performs the lookup, `routes` exposes the registered table, `prettyPrint()` renders the tree with options for common prefix collapsing and metadata inclusion, and `reset()` clears everything. `sanitizeUrlPath(url, [useSemicolonDelimiter])` and the helpers `removeDuplicateSlashes(path)` and `trimLastSlash(path)` are exported as standalone functions for use before routing.

URL normalisation options each change what a path resolves to

The constructor options are where the real semantics live, and each one changes route resolution rather than just cleaning up input.

`defaultRoute` sets the fallback handler. Without it, nothing is invoked for an unmatched path, so you supply one:

js
const router = require('find-my-way')({
  defaultRoute: (req, res) => {
    res.statusCode = 404
    res.end()
  }
})

`onBadUrl` changes what happens with a badly formatted URL such as `/hello/%world`. By default that invokes the `defaultRoute`, which means a malformed URL produces a 404. Setting `onBadUrl` lets you return a 400 instead, which is usually what you want and is a real behavioural difference between two installations of the same router.

`ignoreTrailingSlash` makes `/foo/` and `/foo` map to the same handler, registered once as `/foo/`. `ignoreDuplicateSlashes` does the same for `/foo`, `//foo`, `///foo` and so on. The README specifies the interaction between them: when both are true, duplicate slashes are removed first and trailing slashes second, so `//a//b//c//` converts to `/a/b/c`.

`maxParamLength` sets the length limit for parameters in parametric routes, standard, regex and multi, defaulting to 100 characters. Exceeding it invokes the default route unless you supply `onMaxParamLength`, which is where you would return a 414.

js
const router = require('find-my-way')({
  maxParamLength: 500,
  onMaxParamLength: function (path, req, res) {
    res.statusCode = 414
    res.end('URI Too Long')
  }
})

Note the condition on the custom handler: it fires only if no other route, a wildcard for example, matches. And `caseSensitive` defaults to true per RFC3986; setting it to false matches all paths as lowercase while route parameters and wildcards keep their original casing.

Query string parsing is swappable through `querystringParser`, defaulting to `fast-querystring` and replaceable with something like `qs`, and `useSemicolonDelimiter` restores older behaviour where `;` separates path from query string.

Match order is documented as an API rule, not an implementation detail

Because a radix tree has a fixed traversal order, matching is not arbitrary, and the README documents match order and supported path formats as part of the public API alongside `on()`, `off()` and the lookup family.

The consequences follow from the path formats. Static segments, parametric segments, wildcard segments, multi parameters, regex based routes and versioned routes all live in the same tree, so registration order and specificity interact. The README's match order section is where you learn the precedence rules, and reading it before registering fifty routes is cheaper than debugging a request that resolves to the wrong handler.

Constraints are the other axis. They let you register multiple handlers for the same path distinguished by a host, a version or anything else you define, and `off(methods, path, constraints)` is how you remove a specific constrained route rather than all routes at that path. Custom constraint strategies can be registered, which is the extension point for multi-tenant or regional routing.

Versioned routes are handled through the options passed to `on()`, with documented `default` and `custom` forms, so URL versioning and header or media type versioning are both expressible at registration time.

Shorthand methods exist for registering common methods, which is ergonomics rather than behaviour. Nothing in the documentation makes matching order configurable, and that is the point: the router's guarantee is that resolution is predictable and identical across Node versions, which is what you want underneath a server.

Where a radix router is the wrong abstraction

The first wrong case is an application with a small route table. If you have twenty endpoints and no measured routing cost, everything above is surface area you have to learn and keep in mind, including match order, constraints, versioning and the URL-handling options. A framework's built-in routing would be shorter and easier to reason about.

The second is a non Node HTTP server. find-my-way operates on Node's `req` and `res` objects through `lookup(request, response, [context], [done])`. The `find(method, path, [constraints])` entry point is the framework independent part, but the package describes itself as framework independent in the sense of Node frameworks, as Fastify and Restify show. Adapting it to a worker or a runtime without Node's http primitives means reimplementing the integration surface.

The third case is where you need routing as a middleware with its own semantics: authentication scoping, per-tenant sub-routers, file serving fallbacks. Those are layering concerns a bare router deliberately leaves alone, and composing them means writing the layer yourself.

On the performance side, be precise about what is being claimed. The README points to a separate `router-benchmark` repository for comparison with commonly used routers and describes the tree as highly performant, without publishing figures in the documentation. If routing is a bottleneck in your measurement, run that comparison on your own hardware and your own route shapes before deciding, since a radix tree's advantage depends on how much your paths share prefixes.

TypeScript support and benchmark tooling ship with the package

The repository is set up so the type definitions are tested rather than hand maintained. `test:typescript` runs `tstyche`, and `package.json` points `types` at `index.d.ts`. The repository includes `neostandard` as a development dependency and an `eslint.config.js` file.

The repository includes benchmark tooling and three benchmark scripts. There are three scripts: `bench` runs `node./benchmark/bench.js`, `bench:cmp` runs `compare-branches.js`, and `bench:cmp:ci` runs the same with the `--ci` flag. The `bench:cmp` and `bench:cmp:ci` scripts run `compare-branches.js`, with the latter adding the `--ci` flag.

The benchmark and test tools are listed as development dependencies.

Licensing is the simplest thing in the package: MIT, declared in `package.json` and covered by the root `LICENSE` file. The author field names Tomas Della Vedova, and the repository is delvedor/find-my-way on the `main` branch, currently at 9.10.0.

Editorial conclusion

Adopt find-my-way if you are writing a server, a framework or an internal service in Node 20 or later and need matching throughput and constraints plus versioned routes, since the same router backs Fastify and Restify and the package carries only fast-deep-equal, fast-querystring and safe-regex2 at runtime. Skip it if your routing needs are modest, because the options surface alone is longer than most application routers and you would be learning a library to solve a problem you do not have. Before you adopt it, decide whether `allowUnsafeRegex` is acceptable anywhere in your codebase, since that flag disables the exponential time detection the README documents, and pick a `maxParamLength` deliberately rather than leaving the 100 character default that routes over-long parameters to your default handler.

Frequently asked questions

How do I install find-my-way and register my first route?

Run `npm i find-my-way --save`, create a router with `require('find-my-way')()`, register a path with `router.on('GET', '/', handler)`, and mount `router.lookup(req, res)` on an http server. The package requires Node 20 or later and is CommonJS.

What happens if I register a regex route that can run exponentially?

find-my-way throws at registration, detecting potentially catastrophic exponential-time regular expressions with safe-regex2. You can disable that check by passing the `allowUnsafeRegex` option, at the cost of allowing a pattern that can hang on untrusted input.

How do I make find-my-way ignore trailing slashes or duplicate slashes?

Set `ignoreTrailingSlash` so `/foo/` and `/foo` reach the same handler, or `ignoreDuplicateSlashes` so `/foo`, `//foo` and `///foo` do. When both are true, duplicates are removed first and trailing slashes second, so `//a//b//c//` becomes `/a/b/c`.

What is the maxParamLength option in find-my-way?

It sets the maximum length for parameters in parametric routes, standard, regex and multi, and defaults to 100 characters. Reaching the limit invokes the default route unless you provide an `onMaxParamLength` handler, which would typically respond with a 414.

Which real projects use find-my-way?

Fastify and Restify both use it, and the README points to a separate router-benchmark repository for comparison against commonly used routers. The current version is 9.10.0 and the last push was on 2026-10-10.

Official sources

  1. delvedor/find-my-way on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/delvedor-find-my-way.svg)](https://hysenlabs.com/projects/delvedor-find-my-way)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/delvedor-find-my-way.svg)](https://hysenlabs.com/projects/delvedor-find-my-way)

Was this useful?