Core ConceptsPath Matching

Path Matching

MockMaster’s core package provides powerful path matching capabilities for HTTP routing.


Overview

Path matching enables:

  • Exact matching - Match specific paths
  • Parameter matching - Extract dynamic path segments
  • Wildcard matching - Match multiple paths with patterns
  • Type-safe extraction - Get parameter values

Exact Path Matching

Match paths exactly as they are:

import { matchPath } from '@mockmaster/core'
 
matchPath('/users', '/users')           // true
matchPath('/users', '/posts')           // false
matchPath('/users', '/users/123')       // false
matchPath('/api/v1/users', '/api/v1/users')  // true

Path Parameters

Single Parameter

Match paths with dynamic segments:

import { matchPath } from '@mockmaster/core'
 
// Pattern with :id parameter
matchPath('/users/:id', '/users/123')     // true
matchPath('/users/:id', '/users/456')     // true
matchPath('/users/:id', '/users/abc')     // true
 
// Different paths don't match
matchPath('/users/:id', '/posts/123')     // false
matchPath('/users/:id', '/users')         // false

Multiple Parameters

matchPath('/users/:userId/posts/:postId', '/users/1/posts/42')  // true
matchPath('/api/:version/users/:id', '/api/v1/users/123')       // true

Named Parameters

Parameters can have any name:

matchPath('/users/:userId', '/users/123')           // true
matchPath('/posts/:postId', '/posts/456')           // true
matchPath('/orders/:orderId', '/orders/789')        // true

Extracting Parameters

Basic Extraction

Extract parameter values from paths:

import { extractParams } from '@mockmaster/core'
 
const params = extractParams('/users/:id', '/users/123')
console.log(params)  // { id: '123' }

Multiple Parameters

const params = extractParams(
  '/users/:userId/posts/:postId',
  '/users/1/posts/42'
)
 
console.log(params)
// { userId: '1', postId: '42' }

With Different Names

extractParams('/api/:version/users/:userId', '/api/v1/users/123')
// { version: 'v1', userId: '123' }
 
extractParams('/posts/:id/comments/:commentId', '/posts/42/comments/7')
// { id: '42', commentId: '7' }

Wildcard Matching

Single Wildcard

Match any path after the wildcard:

import { matchPath } from '@mockmaster/core'
 
matchPath('/api/*', '/api/users')                  // true
matchPath('/api/*', '/api/users/123')              // true
matchPath('/api/*', '/api/users/123/posts')        // true
matchPath('/api/*', '/api/v1/products')            // true
 
// Must start with /api/
matchPath('/api/*', '/users')                      // false

Wildcard at Different Positions

matchPath('/api/v1/*', '/api/v1/users')            // true
matchPath('/api/v1/*', '/api/v1/users/123')        // true
matchPath('/api/v1/*', '/api/v2/users')            // false
 
matchPath('/files/*/download', '/files/doc.pdf/download')     // true
matchPath('/files/*/download', '/files/image.png/download')   // true

Combining Parameters and Wildcards

// Parameter + wildcard
matchPath('/users/:id/*', '/users/123/posts')              // true
matchPath('/users/:id/*', '/users/123/posts/456')          // true
 
// Wildcard + parameter
matchPath('/api/*/users/:id', '/api/v1/users/123')         // true
matchPath('/api/*/users/:id', '/api/v2/users/456')         // true

Creating Matchers

For better performance when matching multiple times, create reusable matchers:

import { createMatcher } from '@mockmaster/core'
 
const matcher = createMatcher([
  '/users',
  '/users/:id',
  '/posts',
  '/posts/:id',
  '/api/*'
])
 
// Fast matching
matcher('/users')           // { matched: true, pattern: '/users' }
matcher('/users/123')       // { matched: true, pattern: '/users/:id' }
matcher('/posts/456')       // { matched: true, pattern: '/posts/:id' }
matcher('/api/anything')    // { matched: true, pattern: '/api/*' }
matcher('/unknown')         // { matched: false }

Real-World Examples

REST API Routes

import { matchPath, extractParams } from '@mockmaster/core'
 
// List resources
matchPath('/users', '/users')                      // true
matchPath('/products', '/products')                // true
 
// Get single resource
matchPath('/users/:id', '/users/123')              // true
const { id } = extractParams('/users/:id', '/users/123')  // id: '123'
 
