Catalyst Archives

Reinvent the REST

The other day I was working on yet another side project. Almost immediately I got side tracked. Does that make the new project a side side project?

What shiny bobble or cool new tech got my attention you ask? It wasn't $buzzword, or $fancy_tech. Nawe, nothing that resumé worthy. Instead, I decided to see what a MVP for a REST API would look like in using Catalyst. Look ma, no extra modules! Except a JSON one, but I don't count it since Catalyst also uses a JSON module.

NOTE: If you are trying to be productive, you probably want to just use Catalyst::Action::REST instead of rolling your own. That's what I've used at work with great success.

Other than generating the scripts I went ahead and hand wrote out everything else and was pleasantly surprised. There was so little code that I went ahead and retyped the only three modules needed here. Apologies if there's a typo.

Onward to the MVP!

Right off the bat, I was worried about what all would be needed to parse the incoming data. Instead I happily found out that Catalyst out of the box can parse JSON posts. I've been writing Catalyst code for like, I dunno 12 years. Even now, I'll sometimes come across some nice feature, or well thought out bit that just shows you how much effort, and smarts the Catalyst people put into designing a good framework. This time it was data handlers. The example the documentation gave is a great starting point. I think it'd be pretty simple to add in data handlers for XML, or YAML if needed.

The main library is all standard boilerplate.

  • rest_mvp/lib/api.pm

    package api;  
    use Moose;  
    use namespace::autoclean;  
    use Catalyst::Runtime 5.90;
    
    
    extends 'Catalyst';
    
    
    our $VERSION = '0.01';
    
    
    __PACKAGE__->setup;  
    __PACKAGE__->meta->make_immutable;
    
    
    1;
    

The only thing that I really had to decide on was how to pass the data to the View. Since this is a quick prototype project, I just followed a convention I'd seen before and used rest as the stash key.

As a proof of concept I created 2 subs. One that accepts get requests at /api/server_time. As the name suggests it returns the server time. The other accepts post requests at /api/cap. It regurgitates whatever was sent in, or a crash if nothing. :)

  • api/lib/api/Controller/api.pm

    package rest_mvp::Controller::api;  
    use Moose;  
    use namespace::autoclean;  
    
    
    BEGIN { extends 'Catalyst::Controller' };
    
    
    sub cap :Local :POST  {  
        my ( $self, $c )  = @_;  
        $c->stash->{rest} = $c->req->body_data;  
    }
    
    
    sub server_time :Local :GET {  
        my ( $self, $c )  = @_;  
        $c->stash->{rest}  = { now => '' . localtime };  
    }
    
    
    sub end : ActionClass('RenderView') { }
    
    
    __PACKAGE__->meta->make_immutable;
    
    
    1;
    

Catalyst has sensible defaults. As long as I also do something sensible, the view sensibly works.

  • rest_mvp/lib/api/View/json.pm

    package api::View::json;  
    use Moose;  
    use namespace::autoclean;  
    use Cpanel::JSON::XS;  
    
    
    BEGIN { extends 'Catalyst::View' };
    
    
    sub process {  
        my ($self, $c) = @_;  
        $c->res->output( encode_json( $c->stash->{rest} ) );  
    }
    
    
    __PACKAGE__->meta->make_immutable;
    
    
    1;
    

Wrap Up

Anywho, the above code works great as a quick proof of concept. Don't expect error handling tho. Feel free to play around with it. The github repo has an explanation on how to run it.

About J

user-pic I've been Programming Perl for several centuries now. Mostly focused on data processing, web, API's, and finding the Perl code of immortality.