npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qezor/urlex

v1.0.10

Published

Zero-dependency URL pattern matcher with named params, wildcards, ranged globs and literal sets

Downloads

42

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/urlex

Import / 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))  // null

Pattern Syntax

Literal segment

Exact match. Case-sensitive.

example.com/users/profile

Matches 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)  // → null

Escaping

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 host
const 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)  // → null

Glob 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/42

Supported 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 present

parse(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 test
52 passed, 0 failed

License

MIT © Abdul Ahad