// Nested resources
matchPath('/users/:userId/posts/:postId', '/users/1/posts/42')  // true
const params = extractParams(
  '/users/:userId/posts/:postId',
  '/users/1/posts/42'
)  // { userId: '1', postId: '42' }
 
// Actions on resources
matchPath('/users/:id/activate', '/users/123/activate')    // true
matchPath('/posts/:id/publish', '/posts/456/publish')      // true

API Versioning

// Version in path
matchPath('/api/v1/users', '/api/v1/users')                // true
matchPath('/api/v2/users', '/api/v2/users')                // true
 
// Version as parameter
matchPath('/api/:version/users', '/api/v1/users')          // true
matchPath('/api/:version/users/:id', '/api/v2/users/123')  // true
 
// Wildcard for all versions
matchPath('/api/*/users', '/api/v1/users')                 // true
matchPath('/api/*/users', '/api/v2/users')                 // true
matchPath('/api/*/users', '/api/v3/users')                 // true

File Paths

// Static files
matchPath('/static/*', '/static/css/main.css')             // true
matchPath('/static/*', '/static/js/app.js')                // true
matchPath('/static/*', '/static/images/logo.png')          // true
 
// User uploads
matchPath('/uploads/:userId/*', '/uploads/123/photo.jpg')  // true
matchPath('/uploads/:userId/*', '/uploads/456/doc.pdf')    // true
 
// Downloads
matchPath('/downloads/:fileId', '/downloads/abc123')       // true

Admin Routes

// Admin area
matchPath('/admin/*', '/admin/users')                      // true
matchPath('/admin/*', '/admin/settings')                   // true
matchPath('/admin/*', '/admin/dashboard')                  // true
 
// Specific admin routes
matchPath('/admin/users/:id', '/admin/users/123')          // true
matchPath('/admin/posts/:id/edit', '/admin/posts/456/edit')  // true

Pattern Priority

When using multiple patterns, more specific patterns should be checked first:

import { createMatcher } from '@mockmaster/core'
 
// Order matters for overlapping patterns
const matcher = createMatcher([
  '/users/me',           // Most specific - check first
  '/users/:id',          // Less specific
  '/users/*',            // Least specific - check last
])
 
matcher('/users/me')         // Matches: '/users/me'
matcher('/users/123')        // Matches: '/users/:id'
matcher('/users/anything')   // Matches: '/users/*'

Edge Cases

Trailing Slashes

matchPath('/users', '/users')      // true
matchPath('/users', '/users/')     // false (exact match)
 
// Use wildcard for flexibility
matchPath('/users/*', '/users/')   // true

Query Strings

Path matching ignores query strings:

// Extract path first, then match
const url = '/users/123?sort=name&page=1'
const path = url.split('?')[0]  // '/users/123'
matchPath('/users/:id', path)   // true

Empty Segments

matchPath('/users/:id', '/users/')     // false
matchPath('/users/:id', '/users//')    // false

Use with Replay Handler

Path matching is used internally by replay handlers:

import { createScenario, createRecording, addRecordingToScenario, createReplayHandler } from '@mockmaster/msw-adapter'
 
let scenario = createScenario('api', 'API endpoints')
 
// Add recording with parameter
const recording = createRecording(
  { method: 'GET', url: 'https://api.example.com/users/123', path: '/users/:id', timestamp: Date.now() },
  { status: 200, body: { id: 123, name: 'John' }, timestamp: Date.now() }
)
 
scenario = addRecordingToScenario(scenario, recording)
 
const handler = createReplayHandler(scenario)
 
// Handler uses path matching internally
const response = handler({ method: 'GET', path: '/users/123' })
// Matches '/users/:id' pattern and returns response

Best Practices

1. Be Specific

Use specific patterns when possible:

// Good - specific
'/users/:id'
'/api/v1/users/:id'
 
// Avoid if unnecessary - too broad
'/*'
'/api/*'

2. Use Meaningful Parameter Names

// Good
'/users/:userId'
'/posts/:postId'
'/orders/:orderId'
 
// Bad
'/users/:id'
'/posts/:id'
'/orders/:id'  // All use generic 'id'

3. Document Patterns

Add comments explaining complex patterns:

// Match: /api/v1/users/123/posts/456
// Params: version, userId, postId
matchPath('/api/:version/users/:userId/posts/:postId', path)

4. Validate Extracted Parameters

const params = extractParams('/users/:id', path)
 
if (params.id) {
  const userId = parseInt(params.id, 10)
  if (isNaN(userId)) {
    console.error('Invalid user ID')
  }
}

Next Steps