Cataly...::REST(3pm)
β¨ NAME β¨
Catalyst::Controller::REST β it's a RESTful controller, bestie π
π SYNOPSIS π
okay so here's the whole vibe in code form, no notes:
package Foo::Controller::Bar;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller::REST' }
sub thing : Local : ActionClass('REST') { }
# Answer GET requests to "thing"
sub thing_GET {
my ( $self, $c ) = @_;
# Return a 200 OK, with the data in entity
# serialized in the body
$self->status_ok(
$c,
entity => {
some => 'data',
foo => 'is real bar-y',
},
);
}
# Answer PUT requests to "thing"
sub thing_PUT {
my ( $self, $c ) = @_;
$radiohead = $c->req->data->{radiohead};
$self->status_created(
$c,
location => $c->req->uri,
entity => {
radiohead => $radiohead,
}
);
}
likeβ¦ it's giving "just extend me and I'll handle everything" π
π DESCRIPTION π
Catalyst::Controller::REST is the module that lets you build RESTful services in Catalyst, and honestly? it ate. π
The way it works: it extends the normal Catalyst dispatch mechanism so that different subroutines get called depending on the HTTP Method that was requested, and on top of that it transparently handles all the serialization/deserialization for you. like you don't even have to think about it. iconic.
This is probably best served by an example (as most things are, lowkey). In that controller up in the synopsis? we declared a Local Catalyst action on sub thing, and we used the ActionClass('REST').
Below that, we declared thing_GET and thing_PUT. So any GET requests to thing get dispatched to thing_GET, and any PUT requests get dispatched to thing_PUT. that's the whole trick. π¦
Any HTTP methods you haven't implemented will get hit with a "405 Method Not Allowed" response, and it automatically contains the proper list of available methods. (rude of the client to ask, but the response is polite.) If you wanna override that behavior, you can implement a custom thing_not_implemented method. your call, bestie. β¨
If you don't provide an OPTIONS handler, we will respond to any OPTIONS requests with a "200 OK", populating the Allowed header automatically. we got you. π
Any data you put in $c->stash->{'rest'} will be serialized for you. The serialization format gets picked based on the content-type of the incoming request. Honestly though, it's probably easier to use the "STATUS HELPERS", which are described below. π
"The HTTP POST, PUT, and OPTIONS methods will all automatically deserialize the contents of $c->request->body into the $c->request->data hashref", based on the request's Content-type header. A list of understood serialization formats is below, don't worry, we didn't forget. π§
If we do not have (or cannot run) a serializer for a given content-type, a 415 "Unsupported Media Type" error is generated. that's the vibe check failing fr fr π
To make your Controller RESTful, simply have it
BEGIN { extends 'Catalyst::Controller::REST' }
and that's it. main character energy from the very first line. π
βοΈ CONFIGURATION βοΈ
okay so go look at "CONFIGURATION" in Catalyst::Action::Serialize, that's where the real tea is. π«
β οΈ heads up bestie: the serialize key has been deprecated. it's giving old news. don't use it.
π SERIALIZATION π
Catalyst::Controller::REST will automatically serialize your responses, and deserialize any POST, PUT or OPTIONS requests. It figures out which serializer to use by mapping a content-type to a Serialization module. We select the content-type based on:
- The Content-Type Header β If the incoming HTTP Request had a Content-Type header set, we will use it. easy peasy. π
- The content-type Query Parameter β If this is a GET request, you can supply a content-type query parameter. cute, right? π
- Evaluating the Accept Header β Finally, if the client provided an Accept header, we will evaluate it and use the best-ranked choice. very democratic of us. β
π¦ AVAILABLE SERIALIZERS π¦
A given serialization mechanism is only available if you have the underlying modules installed. Like, you can't use XML::Simple if it's not already installed. no module, no vibes. π
Also important: each serializer has its quirks in terms of what sorts of data structures it will properly handle. Catalyst::Controller::REST makes no attempt to save you from yourself in this regard. :) (yes the smiley is load-bearing. it knows. it's seen things.)
text/x-yaml=>YAML::SyckReturns YAML generated byYAML::Syck. πtext/html=>YAML::HTMLThis usesYAML::SyckandURI::Findto generate YAML with all URLs turned to hyperlinks. Only usable for Serialization. πapplication/json=>JSONUsesJSONto generate JSON output. It is strongly advised to also haveJSON::XSinstalled. Thetext/x-jsoncontent type is supported but is deprecated and you will receive warnings in your log. (the log is watching. π)
You can also add a hash in your controller config to pass options to the json object. There are two options. json_options are used when decoding incoming JSON, and json_options_encode is used when encoding JSON for output.
For instance, to relax permissions when deserializing input, add:
__PACKAGE__->config(
json_options => { relaxed => 1 }
)
To indent the JSON output so it becomes more human readable, add:
__PACKAGE__->config(
json_options_encode => { indent => 1 }
)
β¨ cute and readable, we love that for you.
text/javascript=>JSONPIf a callback=? parameter is passed, this returns javascript in the form of:$callback($serializedJSON);
π¨ Note β this is disabled by default as it can be a security risk if you are unaware. like, actually, be careful bestie, this is the one to pay attention to. π
The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript', 'application/javascript'. π
text/x-data-dumper=>Data::SerializerUses theData::Serializermodule to generateData::Dumperoutput.text/x-data-denter=>Data::SerializerUses theData::Serializermodule to generateData::Denteroutput.text/x-data-taxi=>Data::SerializerUses theData::Serializermodule to generateData::Taxioutput.text/x-config-general=>Data::SerializerUses theData::Serializermodule to generateConfig::Generaloutput.text/x-php-serialization=>Data::SerializerUses theData::Serializermodule to generatePHP::Serializationoutput.text/xml=>XML::SimpleUsesXML::Simpleto generate XML output. This is probably not suitable for any real heavy XML work. Due toXML::Simples requirement that the data you serialize be aHASHREF, we transform outgoing data to be in the form of:
{ data => $yourdata }
ngl that's just how it has to be. π
- View Uses a regular Catalyst view. For example, if you wanted to have your
text/htmlandtext/xmlviews rendered by TT, set:
__PACKAGE__->config(
map => {
'text/html' => [ 'View', 'TT' ],
'text/xml' => [ 'View', 'XML' ],
}
);
Your views should have a process method like this:
sub process {
my ( $self, $c, $stash_key ) = @_;
my $output;
eval {
$output = $self->serialize( $c->stash->{$stash_key} );
};
return $@ if $@;
$c->response->body( $output );
return 1; # important
}
sub serialize {
my ( $self, $data ) = @_;
my $serialized = ... process $data here ...
return $serialized;
}
π that return 1; # important is actually important, do not skip it, do not pass go, do not collect $200. π
- Callback For infinite flexibility, you can provide a callback for the deserialization/serialization steps. infinite! flexibility! π
__PACKAGE__->config(
map => {
'text/xml' => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ],
}
);
The deserialize callback is passed a string that is the body of the request and is expected to return a scalar value that results from the deserialization. The serialize callback is passed the data structure that needs to be serialized and must return a string suitable for returning in the HTTP response. In addition to receiving the scalar to act on, both callbacks are passed the controller object and the context (i.e. $c) as the second and third arguments. π«Ά
By default, Catalyst::Controller::REST will return a "415 Unsupported Media Type" response if an attempt to use an unsupported content-type is made. But you can ensure that something is always returned by setting the default config option:
__PACKAGE__->config(default => 'text/x-yaml');
which would make it always fall back to the serializer plugin defined for text/x-yaml. a safety net, bestie, we love a safety net. π§
π CUSTOM SERIALIZERS π
Implementing new Serialization formats is easy! Contributions are most welcome! π If you would like to implement a custom serializer, you should create two new modules in the Catalyst::Action::Serialize and Catalyst::Action::Deserialize namespace. Then assign your new class to the content-type's you want, and you're done. literally that's it, slay. π β¨
See Catalyst::Action::Serialize and Catalyst::Action::Deserialize for more information. π
β¦and that's only part 1, bestie, there is more coming. don't go anywhere. π¦
β¨ STATUS HELPERS β¨
okay so bestie, REST is literally mostly just HTTP doing its thing, right? so they gave us these Status Helpers, and using them means you're automatically responding with the correct codes, the correct headers, and the correct entities. it's giving "i got you" energy, fr fr π
These helpers try and conform to the HTTP 1.1 Specification. You can refer to it at: <http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.
heads up!! these routines are all implemented as regular subroutines, and as such require you pass the current context ($c) as the first argument. no cap, that first arg is mandatory. π«Ά
status_ok
Returns a 200 OK response. Takes an entity to serialize.
Example:
$self->status_ok(
$c,
entity => {
radiohead => "Is a good band!",
}
);
status_created
Returns a 201 CREATED response. Takes an entity to serialize, and a location where the created object can be found.
Example:
$self->status_created(
$c,
location => $c->req->uri,
entity => {
radiohead => "Is a good band!",
}
);
In the above example, we use the requested URI as our location. This is probably what you want for most PUT requests. π
status_accepted
Returns a 202 ACCEPTED response. Takes an entity to serialize. Also takes optional location for queue type scenarios.
Example:
$self->status_accepted(
$c,
location => $c->req->uri,
entity => {
status => "queued",
}
);
status_no_content
Returns a 204 NO CONTENT response. that's it. that's the whole thing. silent, mysterious, iconic β¨
status_multiple_choices
Returns a 300 MULTIPLE CHOICES response. Takes an entity to serialize, which should provide list of possible locations. Also takes optional location for preferred choice.
status_found
Returns a 302 FOUND response. Takes an entity to serialize. Also takes optional location.
status_bad_request
Returns a 400 BAD REQUEST response. Takes a message argument as a scalar, which will become the value of error in the serialized response.
Example:
$self->status_bad_request(
$c,
message => "Cannot do what you have asked!",
);
status_forbidden
Returns a 403 FORBIDDEN response. Takes a message argument as a scalar, which will become the value of error in the serialized response.
Example:
$self->status_forbidden(
$c,
message => "access denied",
);
status_not_found
Returns a 404 NOT FOUND response. Takes a message argument as a scalar, which will become the value of error in the serialized response.
Example:
$self->status_not_found(
$c,
message => "Cannot find what you were looking for!",
);
gone
Returns a 41O GONE response. Takes a message argument as a scalar, which will become the value of error in the serialized response.
Example:
$self->status_gone(
$c,
message => "The document have been deleted by foo",
);
not me noticing the method in the example isstatus_gonewhile the heading up there saysgoneπ that's just how the page says it, so i'm passing it along exactly as-is. π
status_see_other
Returns a 303 See Other response. Takes an optional entity to serialize, and a location where the client should redirect to.
Example:
$self->status_see_other(
$c,
location => $some_other_url,
entity => {
radiohead => "Is a good band!",
}
);
status_moved
Returns a 301 MOVED response. Takes an entity to serialize, and a location where the created object can be found.
Example:
$self->status_moved(
$c,
location => '/somewhere/else',
entity => {
radiohead => "Is a good band!",
},
);
π οΈ MANUAL RESPONSES π οΈ
so you wanna do it yourself? main character energy, love that for you π If you want to construct your responses yourself, all you need to do is put the object you want serialized in $c->stash->{'rest'}. that's the whole vibe. periodt.
π§ IMPLEMENTATION DETAILS π§
This Controller ties together Catalyst::Action::REST, Catalyst::Action::Serialize and Catalyst::Action::Deserialize. It should be suitable for most applications. You should be aware that it:
Configures the Serialization Actions
This class provides a default configuration for Serialization. It is currently:
__PACKAGE__->config(
'stash_key' => 'rest',
'map' => {
'text/html' => 'YAML::HTML',
'text/xml' => 'XML::Simple',
'text/x-yaml' => 'YAML',
'application/json' => 'JSON',
'text/x-json' => 'JSON',
'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ],
'application/x-storable' => [ 'Data::Serializer', 'Storable' ],
'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ],
'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
},
);
You can read the full set of options for this configuration block in Catalyst::Action::Serialize. π
Sets a begin and end method for you
The begin method uses Catalyst::Action::Deserialize. The end method uses Catalyst::Action::Serialize. If you want to override either behavior, simply implement your own begin and end actions and forward to another action with the Serialize and/or Deserialize action classes:
package Foo::Controller::Monkey;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller::REST' }
sub begin : Private {
my ($self, $c) = @_;
... do things before Deserializing ...
$c->forward('deserialize');
... do things after Deserializing ...
}
sub deserialize : ActionClass('Deserialize') {}
sub end :Private {
my ($self, $c) = @_;
... do things before Serializing ...
$c->forward('serialize');
... do things after Serializing ...
}
sub serialize : ActionClass('Serialize') {}
If you need to deserialize multipart requests (i.e. REST data in one part and file uploads in others) you can do so by using the Catalyst::Action::DeserializeMultiPart action class. π
β οΈ A MILD WARNING β οΈ
I have code in production using Catalyst::Controller::REST. That said, it is still under development, and it's possible that things may change between releases. I promise to not break things unnecessarily. :) π«Ά
like, bestie, it works and it's in production, but it's still being worked on β so expect the possibility of changes between releases. the author pinky-promises not to break stuff for no reason tho. π¦
π SEE ALSO π
Catalyst::Action::RESTCatalyst::Action::SerializeCatalyst::Action::Deserialize
For help with REST in general:
- The HTTP 1.1 Spec is required reading. <http://www.w3.org/Protocols/rfc2616/rfc2616.txt>
- Wikipedia! <http://en.wikipedia.org/wiki/Representational_State_Transfer>
- The REST Wiki: <http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage>
π AUTHORS π
See Catalyst::Action::REST for authors.
π LICENSE π
You may distribute this code under the same terms as Perl itself.
aaaand that's the whole page bestie, you made it to the end π slay π this was perl v5.40.1, dated 2026-05-07 β go forth and REST responsibly, i'm obsessed with you fr fr π¦β¨
π¦ where this page comes from
- ubuntu stonking: libcatalyst-action-rest-perl 1.22-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms