radix3 vs find-my-way vs trek-router: HTTP Router Engines in Node.js (2026)
TL;DR
radix3 is the UnJS radix tree router — ultra-fast path matching, used internally by H3 and Nitro, supports static routes, parameters, and wildcards. find-my-way is Fastify's router — radix tree with HTTP method support, parametric routes, constraints (versioning, host-based routing), and the fastest benchmarked router. trek-router is a lightweight trie-based router — simple API, named parameters, used as a building block. In 2026: find-my-way for Fastify ecosystem or standalone use, radix3 for UnJS/H3 ecosystem, trek-router for educational or minimal use cases.
Key Takeaways
- radix3: ~5M weekly downloads — UnJS, powers H3/Nitro routing, radix tree, TypeScript
- find-my-way: ~5M weekly downloads — Fastify core, HTTP method support, constraints, fastest
- trek-router: ~50K weekly downloads — minimal trie router, simple API
- These are router engines — they match URL paths to handlers, not full frameworks
- Radix tree routers are O(path length) — much faster than linear array scanning
- find-my-way is the only one with built-in HTTP method support and constraints
How Router Engines Work
Linear scan (naive):
routes = ["/api/users", "/api/users/:id", "/api/posts", ...]
For each request, scan ALL routes until match → O(n) per request
Radix tree (fast):
/api
/users
/:id → handler
/ → handler
/posts
/:id → handler
/ → handler
Lookup is O(path length), not O(route count)
1000 routes? Same speed as 10 routes.
radix3
radix3 — UnJS radix tree router:
Basic usage
import { createRouter } from "radix3"
const router = createRouter()
// Static routes:
router.insert("/api/health", { handler: "healthCheck" })
router.insert("/api/packages", { handler: "listPackages" })
// Parameter routes:
router.insert("/api/packages/:name", { handler: "getPackage" })
router.insert("/api/packages/:name/versions/:version", { handler: "getVersion" })
// Wildcard:
router.insert("/static/**", { handler: "serveStatic" })
// Lookup:
const match = router.lookup("/api/packages/react")
// → { handler: "getPackage", params: { name: "react" } }
const match2 = router.lookup("/api/packages/react/versions/19.0.0")
// → { handler: "getVersion", params: { name: "react", version: "19.0.0" } }
const match3 = router.lookup("/static/images/logo.png")
// → { handler: "serveStatic", params: { _: "images/logo.png" } }
const miss = router.lookup("/not/found")
// → null
Remove and update routes
import { createRouter } from "radix3"
const router = createRouter()
router.insert("/api/packages", { handler: "v1" })
// Remove:
router.remove("/api/packages")
// Re-insert with updated data:
router.insert("/api/packages", { handler: "v2" })
How H3 uses radix3
// H3 (UnJS HTTP framework) uses radix3 internally:
import { createApp, createRouter, eventHandler } from "h3"
const app = createApp()
const router = createRouter()
// These routes are stored in a radix3 tree:
router.get("/api/packages", eventHandler(() => ({ packages: [] })))
router.get("/api/packages/:name", eventHandler((event) => {
return { name: event.context.params.name }
}))
app.use(router)
// radix3 handles the path matching — O(path length) lookups
find-my-way
find-my-way — Fastify's router:
Basic usage
import FindMyWay from "find-my-way"
const router = FindMyWay()
// Routes with HTTP methods:
router.on("GET", "/api/packages", (req, res, params) => {
res.end(JSON.stringify({ packages: [] }))
})
router.on("GET", "/api/packages/:name", (req, res, params) => {
res.end(JSON.stringify({ name: params.name }))
})
router.on("POST", "/api/packages", (req, res, params) => {
res.end(JSON.stringify({ created: true }))
})
// Wildcard:
router.on("GET", "/static/*", (req, res, params) => {
res.end(`Serving: ${params["*"]}`)
})
// Lookup:
router.lookup(req, res) // Finds and calls the matching handler
Parametric routes
const router = FindMyWay()
// Named parameters:
router.on("GET", "/api/packages/:name", handler)
// Matches: /api/packages/react → params.name = "react"
// Multiple parameters:
router.on("GET", "/api/packages/:name/versions/:version", handler)
// Matches: /api/packages/react/versions/19.0.0
// Regex constraints on parameters:
router.on("GET", "/api/packages/:id(^\\d+$)", handler)
// Matches: /api/packages/123 (only numbers)
// Rejects: /api/packages/react
// Multi-parametric:
router.on("GET", "/api/:org-:repo", handler)
// Matches: /api/facebook-react → params = { org: "facebook", repo: "react" }
Constraints (versioning, host-based)
import FindMyWay from "find-my-way"
const router = FindMyWay()
// Version-based routing:
router.on("GET", "/api/packages", { constraints: { version: "1.0.0" } }, (req, res) => {
res.end("API v1")
})
router.on("GET", "/api/packages", { constraints: { version: "2.0.0" } }, (req, res) => {
res.end("API v2")
})
// Host-based routing:
router.on("GET", "/", { constraints: { host: "api.pkgpulse.com" } }, (req, res) => {
res.end("API subdomain")
})
router.on("GET", "/", { constraints: { host: "www.pkgpulse.com" } }, (req, res) => {
res.end("Web subdomain")
})
Performance
find-my-way benchmarks (operations per second):
Static routes: ~15M ops/sec
Parametric routes: ~5M ops/sec
Wildcard routes: ~8M ops/sec
vs koa-router: ~500K ops/sec (30x slower)
vs express router: ~300K ops/sec (50x slower)
find-my-way is consistently the fastest Node.js router.
How Fastify uses find-my-way
// Fastify uses find-my-way internally for ALL route matching:
import Fastify from "fastify"
const app = Fastify()
// These routes are stored in find-my-way's radix tree:
app.get("/api/packages/:name", async (request) => {
return { name: request.params.name }
})
// Fastify's constraint system (versioning) is powered by find-my-way:
app.route({
method: "GET",
url: "/api/data",
constraints: { version: "2.0.0" },
handler: async () => ({ version: 2 }),
})
trek-router
trek-router — minimal trie router:
Basic usage
import Router from "trek-router"
const router = new Router()
// Add routes:
router.add("GET", "/api/packages", "listPackages")
router.add("GET", "/api/packages/:name", "getPackage")
router.add("POST", "/api/packages", "createPackage")
// Find:
const [handler, params] = router.find("GET", "/api/packages/react")
// handler → "getPackage"
// params → [{ name: "name", value: "react" }]
const [handler2, params2] = router.find("GET", "/not-found")
// handler2 → undefined
// params2 → undefined
trek-router limitations
trek-router:
✅ Simple API (add + find)
✅ Trie-based (fast lookups)
✅ Lightweight (~2 KB)
❌ No wildcard routes
❌ No constraints
❌ No route removal
❌ Not actively maintained
❌ Limited parameter syntax
For production: use find-my-way or radix3
Feature Comparison
| Feature | radix3 | find-my-way | trek-router |
|---|---|---|---|
| Data structure | Radix tree | Radix tree | Trie |
| HTTP methods | ❌ (path only) | ✅ | ✅ |
| Named parameters | ✅ | ✅ | ✅ |
| Wildcard routes | ✅ | ✅ | ❌ |
| Regex constraints | ❌ | ✅ | ❌ |
| Version routing | ❌ | ✅ | ❌ |
| Host routing | ❌ | ✅ | ❌ |
| Route removal | ✅ | ✅ | ❌ |
| TypeScript | ✅ | ✅ | ⚠️ |
| Used by | H3, Nitro | Fastify | — |
| Weekly downloads | ~5M | ~5M | ~50K |
When to Use Each
Use radix3 if:
- Building with H3/Nitro (UnJS ecosystem)
- Need a pure path matcher (no HTTP method binding)
- Want the simplest possible radix tree router
- Building custom routing logic on top
Use find-my-way if:
- Building with Fastify or standalone HTTP servers
- Need HTTP method support (GET/POST/PUT/DELETE)
- Want constraint-based routing (versioning, host)
- Need the fastest benchmarked router
Use trek-router if:
- Learning how trie/radix routers work
- Need the most minimal router possible
- Building a simple proof of concept
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on radix3 v1.x, find-my-way v8.x, and trek-router v1.x.