@qezor/urlex
v1.0.10
Published
Zero-dependency URL pattern matcher with named params, wildcards, ranged globs and literal sets
Downloads
42
Maintainers
Readme
@qezor/urlex
A zero-dependency, production-grade URL pattern matcher for Node.js and any JavaScript runtime.
Patterns are compiled once and reused for efficient repeated matching. Supports host-based routing, named params, wildcards, ranged globs with backtracking, literal sets, signed integer bounds, and literal escaping.
Installation
npm install @qezor/urlexImport / Require
// CommonJS
const { compile, match, parse, matchHost, matchPath } = require("@qezor/urlex")
// ES Modules
import { compile, match, parse, matchHost, matchPath } from "@qezor/urlex"Quick Start
const { compile, match } = require("@qezor/urlex")
const pattern = compile("example.com/users/:id")
// With param capture
const params = match(pattern, "https://example.com/users/42", true)
console.log(params) // { id: "42", "**": [] }
// Match only — zero allocation
const ok = match(pattern, "https://example.com/users/42", false)
console.log(ok) // true
// No match
console.log(match(pattern, "https://example.com/posts/1", true)) // nullPattern Syntax
Literal segment
Exact match. Case-sensitive.
example.com/users/profileMatches https://example.com/users/profile only.
:name — named required param
Captures exactly one segment.
const c = compile("example.com/users/:id")
match(c, "https://example.com/users/42", true)
// → { id: "42", "**": [] }*:name — named optional param
Consumes one segment if present, empty string if absent. Best used at the end of a pattern.
const c = compile("example.com/api/*:version/users")
match(c, "https://example.com/api/v2/users", true) // → { version: "v2", "**": [] }
match(c, "https://example.com/api/users", true) // → { version: "", "**": [] }* — unnamed single-segment wildcard
Matches exactly one segment, no capture.
const c = compile("example.com/files/*/download")
match(c, "https://example.com/files/report/download", false) // → true
match(c, "https://example.com/files/download", false) // → null** — unlimited glob
Matches all remaining segments. Must be the last segment — throws at compile time otherwise.
Captured in params["**"][n] as an array of segments.
const c = compile("example.com/static/**")
match(c, "https://example.com/static/assets/img/logo.png", true)
// → { "**": [ ["assets", "img", "logo.png"] ] }
match(c, "https://example.com/static/", true)
// → { "**": [ [] ] } ← zero remaining segments[n] — exact segment count
Matches exactly n segments. Captured in params["**"].
const c = compile("example.com/[3]/end")
match(c, "https://example.com/a/b/c/end", true)
// → { "**": [ ["a", "b", "c"] ] }[min,max] — ranged segment count
Matches between min and max segments. Range nodes can appear anywhere in a pattern — backtracking handles downstream matching automatically.
const c = compile("example.com/[1,3]/end")
match(c, "https://example.com/a/b/end", true) // → matched, "**"[0] = ["a","b"]
match(c, "https://example.com/a/b/c/d/end", true) // → null (over max)Variants:
| Pattern | Meaning |
|---|---|
| [n] | exactly n segments |
| [min,max] | between min and max |
| [min,] | at least min |
| [,max] | at most max |
| [-3,5] | signed bounds — negative min supported |
| [-5,-1] | both bounds negative |
{a,b,c} — literal set (OR match)
Matches exactly one segment that equals any value in the set. Useful for file extensions, method names, fixed enums.
const c = compile("example.com/files/{jpg,png,gif}")
match(c, "https://example.com/files/jpg", false) // → true
match(c, "https://example.com/files/pdf", false) // → nullEscaping
Backslash escapes a special character, treating it as a plain literal.
| Escape | Produces |
|---|---|
| \{ | literal { |
| \* | literal * |
| \: | literal : |
| \\ | literal \ |
compile("example.com/\\{3}") // matches the literal string "{3}"
compile("example.com/\\*") // matches the literal string "*"
compile("example.com/\\:id") // matches the literal string ":id"Host Matching
Hosts are matched right-to-left, making wildcard subdomain patterns natural.
Host labels are matched case-insensitively and * matches exactly one host label.
compile("*.example.com/page") // any subdomain
compile("api.example.com/v1/**") // specific subdomain
compile("*.*.example.com/path") // two-level wildcard
compile("/users/:id") // path-only — matches any hostconst c = compile("*.example.com/page")
match(c, "https://blog.example.com/page", false) // → true
match(c, "https://cdn.example.com/page", false) // → true
match(c, "https://example.com/page", false) // → null
match(c, "https://other.com/page", false) // → nullGlob Captures — params["**"]
params["**"] is always an array — one entry per glob or range in the pattern, in pattern order. Each entry is an array of the matched segments (never joined — no information loss).
// Single glob
const c = compile("example.com/static/**")
match(c, "https://example.com/static/img/logo.png", true)
// → { "**": [ ["img", "logo.png"] ] }
// ↑ index 0 = first glob
// Multiple globs in order
const c2 = compile("example.com/[1,2]/mid/[1,2]/end")
match(c2, "https://example.com/a/b/mid/x/y/end", true)
// → { "**": [ ["a","b"], ["x","y"] ] }
// ↑ index 0 ↑ index 1
// Mix of named params and ranges
const c3 = compile("example.com/:version/[1,2]/users/:id")
match(c3, "https://example.com/v2/a/b/users/99", true)
// → { version: "v2", id: "99", "**": [ ["a","b"] ] }When no glob or range is in the pattern, params["**"] is always [] — never null, never undefined, safe to access without checks.
save=false — zero allocation fast path
When you only need to know if a URL matches without capturing params, pass save=false. No params object allocated, no array created — pure boolean check.
// Route filtering — check match only
if (match(compiled, url, false)) {
// matched
}URL Handling
The parser handles any protocol, strips port, query string, and hash automatically.
https://example.com:8080/users/42?q=hello#section
↓
example.com/users/42Supported protocols: http, https, ws, wss, ftp, and any other scheme:// format.
API
compile(pattern) → CompiledPattern
Compiles a pattern string into an optimized structure. Throws clearly at compile time on invalid or illegal patterns — never silently at match time.
const compiled = compile("example.com/users/:id")
// → { host: ["example","com"], path: [":id"] }Throws on:
**not in last position- Empty literal set
{} - Invalid range
[5,2](max < min) - Empty range
[]
match(compiled, url, save) → MatchParams | true | null
null → no match
true → matched, save=false
object → matched, save=true — params["**"] always presentparse(url) → { host: string[], path: string[] }
Splits a raw URL into segments. Useful for building custom matchers on top.
parse("https://api.example.com:3000/users/42?page=1")
// → { host: ["api","example","com"], path: ["users","42"] }matchHost(pattern, actual) → boolean
Low-level host matcher. Right-to-left, ["*"] matches any host.
matchPath(pattern, actual, params, save) → { matched, glob }
Low-level path matcher. Thin wrapper around the recursive backtracker.
Error Handling
All errors thrown by compile() are plain Error instances with descriptive messages — caught at build time, never at request time.
try {
compile("example.com/**/users") // ** not last
} catch (err) {
console.log(err.message)
// → '"**" must be the last segment in pattern: "example.com/**/users"'
}
compile("example.com/[5,2]/end") // max < min
// → 'Range max < min: "[5,2]"'
compile("example.com/{}/end") // empty set
// → 'Invalid literal set: "{}"'Testing
npm test52 passed, 0 failedLicense
MIT © Abdul Ahad
