NAME

Router::Resource - Build REST-inspired routing tables

SYNOPSIS

use Router::Resource;
use Plack::Builder;
use namespace::autoclean;

sub app {
    # Create a routing table.
    my $router = router {
        resource '/' => sub {
            GET  { $template->render('home') };
        };

        resource '/blog/{year}/{month}' => sub {
            GET  { [200, [], [ $template->render({ posts => \@posts }) ] };
            POST { push @posts, new_post(shift); [200, [], ['ok']] };
        };
    };

    # Build the Plack app to use it.
    builder {
        sub { $router->dispatch(shift) };
    };
}

DESCRIPTION

There are a bunch of path routers on CPAN, but they tend not to be very RESTy. A basic idea of a RESTful API is that URIs point to resources and the standard HTTP methods indicate the actions to be taken on those resources. So to encourage you to think about it that way, Router::Resource requires that you declare resources and then the HTTP methods that are implemented for those resources.

The rules for matching paths are defined by Router::Simple's routing rules, which offer quite a lot of flexibility.

INTERFACE

You create a router in a router block. Within that block, define resources understood by the router with the resource keyword, which takes a resource path and a block defining its interface:

my $router = {
    resource '/'    => sub { [[200, [], ['ok']] };
    resource '/foo' => sub { [[200, [], ['ok']] };
};

Within a resource block, declare the HTTP methods that the resource responds to by using one or more of the following keywords:

GET
POST
PUT
DELETE
OPTIONS
TRACE
CONNECT
PATCH

Note that if you define a GET method but not a HEAD method, the GET method will respond to HEAD requests.

These methods should expect two arguments: the matched request (generally a PSGI $env hash) and a hash of the matched data as created by Router::Simple. For example, in a Plack-powered Wiki app you might do something like this:

resource '/wiki/{name}' => sub {
    GET {
        my $req    = Plack::Request->new(shift);
        my $params = shift;
        my $wiki   = Wiki->lookup( $params->{name} );
        my $res    = $req->new_response;
        $res->content_type('text/html; charset=UTF-8');
        $res->body($wiki);
        return $res->finalize;
    };
};

But of course you can abstract that into a controller or other code that the HTTP method simply dispatches to.

If you wish the router to create an OPTIONS handler for you, pass the auto_options parameter to router:

$router = router {
    resource '/blog/{year}/{month}' => sub {
        GET  { [200, [], [ $template->render({ posts => \@posts }) ] };
        POST { push @posts, new_post(shift); [200, [], ['ok']] };
    };
} auto_options => 1;

With auto_options enabled, Router::Resource will look at the methods defined for a resource to define the OPTIONS handler. In this example, $router's OPTIONS method will specify that GET, HEAD, and OPTIONS are valid for /blog/{year}/{month}.

Dispatching

Use the dispatch method to have the router dispatch HTTP requests. For a Plack app, it looks something like this:

sub { $router->dispatch(shift) };

The assumption is that the methods you've defined will return a PSGI-compatible array reference. When the router finds no matching resource or method, such an array is precisely what it will return. When a resource cannot be found, it will return

[404, [], ['not found']]

If the resource is found but the requested method is not defined, it returns something like:

[405, [Allow => 'GET, HEAD'], ['not allowed']]

The "Allow" header will list the methods that the requested resource does respond to.

Of course you may not want something so simple for your app. So use the missing keyword to specify a code block to handle this situation. The code block should expect two arguments: the unmatched request $env hash and a hash describing the failure. For an unfound resource, that hash will contain:

{ code => 404, message => 'not found', headers => [] }

If a resource was found but it does not define the requested method, the hash will look something like this:

{ code => 405, message => 'not allowed', headers => [Allow => 'GET, HEAD'] }

This is designed to make it relatively easy to create a custom response to unfound resources and missing methods. Something like:

missing {
    my $req    = Plack::Request->new(shift);
    my $params = shift;
    my $res    = $req->new_response($params->{code});
    $res->headers(@{ $params->{headers} });
    $res->content_type('text/html; charset=UTF-8');
    $res->body($template->show('not_found', $params));
    return $res->finalize;
};

SEE ALSO

ACKNOWLEDGEMENTS

Thanks to the denizens of #plack for their feedback and advice on this module, including:

  • Hans Dieter Pearcey (confound)

  • Florian Ragwitz (rafl)

  • Paul Evans (LeoNerd)

  • Matt S Trout (mst)

  • Tatsuhiko Miyagawa (miyagawa)

  • Pedro Melo (melo)

AUTHOR

David E. Wheeler <david@kineticode.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2011 by David E. Wheeler.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.