=head1 Writing REST web services with Catalyst::Controller::REST This article is a minor update to the 2006 entry about L, which can be found at L. =head1 What is REST REST means REpresentational State Transfer. The REST approach is using the HTTP verbs (GET, PUT, POST, DELETE) to interact with a web service, using the content type of the request to determine the format of the response and then mapping the URI to a resource. Look at this simple query: curl -X GET -H "Content-type: application/json" http://baseuri/book/1 The query asks (GET) for the book (the resource) with the id of 1, and wants the response (C) in JSON. =head1 Using Catalyst::Controller::REST The L module helps us easily create REST web services in Catalyst. =head2 HTTP Verbs First you declare a new resource: sub book : Local : ActionClass('REST') { } Then subroutines for the methods you want to handle that resource: sub book_GET { } sub book_POST { } Catalyst dispatches to the subroutine with the appropriate name. The book() subroutine will be executed each time you request the book resource, whatever the HTTP method is. For example, when you call GET on /book/, first Catalyst goes to the book subroutine, then the book_GET subroutine. If an ID is required to access the book resource, check for it in book(): sub book : Local : ActionClass('REST') { my ($self, $c, $id) = @_; if (!$id) { $self->status_bad_request($c, message => 'id is missing'); $c->detach(); } } Now if C<$id> is missing book_GET() and book_POST() will not be called. =head2 Serialization A nice feature of L is automatic serialization and deserialization. You don't need to serialize the response, or know how the data sent to you were serialized. When a client makes a request, L attempts to find the appropriate content-type for the query. It looks for: =over 4 =item B It tries to find the C of the request in the in the HTTP header. This value can be set: my $req = HTTP::Request(GET => 'http://...'); $req->header('Content-Type' => 'application/json'); =item B For GET requests, L also checks the query's C parameter: http://www.example.com/book/id?content-type=application/json This is nice, because you can do a request for a specific content-type from your browser, without changing the content-type value of the header. =item B Finally if nothing is found, L extracts content-type from Accept-Content in the HTTP request. =back =head2 HTTP Helpers L comes with helpers to generate the appropriate HTTP response to a query. When you receive a POST query and you create a new entry, you can use the C helper, to generate an HTTP response with code 201. If a request returned no record, you can use C to return a 404. =head2 Configuration L is usable without any configuration. You can also customize many of its parts. When you $self->status_ok($c, entity => {foo => 'bar'}); the content of entity will be set in the 'rest' key of the stash. You can change the name of this key: __PACKAGE__->config('stash_key' => 'my_rest_key'); Various serializations are supported: JSON, YAML, storable, XML, etc. You might want to limit your application to a subset of these formats. __PACKAGE__->config(map => { 'text/x-yaml' => 'YAML', 'application/json' => 'JSON', }); It is possible to force a default serializer. If no serializer is found for a requested content-type, this one is used: __PACKAGE__->config('default' => 'application/json'); =head1 Writing a simple controller Imagine you have a nice website with a database, and you want to provide users an easy way to access data. package BookStore::Controller::API::REST; use Moose; BEGIN { extends 'Catalyst::Controller::REST'}; sub book : Local : ActionClass('REST') { } sub book_get { my ( $self, $c, $id ) = @_; if ( !$id ) { $self->status_bad_request( $c, message => "id is missing" ); $c->detach(); } # do something clever my $book = ... $self->status_ok( $c, entity => { author => $book->author, title => $book->title } ); } sub book_POST { my ( $self, $c ) = @_; my $book_content = $c->req->data; # insert book $self->status_created( $c, location => $c->req->uri->as_string, entity => { title => $book->title, author => $book->author } ); } sub book_PUT { my ( $self, $c ) = @_; my $new_quantity = $c->req->data->{quantity}; # update quantity } sub book_DELETE { my ( $self, $c, $id ) = @_; $self->status_accepted( $c, entity => { status => "deleted" } ); } 1; =head1 SEE ALSO L L L =head1 Author Franck Cuny