uri-template-matcher
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

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

Package Sidebar

Install

npm i uri-template-matcher

Weekly Downloads

1,629

Version

1.1.1

License

ISC

Unpacked Size

49.3 kB

Total Files

9

Last publish

Collaborators

  • pablopang