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 🙏

© 2025 – Pkg Stats / Ryan Hefner

uri-template-matcher

v1.1.2

Published

A lightweight URI template matcher based on RFC 6570

Readme

URI Template Matcher

A lightweight URI template library based on RFC 6570 that supports both matching URIs against templates to extract parameters and expanding templates with variables to generate URIs.

Installation

npm install uri-template-matcher
pnpm add uri-template-matcher

Usage

Basic Usage

import { UriTemplateMatcher } from 'uri-template-matcher';

const matcher = new UriTemplateMatcher();

// Register URI templates
matcher.add('/users/{id}');
matcher.add('/posts/{postId}/comments/{commentId}');

// Match URIs and extract parameters
const result = matcher.match('/users/123');
console.log(result);
// Output: { template: '/users/{id}', params: { id: '123' } }

const result2 = matcher.match('/posts/456/comments/789');
console.log(result2);
// Output: { 
//   template: '/posts/{postId}/comments/{commentId}', 
//   params: { postId: '456', commentId: '789' } 
// }

Working with Multiple Templates

const matcher = new UriTemplateMatcher();

// Add multiple templates
matcher.add('/api/users/{id}');
matcher.add('/api/posts/{id}');
matcher.add('/api/health');

// The matcher returns the first matching template
const userMatch = matcher.match('/api/users/123');
console.log(userMatch);
// Output: { template: '/api/users/{id}', params: { id: '123' } }

const healthMatch = matcher.match('/api/health');
console.log(healthMatch);
// Output: { template: '/api/health', params: {} }

// No match returns null
const noMatch = matcher.match('/api/unknown');
console.log(noMatch); // Output: null

URL Encoding Support

The matcher automatically handles URL encoding and decoding:

const matcher = new UriTemplateMatcher();
matcher.add('/search/{query}');

const result = matcher.match('/search/hello%20world');
console.log(result);
// Output: { template: '/search/{query}', params: { query: 'hello world' } }

RFC 6570 Level 2 - Reserved String Expansion

const matcher = new UriTemplateMatcher();

// Reserved expansion with '+' operator
matcher.add('/files/{+path}');
const result = matcher.match('/files/docs/readme.txt');
console.log(result);
// Output: { template: '/files/{+path}', params: { path: 'docs/readme.txt' } }

// Fragment expansion with '#' operator
matcher.add('/page#{section}');
const fragmentResult = matcher.match('/page#introduction');
console.log(fragmentResult);
// Output: { template: '/page#{section}', params: { section: 'introduction' } }

RFC 6570 Level 3 - Advanced Operators

const matcher = new UriTemplateMatcher();

// Dot notation
matcher.add('/files{.format}');
console.log(matcher.match('/files.json'));
// Output: { template: '/files{.format}', params: { format: 'json' } }

// Path segments
matcher.add('/api{/version}/users');
console.log(matcher.match('/api/v1/users'));
// Output: { template: '/api{/version}/users', params: { version: 'v1' } }

// Query parameters
matcher.add('/search{?q}');
console.log(matcher.match('/search?q=test'));
// Output: { template: '/search{?q}', params: { q: 'test' } }

// Multiple query parameters
matcher.add('/search{?q,limit}');
console.log(matcher.match('/search?q=test&limit=10'));
// Output: { template: '/search{?q,limit}', params: { q: 'test', limit: '10' } }

// Query continuation
matcher.add('/search?type=user{&q}');
console.log(matcher.match('/search?type=user&q=john'));
// Output: { template: '/search?type=user{&q}', params: { q: 'john' } }

// Semicolon parameters
matcher.add('/api{;version}');
console.log(matcher.match('/api;version=v1'));
// Output: { template: '/api{;version}', params: { version: 'v1' } }

RFC 6570 Level 4 - Value Modifiers

const matcher = new UriTemplateMatcher();

// Prefix modifiers
matcher.add('/api/{name:3}');
console.log(matcher.match('/api/toolong'));
// Output: { template: '/api/{name:3}', params: { name: 'too' } }

// Explode modifiers with lists
matcher.add('/tags{.tags*}');
console.log(matcher.match('/tags.red.green.blue'));
// Output: { template: '/tags{.tags*}', params: { tags: ['red', 'green', 'blue'] } }

// Explode modifiers with query parameters
matcher.add('/search{?filters*}');
console.log(matcher.match('/search?color=red&size=large'));
// Output: { template: '/search{?filters*}', params: { filters: ['color=red', 'size=large'] } }

Managing Templates

const matcher = new UriTemplateMatcher();

// Add templates
matcher.add('/users/{id}');
matcher.add('/posts/{id}');

// Get all registered templates
console.log(matcher.all());
// Output: ['/users/{id}', '/posts/{id}']

// Clear all templates
matcher.clear();
console.log(matcher.all());
// Output: []

Real-World Examples

REST API Routing

const matcher = new UriTemplateMatcher();

// Register API endpoints
matcher.add('/api/v1/users/{id}');
matcher.add('/api/v1/users/{id}/posts');
matcher.add('/api/v1/posts/{postId}/comments/{commentId}');

// Route incoming requests
function routeRequest(path) {
  const match = matcher.match(path);
  
  if (!match) {
    return { status: 404, message: 'Not Found' };
  }
  
  return {
    template: match.template,
    params: match.params,
    status: 200
  };
}

console.log(routeRequest('/api/v1/users/123'));
// Output: { template: '/api/v1/users/{id}', params: { id: '123' }, status: 200 }

GitHub-style URLs

const matcher = new UriTemplateMatcher();
matcher.add('/repos/{owner}/{repo}/issues/{issue_number}');

