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') // truePath 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') // falseMultiple Parameters
matchPath('/users/:userId/posts/:postId', '/users/1/posts/42') // true
matchPath('/api/:version/users/:id', '/api/v1/users/123') // trueNamed Parameters
Parameters can have any name:
matchPath('/users/:userId', '/users/123') // true
matchPath('/posts/:postId', '/posts/456') // true
matchPath('/orders/:orderId', '/orders/789') // trueExtracting 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') // falseWildcard 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') // trueCombining 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') // trueCreating 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') // trueAPI 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') // trueFile 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') // trueAdmin 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') // truePattern 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/') // trueQuery 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) // trueEmpty Segments
matchPath('/users/:id', '/users/') // false
matchPath('/users/:id', '/users//') // falseUse 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 responseBest 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
- Use path matching with Record & Replay
- Combine with Scenarios for complete routing
- See Examples for practical use cases