shelf_route 0.13.1 shelf_route: ^0.13.1 copied to clipboard
Routing middleware for Shelf
Router for Dart Shelf #
Introduction #
Provides Shelf middleware for defining routes.
shelf_route
is a powerful router that makes it easy to define routes in a modular way.
shelf_route
is designed to be:
- easy to use out of the box
- simple to customise
- highly extensible to make it easy for component authors to create new routing apis and incorporate routing into their components.
This makes it very versatile and lets you mix and match it with your other favourite Shelf middleware components.
###Routing Choices###
There are a number of choices for routing in the shelf
world. Here is a simple guide to help you choose between a few of them.
shelf_route
. Good choice if:- you want a powerful router with a fluent api
- you don't want to use mirrors or annotations
- you prefer a bit more boilerplate over any magic that comes with mirrors
- shelf_rest. Good choice if:
- you want all the features of
shelf_route
plus - you are happy to use annotations (supported by mirrors) to significantly reduce boilerplate
- you like consistency in your REST APIs and like support to help with that
- you want all the features of
- mojito. Good choice if:
- you want all the features of shelf_rest plus
- you want a light framework that provides a fluent api on many other shelf components for things like:
- authentication & authorisation;
- serving static resources via the filesystem or via pub serve;
- oauth;
- logging and more
- authentication & authorisation;
In short, if you want to build your own stack then shelf_route
and shelf_rest
will likely suit you better. If you want a more fully featured framework, whilst still being highly extensible, then mojito
is the better option.
Using #
Basics #
You create a router using the router
function
var myRouter = router();
Use the router
's get
method to add a route using the GET Http method
myRouter.get('/', (_) => new Response.ok("Hello World");
Use the router
's handler
property to obtain a Shelf Handler
var handler = myRouter.handler;
Now you can serve up your routes with Shelf IO
io.serve(handler, 'localhost', 8080);
So the complete hello world looks like
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_route/shelf_route.dart';
void main() {
var myRouter = router()
..get('/', (_) => new Response.ok("Hello World"));
io.serve(myRouter.handler, 'localhost', 8080);
}
Http Methods #
It supports all the standard http methods
myRouter..get('/', (_) => new Response.ok("Hello World"))
..post('/', (_) => new Response.ok("Hello World"))
..put('/', (_) => new Response.ok("Hello World"))
..delete('/', (_) => new Response.ok("Hello World"));
You can specify several methods by using add
myRouter.add('/', ['GET', 'PUT'], (_) => new Response.ok("Hello World"));
Path Parameters #
Shelf Route uses UriPattern to define the paths to match on for each route. This means you can use whatever format for the paths that you like as long as it implements this interface.
By default it uses UriTemplate which implements the powerful standard of the same name.
UriTemplate allows binding to both:
path segments
like /greeting/fredquery parameters
like /greeting?name=fred
It uses {parameter name}
notation to denote path parameters.
myRouter.get('/{name}', (request) =>
new Response.ok("Hello ${getPathParameter(request, 'name')}"));
Path parameters are fetched via Shelf Path's getPathParameter
function.
Similarly you can also bind to query parameters
myRouter.get('/{name}{?age}', myHandler);
myHandler(request) {
var name = getPathParameter(request, 'name');
var age = getPathParameter(request, 'age');
return new Response.ok("Hello $name of age $age");
}
Hierarchical Routers #
To improve modularity you can break up your routes into a series of nested routes.
You add child routes using the addAll
method.
For example you can add a child router for all routes starting with /banking
var rootRouter =
router()..addAll((Router r) => r
..addAll((Router r) => r
..get('/', fetchAccountHandler)
..post('/deposit', makeDepositHandler),
path: '/account/{accountNumber}'),
path: '/banking');
Then serve up all the routes via the rootRouter
io.serve(rootRouter.handler, 'localhost', 8080)
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
Route Specific Middleware #
You can add additional middleware to individual routes
myRouter.get('/', (_) => new Response.ok("Hello World"), middleware: logRequests());
This middleware will be applied to all requests on that route.
If you add it to a child router it will apply to all routes for that router
var bankingRouter = rootRouter.addAll((Router r) {...},
path: '/banking', middleware: logRequests()),
will apply to all banking routes and all sub routes of '/banking'
.
Grouping Routes in Classes #
The Router's
addAll
method takes a typedef
that looks like
typedef RouteableFunction(Router router);
Thanks to Darts function emulator capability, this means you can easily group a set of routes together in a class.
class MyGroup {
void call(Router router) {
router..get('/', (_) => new Response.ok("Hello World"))
..get('/greeting/{name}', (request) =>
new Response.ok("Hello ${getPathParameter(request, 'name')}"));
}
}
To make that a little more explicit you can extend the Routeable
class, which simply lets you call the method createRoutes
rather than call
.
And since you now have a class you may as well spin off the handlers into methods
class MyGroup extends Routeable {
void createRoutes(Router router) {
router..get('/', helloWorld)
..get('/greeting/{name}', greeting);
}
Response helloWorld(request) =>
new Response.ok("Hello World"))
Response greeting(request) =>
new Response.ok("Hello ${getPathParameter(request, 'name')}"));
}
Then add it to another router
rootRouter.addAll(new MyGroup());
Printing Routes #
It's easy to see all the routes defined for a router using the printRoutes
function.
var router = r.router()
..get('/', (_) => new Response.ok("Hello World"))
..post('/', (_) => new Response.ok("Hello World"))
..get('/greeting/{name}{?age}', (request) {
var name = getPathParameter(request, 'name');
var age = getPathParameter(request, 'age');
return new Response.ok("Hello $name of age $age");
});
printRoutes(router);
prints
GET -> /
POST -> /
GET -> /greeting/{name}{?age}
Examples #
See more detailed examples in the example
folder under the project source.
Customising #
This section deals with basic customisations.
These are powerful ways to customise how the routing works and will handle most cases for customising shelf_route
.
If you need more then see the section below on Extending
Custom Path Formats #
The path
arguments of all the router methods accept either:
- a String or
- a UriPattern
By default 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.
var accountPattern = new UriParser(new UriTemplate('/account/{accountNumber}'));
You can now use this when you define a route and on the client.
myRouter.get(accountPattern, (_) => new Reponse.ok("Hello World"));
Installing a Custom Path Adapter
To make it more seamless to use your own path style you can install a path adapter into the router. This will be used by all routes in this router and any child routers unless you override it somewhere.
Install the adapter by passing it to the router
function.
var colonStyleAdapter = ....; // obtain the adapter from somewhere
var myRouter = router(pathAdapter: colonStyleAdapter);
Now you can use colon style path parameters
myRouter.get('/:name', (request) =>
new Response.ok("Hello ${getPathParameter(request, 'name')}"));
Custom Handler Adapters #
You can install a custom handler adapter, which allows you to transform the handlers passed into the Router
's methods. This allows for more seamless integration with other Shelf packages.
For example if you want to use ordinary Dart functions as handlers you can use a package like Shelf Bind. Shelf Bind provides such an adapter out of the box.
Install the adapter by passing it to the router
function.
import 'package:shelf_bind/shelf_bind.dart' as bind;
var myRouter = router(handlerAdapter: bind.handlerAdapter())
Now you can do
myRouter.get('/{name}', (name) => "Hello ${name}");
instead of
myRouter..get('/{name}', (request) =>
new Response.ok("Hello ${getPathParameter(request, 'name')}"));
Note without installing the adapter you could still call Shelf Bind's bind
method directly.
myRouter.get('/{name}', bind((name) => "Hello ${name}"));
Note: the simplest way to include shelf_bind is to simply use shelf_rest instead of shelf_route
Custom Routeable Adapters
Similarly to how a HandlerAdapter
allows you to seamlessly integrate packages that provide alternative forms of Handlers
like Shelf Bind, a RouteableAdapter
allows you to seamlessly integrate packages that support alternative representations of a RouteableFunction
.
RouteableFunction
and RouteableAdapter
are defined as follows
typedef RouteableFunction(Router router);
typedef RouteableFunction RouteableAdapter(Function handler);
Installation
You can install the adapter when you create the top level router
var myRouteableAdapter = // create some how
var myRouter = router(routeableAdapter: myRouteableAdapter)
Now you can do
myRouter.addAll(new MyResource(), path: 'mine');
and myRouteableAdapter
will be called to adapt the instance of MyResource
into a RouteableFunction
Note: as with all the adapters you can install them at any level of the routing tree and they will take effect from that point on. For example
myRouter.addAll(new MyResource(), path: 'mine', routeableAdapter: myRouteableAdapter);
Extending (Advanced Usage) #
If you can't achieve the customisations you need using the above techniques then you have come to the right place. But first helps to know a little about the architecture of shelf_route
.
Architecture #
shelf_route
is broken into two main parts:
- The core routing components such as [Route], [SimpleRoute] and [RequestRouter]. These are immutable components that perform the actual routing of requests.
- The router builder components such as [SpecBasedRouterBuilder] and
[DefaultRouterBuilder]. These are responsible for building the runtime
components (Route etc) and is what normal users interact with when using
shelf_route
.
Router Builders
Corresponding to the runtime routing components, are pairs of more abstract
models. These pairs are an abstract representation of a route, called a
route spec
, and an adapter
that is responsible for creating the corresponding
route component from the given route spec. More specifically there are:
SimpleRouteSpec
andSimpleRouteAdapter
which produceSimpleRoute
RouterSpec
andRouterAdapter
which produceRequestRouter
RouteSpec
andRouteAdapter
which are the root of the hierarchy corresponding toRoute
Note these models a deliberately very abstract to support the most flexibility
possible. However, in almost all circumstances you are more likely to deal
with subclasses like DefaultRouterAdapter
that provide more concrete
implementations. The support ways to provide Middleware
and adapt
route paths (e.g. supporting different path styles like ':foo') and handlers
(such as that provided by shelf_bind that allows normal Dart functions to
be used as shelf handlers)
The most advanced form of extensions to shelf_route typically work at this level but producing specs and adapters, either or both of which may be custom subclasses.
Note the Adapters inherit properties from parent routes. So often it is not necessary to provide an adapter at each node in the routing tree. A single one at the top of the tree may be enough.
SpecBasedRouterBuilder
, which is also a router spec, has methods
to add these specs to builder, such as addRoute
For now the best place to look to understand how to extend shelf_route
is the source code of shelf_rest.
More Information #
See the wiki for more details on all the options
Contributing #
Contributions are welcome. Please:
- fork the repo and implement your changes with good unit test coverage of your changes
- create a pull request and include enough detail in the description
Issues #
See open issues.