shelf_route 0.13.1 copy "shelf_route: ^0.13.1" to clipboard
shelf_route: ^0.13.1 copied to clipboard

outdatedDart 1 only

Routing middleware for Shelf

Router for Dart Shelf #

Build Status Pub Version

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.

  1. 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
  2. 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
  3. 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

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/fred
  • query 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:

  1. The core routing components such as [Route], [SimpleRoute] and [RequestRouter]. These are immutable components that perform the actual routing of requests.
  2. 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 and SimpleRouteAdapter which produce SimpleRoute
  • RouterSpec and RouterAdapter which produce RequestRouter
  • RouteSpec and RouteAdapter which are the root of the hierarchy corresponding to Route

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:

  1. fork the repo and implement your changes with good unit test coverage of your changes
  2. create a pull request and include enough detail in the description

Issues #

See open issues.

Authors #

0
likes
0
pub points
33%
popularity

Publisher

unverified uploader

Routing middleware for Shelf

Homepage

License

unknown (LICENSE)

Dependencies

matcher, option, path, shelf, shelf_path, uri

More

Packages that depend on shelf_route