Route Patterns
When a request arrives at your server, the router examines its path and
decides which handler should respond. Route patterns describe what paths
a handler accepts. A pattern like /users matches only that exact path,
but patterns can do much more.
Your First Pattern
The simplest pattern is a literal path:
router.add(method::get, "/health", health_check);
This handler responds when a client requests GET /health. The pattern
/health matches the path /health exactly.
Most applications need multiple routes:
router.add(method::get, "/", serve_homepage);
router.add(method::get, "/about", serve_about);
router.add(method::get, "/contact", serve_contact);
Literal patterns work well for static pages, but what about dynamic content?
Named Parameters
Consider a user profile page. You could write a separate route for every user, but that doesn’t scale. Instead, use a named parameter:
router.add(method::get, "/users/:id", show_user);
The colon introduces a parameter named id. This pattern matches
/users/alice, /users/42, or /users/john-doe. Inside the handler,
retrieve the captured value:
router.add(method::get, "/users/:id",
[](route_params& p)
{
auto user_id = p.param("id"); // "alice", "42", etc.
// ...
return route_done;
});
Parameters capture text until the next / or end of path. They must
capture at least one character, so /users/ without an ID does not match.
Multiple Parameters
Patterns can include several parameters. A blog might organize posts by author and slug:
router.add(method::get, "/blog/:author/:slug", show_post);
This matches /blog/jane/my-first-post with author = "jane" and
slug = "my-first-post".
REST APIs commonly nest resources this way:
router.add(method::get, "/users/:userId/orders/:orderId", show_order);
For the path /users/1001/orders/5042, the handler receives
userId = "1001" and orderId = "5042".
Parameters with Separators
Parameters can appear anywhere in a pattern, separated by literal text. An airline flight lookup might use:
router.add(method::get, "/flights/:from-:to", find_flights);
This matches /flights/LAX-JFK with from = "LAX" and to = "JFK".
The hyphen acts as a delimiter, allowing both parameters to capture
their respective values.
Similarly, a taxonomy browser might use dots:
router.add(method::get, "/species/:genus.:species", show_species);
For /species/Canis.lupus, this captures genus = "Canis" and
species = "lupus".
Wildcards
Parameters stop at / characters. When you need to capture a path
containing slashes, use a wildcard:
router.add(method::get, "/files/*filepath", serve_file);
The asterisk introduces a wildcard named filepath. Unlike parameters,
wildcards capture everything to the end of the path, including /
characters. This pattern matches:
-
/files/readme.txtwithfilepath = "readme.txt" -
/files/docs/manual.pdfwithfilepath = "docs/manual.pdf" -
/files/src/lib/util.cppwithfilepath = "src/lib/util.cpp"
A wildcard must capture at least one character, so /files/ alone does
not match.
GitHub-Style Routes
A version control browser might use this pattern:
router.add(method::get, "/:owner/:repo/blob/:branch/*path", show_file);
For the path /cppalliance/http/blob/develop/include/boost/http.hpp:
-
owner = "cppalliance" -
repo = "http" -
branch = "develop" -
path = "include/boost/http.hpp"
The named parameters capture individual segments while the wildcard captures the remainder.
Optional Groups
Some parts of a path may be optional. An API versioning scheme might
accept both /api and /api/v2:
router.add(method::get, "/api{/v:version}/users", list_users);
The braces define an optional group. This pattern matches:
-
/api/users(no version parameter captured) -
/api/v1/userswithversion = "1" -
/api/v2/userswithversion = "2"
Groups follow all-or-nothing matching. The entire group content must match, or the group is skipped entirely.
Optional File Extensions
A resource that supports multiple formats might use:
router.add(method::get, "/data/:id{.:format}", serve_data);
This matches:
-
/data/reportwithid = "report"(no format) -
/data/report.jsonwithid = "report",format = "json" -
/data/report.xmlwithid = "report",format = "xml"
The handler can check whether format was captured to determine the
response content type.
Nested Groups
Groups can contain other groups for multi-level optional paths:
router.add(method::get, "/archive{/:year{/:month{/:day}}}", list_archive);
This matches:
-
/archive(list all) -
/archive/2025(list year) -
/archive/2025/02(list month) -
/archive/2025/02/01(list day)
Each level of specificity provides more parameters.
Escaping Special Characters
The characters :, *, {, }, and \ have special meaning in
patterns. To match them literally, prefix with backslash:
router.add(method::get, "/config\\:main", show_config);
This matches the literal path /config:main. Without the backslash,
:main would be interpreted as a parameter.
Other escape examples:
-
/path\\*starmatches/path*star -
/path\\{brace\\}matches/path{brace} -
/path\\\\slashmatches/path\slash
Quoted Parameter Names
Standard parameter names follow identifier rules: they start with a letter, underscore, or dollar sign, followed by letters, digits, underscores, or dollar signs.
For parameter names containing spaces or other characters, use quotes:
router.add(method::get, "/query/:\"search term\"", search);
Quoted names can contain any characters except unescaped quotes. Escape quotes inside the name with backslash:
router.add(method::get, "/say/:\"greeting \\\"message\\\"\"", greet);
Grammar Reference
The pattern syntax follows this grammar:
path = *token
token = text / param / wildcard / group
text = 1*( char / escaped ) ; literal characters
param = ":" name ; named parameter
wildcard = "*" name ; wildcard parameter
group = "{" *token "}" ; optional group
name = identifier / quoted-name
identifier = id-start *id-continue
id-start = "$" / "_" / ALPHA
id-continue= "$" / "_" / ALNUM
quoted-name= DQUOTE 1*quoted-char DQUOTE
quoted-char= escaped / %x20-21 / %x23-5B / %x5D-7E ; not " or \
escaped = "\" CHAR ; any escaped character
char = <any character except special>
special = "{" / "}" / "(" / ")" / "[" / "]" / "+" / "?" / "!" / ":" / "*" / "\"
The characters ( ) [ ] + ? ! are reserved. Patterns containing these
characters unescaped produce a parse error.
Matching Behavior
Understanding how the router matches paths helps write correct patterns.
How Parameters Match
A parameter captures characters until it encounters:
-
The next literal character in the pattern
-
A
/character -
The end of the path
Given the pattern /:a-:b, matching against /hello-world:
-
:astarts capturing ath -
:acaptures until it sees-(the next literal) -
:acaptureshello -
Literal
-matches- -
:bcapturesworldto end of path
How Wildcards Match
A wildcard captures from its position to the end of the path, including
all / characters. There can be only one wildcard per pattern, and it
must appear at the end.
How Groups Match
Groups attempt to match their contents. If successful, any parameters inside are captured. If unsuccessful, the group is skipped and matching continues after the group.
For /api{/v:version} matching /api/v2/users:
-
/apimatches/api -
Group attempts:
/vmatches/v,:versioncaptures2 -
Group succeeded
-
Pattern ends, but path has
/usersremaining
This pattern requires end = false (prefix matching) or an additional
wildcard to match the full path.
Router Options
Three options affect how patterns match paths.
Case Sensitivity
By default, matching is case-insensitive. The pattern /Users matches
/users, /USERS, and /Users.
router_options opts;
opts.case_sensitive(true);
basic_router<route_params> router(opts);
router.add(method::get, "/Users/:id", show_user);
With case-sensitive matching, /Users/alice matches but /users/alice
does not.
Pattern Examples
| Pattern | Path | Match | Captured Parameters |
|---|---|---|---|
|
|
Yes |
(none) |
|
|
Yes |
(none, non-strict) |
|
|
No |
|
|
|
Yes |
|
|
|
No |
|
|
|
Yes |
|
|
|
Yes |
|
|
|
No |
|
|
|
Yes |
(none) |
|
|
Yes |
|
|
|
Yes |
|
|
|
Yes |
(none) |
|
|
Yes |
|
|
|
Yes |
(none) |
|
|
Yes |
|
Error Cases
These patterns are invalid and produce parse errors:
| Pattern | Error |
|---|---|
|
Missing parameter name after |
|
Missing wildcard name after |
|
Unclosed group (missing |
|
Unexpected |
|
Reserved character |
|
Reserved character |
|
Reserved character |
|
Reserved character |
|
Reserved character |
|
Empty quoted parameter name |
|
Unterminated quoted string |
|
Trailing backslash with nothing to escape |
See Also
-
Router - request dispatch and handler registration