shelf_route 0.5.0 shelf_route: ^0.5.0 copied to clipboard
Routing middleware for Shelf
Router for Dart Shelf #
Introduction #
Provides Shelf middleware for defining routes.
Shelf Route makes it easy to define routes in a modular way. How?
- A
Router
is simply a Shelf Middleware component. - You can define a single Router for your whole application or as many as you like in a heirarchical structure.
- When a Router passes a request to a child route it first modifies the request as follows:
- The route's
path
is removed from the start ofpathInfo
. e.g./banking/accounts
->/accounts
- The route's
path
is appended to thescriptName
. e.g./
->/banking
- That supports modularity in the child routes as the pathInfo only contains the path relative to that component.
- The route's
Using #
Basics #
First set up a little test handler that just echos the request
shelf.Response _echoRequest(shelf.Request request) {
return new shelf.Response.ok('Request for "${request.pathInfo}"');
}
To route all requests that start with /public
to this handler
var router = (route.router()
..addRoute(_echoRequest, path: '/public'))
.handler;
Then you can create a simple server passing in the router
you just created
io.serve(router, 'localhost', 8080).then((server) {
print('Serving at http://${server.address.host}:${server.port}');
});
To route only 'GET' requests
var router = (route.router()
..addRoute(_echoRequest, path: '/public', method: 'GET'))
.handler;
or better still the simpler form
var router = (route.router()
..get('/public', _echoRequest))
.handler;
Path Variables #
You can include variables in the paths that are passed to the router. Path variables are contained within {}
. For example {name}
is a path variable called name.
To route a 'GET' with a variable for the users name
var router = (route.router()
..get('/user/{name}', _echoRequest))
.handler;
To POST a new deposit into an account resource
var router = (route.router()
..post('/account/{accountNumber}/deposit', _echoRequest))
.handler;
In a future release you will be able to specify constraints like type and regex on path variables. Currently only strings are supported.
You can then access these variables from inside the handler
var pv = route.getPathVariables(request);
var accountNumber = pv['accountNumber'];
Note that the path argument is parsed as a UriTemplate so you can do more than simple {name}
style paths.
Heirarchical Routers #
To improve modularity you can break up your routes into a series of nested routes. For example you can set up a top level /banking
route
var router = route.router();
var bankingRouter = router.addChildRouter('/banking');
var handler = router.handler;
and then set up the bankingRouter
bankingRouter
..post('/account/{accountNumber}/deposit', _echoRequest)
.handler;
Note in this case the full path of the deposit resource is actually /banking/account/{accountNumber}/deposit
.
To try this out, fire up the server and do
curl -d 'lots of money' http://localhost:8080/banking/account/1235/deposit
Custom Uri Patterns #
The path
arguments of all the router methods accept either:
- a String or
- a UriPattern
A String value will be parsed into a UriParser which means it is expected to conform to UriTemplate.
You can also implement your own UriPattern and use that instead. For example you may prefer the :
style of path variables (e.g. :name
).
In addition it allows you to create uri path definitions and potentially share between client and server. e.g.
final UriPattern accountPattern = new UriParser(new UriTemplate('/account/{accountNumber}'));
You can now use this when you define a route and on the client.
More Complex Example #
The following example shows several of these features together.
Full source at example/routing_example.dart
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_route/shelf_route.dart' as route;
void main() {
var publicRoute = const shelf.Stack()
.addHandler(_echoRequest);
var router = (route.router()
..addRoute(publicRoute, path: '/public')
..addRoute(_bankingRoutes(), path: '/banking'))
.handler;
var handler = const shelf.Stack()
.addMiddleware(shelf.logRequests())
.addHandler(router);
io.serve(handler, 'localhost', 8080).then((server) {
print('Serving at http://${server.address.host}:${server.port}');
});
}
shelf.Response _echoRequest(shelf.Request request) {
return new shelf.Response.ok('Request for "${request.pathInfo}"');
}
// supports modularity with routes. i.e. here the banking section has it's own routes
// these are relative to where the main router places them
shelf.Handler _bankingRoutes() {
var transfersRoute = const shelf.Stack()
.addHandler(_echoRequest);
var bankingRouter = (route.router()
..addRoute(_accountsRoute(), path: '/account')
..addRoute(transfersRoute, path: '/transfer'))
.handler;
var bankingRoute = const shelf.Stack()
// .addMiddleware(authenticator); // obviously they would be authenticated
.addHandler(bankingRouter);
return bankingRoute;
}
// all accounts
shelf.Handler _accountsRoute() {
return (route.router()
..addRoute(_individualAccountRoute(), path: '/{accountNumber}')
..get('/', _searchAccounts))
.handler;
}
// a single account with an account number. Note accountNumber is now
// in request.extraParams
shelf.Handler _individualAccountRoute() {
return (route.router()
..get('/', _fetchAccount)
..post('/deposit', _deposit))
.handler;
}
shelf.Response _fetchAccount(shelf.Request request) {
return new shelf.Response.ok(
"Fetch account for acct num ${route.getPathVariables(request)['accountNumber']}");
}
shelf.Response _deposit(shelf.Request request) {
return new shelf.Response.ok(
"Deposit into account for acct num ${route.getPathVariables(request)['accountNumber']}");
}
shelf.Response _searchAccounts(shelf.Request request) {
return new shelf.Response.ok("Search accounts");
}
TODO #
- Add support for types in path variables
- Add support for constraints on path variables
- Add support for custom UriPatterns