shelf_route 0.2.0 shelf_route: ^0.2.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 start with a :
. 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'];
Currently they are added to the request headers with a prefix of shelf.route.
. So, whilst this is discouraged as it will change in the future, you can also get the value as follows
request.headers['shelf.route.accountNumber']
Note that once shelf supports extra parameters on messages Shelf Route will switch over to using them and deprecate the use of headers.
This will be seemless to anyone that uses getPathVariables
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
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 ${request.headers['shelf.route.accountNumber']}");
}
shelf.Response _deposit(shelf.Request request) {
return new shelf.Response.ok(
"Deposit into account for acct num ${request.headers['shelf.route.accountNumber']}");
}
shelf.Response _searchAccounts(shelf.Request request) {
return new shelf.Response.ok("Search accounts");
}