const match = matcher.match('/repos/octocat/Hello-World/issues/1');
console.log(match);
// Output: {
//   template: '/repos/{owner}/{repo}/issues/{issue_number}',
//   params: {
//     owner: 'octocat',
//     repo: 'Hello-World',
//     issue_number: '1'
//   }
// }

File System Paths

const matcher = new UriTemplateMatcher();
matcher.add('file:///{+path}');

const match = matcher.match('file:///home/user/documents/file.txt');
console.log(match);
// Output: {
//   template: 'file:///{+path}',
//   params: { path: 'home/user/documents/file.txt' }
// }

URI Template Expansion

In addition to matching URIs against templates, this library also supports expanding URI templates with variables according to RFC 6570.

Basic Expansion

import { UriTemplateExpander } from 'uri-template-matcher';

const expander = new UriTemplateExpander('/users/{id}');
const uri = expander.expand({ id: '123' });
console.log(uri);
// Output: '/users/123'

Expansion with Multiple Variables

const expander = new UriTemplateExpander('/posts/{postId}/comments/{commentId}');
const uri = expander.expand({ postId: '456', commentId: '789' });
console.log(uri);
// Output: '/posts/456/comments/789'

Reserved String Expansion

const expander = new UriTemplateExpander('/files/{+path}');
const uri = expander.expand({ path: 'docs/readme.txt' });
console.log(uri);
// Output: '/files/docs/readme.txt'

Fragment Expansion

const expander = new UriTemplateExpander('/page#{section}');
const uri = expander.expand({ section: 'introduction' });
console.log(uri);
// Output: '/page#introduction'

Query Parameter Expansion

// Single query parameter
const expander1 = new UriTemplateExpander('/search{?q}');
console.log(expander1.expand({ q: 'test' }));
// Output: '/search?q=test'

// Multiple query parameters
const expander2 = new UriTemplateExpander('/search{?q,limit}');
console.log(expander2.expand({ q: 'test', limit: '10' }));
// Output: '/search?q=test&limit=10'

// Query continuation
const expander3 = new UriTemplateExpander('/search?type=user{&q}');
console.log(expander3.expand({ q: 'john' }));
// Output: '/search?type=user&q=john'

Path and Dot Expansion

// Dot notation
const expander1 = new UriTemplateExpander('/files{.format}');
console.log(expander1.expand({ format: 'json' }));
// Output: '/files.json'

// Path segments
const expander2 = new UriTemplateExpander('/api{/version}/users');
console.log(expander2.expand({ version: 'v1' }));
// Output: '/api/v1/users'

Value Modifiers

// Prefix modifiers
const expander1 = new UriTemplateExpander('/api/{name:3}');
console.log(expander1.expand({ name: 'toolong' }));
// Output: '/api/too'

// Explode modifiers with arrays
const expander2 = new UriTemplateExpander('/tags{.tags*}');
console.log(expander2.expand({ tags: ['red', 'green', 'blue'] }));
// Output: '/tags.red.green.blue'

// Explode modifiers with objects
const expander3 = new UriTemplateExpander('/search{?filters*}');
console.log(expander3.expand({ filters: { color: 'red', size: 'large' } }));
// Output: '/search?color=red&size=large'

Working with Complex Variables

const expander = new UriTemplateExpander('/search{?q,filters*}');
const uri = expander.expand({
  q: 'uri templates',
  filters: {
    category: 'technology',
    lang: 'en'
  }
});
console.log(uri);
// Output: '/search?q=uri%20templates&category=technology&lang=en'

Undefined Variables

Variables that are undefined, null, or empty arrays/objects are ignored:

const expander = new UriTemplateExpander('/search{?q,limit,offset}');
const uri = expander.expand({ q: 'test' }); // limit and offset are undefined
console.log(uri);
// Output: '/search?q=test'

Using the Expand Function Directly

You can also use the expand function directly without creating an instance:

import { expand_template } from 'uri-template-matcher';

const uri = expand_template('/users/{id}', { id: '123' });
console.log(uri);
// Output: '/users/123'

API Reference

Class: UriTemplateMatcher

Constructor

new UriTemplateMatcher()

Creates a new UriTemplateMatcher instance.

Methods

add(template: string): void

Adds a URI template to the matcher.

  • template - The URI template string to add
  • Throws Error if template is invalid
match(uri: string): MatchResult | null

Matches a URI against all registered templates.

  • uri - The URI to match
  • Returns MatchResult if a match is found, null otherwise
  • Throws Error if URI is invalid
clear(): void

Clears all registered templates.

all(): string[]

Returns an array of all registered template strings.

Class: UriTemplateExpander

Constructor

new UriTemplateExpander(template: string)

Creates a new UriTemplateExpander instance.

  • template - The URI template string to use for expansion
  • Throws Error if template is invalid

Methods

expand(variables: Record<string, any>): string

Expands the template with the given variables.

  • variables - Object containing variable names and values
  • Returns the expanded URI string
  • Throws Error if expansion fails

Functions

expand_template(template: string, variables: Record<string, any>): string

Expands a URI template with given variables.

  • template - The URI template string to expand
  • variables - Object containing variable names and values
  • Returns the expanded URI string
  • Throws Error if template is invalid or expansion fails

Types

MatchResult

interface MatchResult {
  template: string;                    // The matched template
  params: Record<string, string | string[]>; // Extracted parameters
}

RFC 6570 Compliance

This library implements URI Template matching and expansion based on RFC 6570 with support for:

  • Level 1: Simple string expansion
  • Level 2: Reserved string expansion (+) and fragment expansion (#)
  • Level 3: Multiple variable expansion with operators (., /, ;, ?, &)
  • Level 4: Value modifiers including prefix (:n) and explode (*) modifiers

License

MIT