roux 1.0.1 copy "roux: ^1.0.1" to clipboard
roux: ^1.0.1 copied to clipboard

A Lightweight, fast router for Dart with expressive pathname syntax.

roux #

Pub Version Test

Lightweight, fast router for Dart with expressive pathname syntax.

Install #

dart pub add roux

With Flutter:

flutter pub add roux

Quick Start #

import 'package:roux/roux.dart';

void main() {
  final router = Router<String>();

  router.add('/', 'root');
  router.add('/users/all', 'usersAll');
  router.add('/users/:id', 'userDetail');
  router.add('/users/*', 'usersWildcard');
  router.add('/**', 'globalFallback');

  final match = router.find('/users/42');
  print(match?.data);   // userDetail
  print(match?.params); // {id: 42}

  final all = router.findAll('/users/all');
  print(all.map((m) => m.data)); // (globalFallback, usersWildcard, usersAll)
}

API #

final router = Router<String>(
  caseSensitive: false,
  cache: LRUCache<String>(),
);

router.add('/posts', 'posts');
router.add('/posts/:id', 'post', method: 'GET');

final match = router.find('/posts/42', method: 'GET');
final matches = router.findAll('/posts/42', method: 'GET');

router.remove('GET', '/posts/:id');

Router #

  • Router({bool caseSensitive = false, Cache<T>? cache})
  • void add(String path, T data, {String? method})
  • RouteMatch<T>? find(String path, {String? method})
  • List<RouteMatch<T>> findAll(String path, {String? method, bool includeAny = false})
  • bool remove(String method, String path)

RouteMatch #

  • data is the registered payload.
  • params is always a Map<String, String>.
  • Routes without params return an empty map.

Route Syntax #

Syntax Meaning Example params
/users/all Exact static route {}
/users/:id Named single-segment param {id: 42}
/users/* Unnamed single-segment wildcard {0: 42}
/users/**:rest Named remainder wildcard {rest: a/b}
/users/** Unnamed remainder wildcard {_: a/b}
/files/:name.:ext Embedded params in one segment {name: readme, ext: md}
/files/file-*-*.png Embedded wildcards in one segment {0: a, 1: b}
/users/:id(\\d+) Named regex param {id: 42}
/path/(\\d+) Unnamed regex group {0: 42}
/users/:id? Optional segment param {} or {id: 42}
/files/:path+ One-or-more repeated segments {path: a/b}
/assets/:rest* Zero-or-more repeated segments {} or {rest: a/b}
/foo{bar} Mandatory group suffix {}
/book{s}? Optional group suffix {}
/users{/:id}? Optional grouped suffix {} or {id: 42}
/blog/:id(\\d+){-:title}? Mixed regex, params, and groups {id: 123} or {id: 123, title: post}

Path Handling #

roux matches pathnames only.

  • Leading slash is normalized: users/:id behaves like /users/:id.
  • Trailing slash is ignored: /users and /users/ match the same route.
  • Repeated slashes are collapsed during registration and lookup.
  • . and .. segments are normalized during registration and lookup.
  • Percent-encoded bytes are matched literally and are not URI-decoded.

Examples:

final router = Router<String>();

router.add('users/:id', 'user');
router.add('/users/./profile', 'profile');
router.add('/caf%C3%A9', 'cafe');

print(router.find('/users/42')?.params);       // {id: 42}
print(router.find('/users/profile')?.data);    // profile
print(router.find('/caf%C3%A9')?.data);        // cafe
print(router.find('/café'));                   // null

Matching Rules #

For find(...), the broad precedence is:

  • static routes beat params
  • params beat wildcards
  • more specific regex or embedded param routes can beat plain params
  • method-specific routes beat method-agnostic routes in the same slot

For findAll(...), matches are returned from broad to specific.

Example:

final router = Router<String>();
router.add('/users/**', 'wildcard');
router.add('/users/:id', 'param');
router.add('/users/all', 'static');

print(router.find('/users/all')?.data); // static
print(router.findAll('/users/all').map((m) => m.data));
// (wildcard, param, static)

HTTP Method Matching #

Method names are trimmed and uppercased internally.

final router = Router<String>();
router.add('/users/:id', 'any');
router.add('/users/:id', 'get', method: ' get ');

print(router.find('/users/1')?.data);                 // any
print(router.find('/users/1', method: 'GET')?.data);  // get
print(router.find('/users/1', method: 'POST')?.data); // any

findAll prefers exact method matches and falls back to method-agnostic matches:

final router = Router<String>();
router.add('/api/:id', 'any');
router.add('/api/:id', 'get', method: 'GET');

print(router.findAll('/api/1', method: 'GET').map((m) => m.data));
// (get)
print(router.findAll('/api/1', method: 'POST').map((m) => m.data));
// (any)

Set includeAny: true to include method-agnostic matches before exact matches:

final router = Router<String>();
router.add('/api/:id', 'any');
router.add('/api/:id', 'get', method: 'GET');

print(router.findAll('/api/1', method: 'GET', includeAny: true).map((m) => m.data));
// (any, get)

Cache #

You can provide any Cache<T> implementation, or use LRUCache<T>.

final router = Router<String>(cache: LRUCache(256));
router.add('/users/:id', 'user');

final first = router.find('/users/42');
final second = router.find('/users/42');

print(identical(first, second)); // true

The cache is cleared after add(...) and remove(...).

Validation and Escapes #

Invalid param names or unclosed regex / group syntax throw FormatException.

Escaped syntax characters are treated literally:

final router = Router<String>();

router.add(r'/v\:1/users', 'versioned');
router.add(r'/files/\*', 'star-file');

print(router.find('/v:1/users')?.data); // versioned
print(router.find('/files/*')?.data);   // star-file

Example #

See example/main.dart for a runnable example.

License #

MIT. See LICENSE.

4
likes
160
points
781
downloads

Documentation

API reference

Publisher

verified publishermedz.dev

Weekly Downloads

A Lightweight, fast router for Dart with expressive pathname syntax.

Repository (GitHub)
View/report issues

Topics

#router #http #path-matching #trie

Funding

Consider supporting this project:

github.com

License

MIT (license)

More

Packages that depend on roux