Pattern Routes
Relevant source files
The following files were used as context for generating this wiki page:
Pattern Routes are an advanced feature in gr8hopper that enables dynamic, parameter-driven URL redirection. Unlike simple routes that match a single, static ID, pattern routes use wildcards and placeholders to match a range of incoming URL paths and query strings. This allows for complex routing logic to be centralized in a single route definition, capturing values from the incoming URL and using them to construct the destination URL.
This system is designed for scenarios requiring flexible routing, such as redirecting to different product pages, customer portals, or file paths based on dynamic segments in the URL. The matching logic is handled primarily by the matchRoute function, which parses patterns, extracts parameters, and validates against the request.
Sources: README.md:13-26, src/utils/matcher.ts:468-482
Pattern Syntax
A route is considered a "Pattern Route" if its ID contains special characters like {, *, ?, or :. The syntax supports defining parameters in both the path and the query string portion of the URL.
Sources: src/admin-html.ts:1330-1333, README.md:121-127
Supported Tokens
The following table summarizes the special tokens that can be used in a route ID to create a pattern.
| Token | Type | Example | Description |
|---|---|---|---|
{param} | Path | shop/{category} | Matches a required path segment and captures its value into category. |
:param | Path | shop/:category | A shorthand for {param}. Matches a required path segment. |
{param?} | Path | blog/{year?} | Matches an optional path segment. If not present, the parameter is not set. |
:param? | Path | blog/:year? | A shorthand for {param?}. Matches an optional path segment. |
{param=default} | Path | lang/{locale=en} | Matches an optional path segment. If not present, the parameter is set to the default value (en). |
* | Path | files/* | Wildcard that matches exactly one path segment. |
** | Path | files/** | Globstar that matches zero or more path segments, consuming the rest of the path. |
?key={value} | Query | product/{id}?lang={lang} | Requires the lang query parameter and captures its value. |
?key={value?} | Query | product/{id}?lang={lang?} | The lang query parameter is optional. |
?key={value=default} | Query | product/{id}?lang={lang=en} | The lang query parameter is optional; defaults to en if not present. |
?key=literal | Query | product/{id}?status=live | Requires the status query parameter to have the exact value live. |
?* | Query | product/{id}?* | A wildcard that allows any and all query parameters to be present. |
Sources: README.md:121-127, src/utils/matcher.ts:91-174
Multiple Wildcards
If a pattern contains multiple wildcards of the same type (* or **), they are indexed sequentially starting from the second instance.
- First
*is captured as{*}, second as{*1}, third as{*2}, etc. - First
**is captured as{\*\*}, second as{\*\*1}, etc.
Example:
- Pattern:
a/*/b/* - Path:
/a/x/b/y - Captured Params:
{ '*': 'x', '*1': 'y' }
Sources: README.md:134-143, src/utils/matcher.ts:192-205, test/matcher.test.ts:114-124
Matching Process
When a request comes in, gr8hopper attempts to match it against defined pattern routes. This process is orchestrated by the matchRoute function.
The following diagram illustrates the high-level flow of the matching logic.
This diagram shows the main steps within the matchRoute function to determine if a URL matches a given pattern.
Sources: src/utils/matcher.ts:468-517
1. Pattern Parsing
Before matching, the route ID pattern string is parsed into a structured object by the parsePattern function. This function separates the path pattern from the query string specifications.
To optimize performance, the results of parsePattern are cached in a memoization map (parsePatternCache). This avoids re-parsing the same pattern string on every request. The cache has a maximum size and uses a "Least Recently Used" (LRU) eviction strategy.
Sources: src/utils/matcher.ts:22-38
The parsing logic can be visualized as follows:
This diagram shows the flow of the parsePattern function, including its caching mechanism.
Sources: src/utils/matcher.ts:22-177
2. Path Matching
Path matching is performed by the matchPathSegments function, which recursively compares segments of the request path against the parsed path pattern.
- Case-Insensitivity: Path matching is case-insensitive. A pattern
Shop/{id}will match/shop/123and/SHOP/123. - Parameter Casing: Captured parameter values, however, preserve their original casing from the request URL.
- ReDoS Protection: To prevent Regular Expression Denial of Service (ReDoS) attacks from overly complex patterns with multiple globstars, the function implements a backtracking limit (
MAX_BACKTRACK_DEPTH). If the number of recursive visits exceeds this limit, the match is aborted.
Sources: README.md:145-149, src/utils/matcher.ts:18-20, 207-215, SECURITY.md:55-56
The recursive logic handles different segment types:
This diagram shows the recursive decision-making process inside matchPathSegments.
Sources: src/utils/matcher.ts:207-321
3. Query Parameter Matching
After the path successfully matches, the matchQueryParams function checks the request's query parameters against the pattern's query specifications.
- Case-Sensitivity: Query parameter names are matched case-sensitively. A pattern
?source={src}will not match?Source=.... - Precedence: If a parameter is defined in the pattern (e.g.,
?lang={lang}), it is handled by the matcher. If the route also haspassthrough: trueenabled, this declared parameter is excluded from the passthrough logic to avoid duplication.
Sources: README.md:150-152, 166-169, test/matcher.test.ts:145-153, src/utils/matcher.ts:420-466
Pattern Validation
To ensure system stability and prevent malformed routes, every pattern route ID is validated before it is created or updated. This is handled by the validateRouteIdPattern function, which is called from the admin API handler.
Sources: src/handlers/admin.ts:100-103, src/utils/validation.ts:29-71
The validation checks for several common errors:
- Unbalanced or nested
{}braces. - Empty parameter names (e.g.,
shop/{}). - Invalid characters in parameter names.
- Malformed query parameter specifications.
This diagram shows how pattern validation is integrated into the admin API workflow.
Sources: src/handlers/admin.ts:92-113, test/validation.test.ts
Reserved Placeholders & Parameters
{route} Placeholder
The placeholder {route} is reserved. It is automatically replaced with the route's ID (URL-encoded) in the destination URL template. This is useful for tracking which route was triggered. Any user-defined parameter named route in a pattern will be overwritten by this automatic value.
Sources: README.md:107-109, 153-155, src/utils/matcher.ts:262-265, 411-414
Excluded Passthrough Parameters
When a pattern route has passthrough: true, query parameters from the source URL are appended to the destination URL. However, to prevent conflicts and redundancy, any parameter name explicitly declared in the route pattern (both path and query) is excluded from this passthrough. The reserved route parameter is also always excluded.
The getPatternParamNames function is responsible for extracting all declared parameter names from a pattern for this purpose.