Perl API crash course

Rapid web application development using the Kelp framework

Subject

We're going to create a new web API using Modern Perl and Kelp, a micro framework.

Prerequisites

Basic understanding of imperative programming and web programming, any version of Perl installed with cpan client (preferably cpanm) and ability to install packages (either by local::lib or perlbrew), 15 minutes of free time.

The absolute minimum

We need to create a new file api.pl and fill it with just enough to make it a working Kelp application:

use Modern::Perl;
use Kelp::Less;

get '/' => sub {
        return "Hello";
};

run;

I said minimum, haven't I? This is just enough to see Hello in the browser window after running it. But first, we need to obtain the dependencies by running these commands in the system shell:

cpanm Modern::Perl
cpanm Kelp

These can actually take a while. While you're waiting, lets analyze what is actually going on in this example:

  • we use Modern::Perl to gain strictness, warnings and as much optional features as possible
  • Kelp is our framework, so we include that as well, in a simple ::Less flavor
  • get creates a new URL rule for the root path /. After the fat arrow, we can specify our function that will be ran everytime a request comes in on that URL. Anything that function returns will be printed out.
  • we call run to start our application

But why Kelp?

This guide was initially written for Dancer2 framework, however I later chose Kelp to be a slightly more straightforward option.

Both of them are among the most dense and pragmatic web frameworks out there. They may not be the coolest kids on the playground but they get the job done very well. Paired with perl's flexibility they allow for very rapid prototyping with fairly easy transition into full applications.

More popular option in the perl space is Mojolicious, which is developed with quite different principles as those two. The transition from one to another is pretty much painless, should you ever want to switch.

Server and environment

Your dependencies should be ready at this point. Lets run our development server and see how it goes:

plackup api.pl

If everything goes well, you will see an information about server accepting connections. There should be a link printed out, follow it and you should see a greeting. What you just ran is a PSGI development server that is capable of running compatible applications. Same execution method can be used for production environments and there are a couple of PSGI production-ready servers with different capabilites, like Twiggy or Starman. A regular HTTP server (like Apache) can be used to forward traffic towards the perl server.

Using JSON

To serve us as an API, our application needs to be able to encode output in some kind of transportation format. JSON is a good and straightforward solution and we'll use it in this example. Unfortunately, we won't have much to work on here, because Kelp automatically converts stuff to JSON when anything else than a scalar value is returned from its routes. Let's wrap everything in response field, to make room for some more meta-data we might want later.

What I like to do when I need to add a middleware is to actually make it a "middle"-ware. We're talking about a concept here. There are likely a couple of ways to do this, but we'll use one that can be expressed in a single perl statement:

sub api_call {
        my ($sub) = @_;
        return sub {
                return { response => $sub->(@_) };
        };
}

We also need a line that will load JSON module into framework:

module 'JSON';

This function will enclose another function inside yet another function, which will wrap anything returned by the original one in response field. If it sounds complicated that's because it probably is, but it allows us to write this:

get '/' => api_call sub {
        return { greeting => "Hello" };
};

Note how it is immediately documented that we intend this to be an api_call. Attention: to be able to skip the parens, the function needs to be declared above the get call.

After running the server again we should see a stringified JSON with the response field.

More actions and static data

For our final example I'll implement a very simple FIFO queue using two API actions. Data will be stored statically in the application. This type of data storage is not only very fragile to server crashes but will not work in every server implementation. This being said, it works on plack default development server (HTTP::Server::PSGI), so we'll use it to make things simpler.

In perl, variables declared with my keyword will be stored in the lexical scope in which the declaration occured. If we declare them outside of any block they will be static to the package they're in. Another mechanism called closure will allow functions to access all of their lexically scoped variables during their runtime. Connecting those two facts can yield the following code:

my @queue;

get '/push/:el' => api_call sub {
        my ($self, $el) = @_;
        return { status => push @queue, $el };
};

get '/pop' => api_call sub {
        return { el => shift @queue };
};

The push method contains a named argument in its route, which allows us to write urls like push/5 or push/test. Argument is retrieved from function arguments.

Run the server and test the new push and pop actions to your liking.

Making it persistent

If you're interested in making your perl scripts persistent you should have a look at ubic services on CPAN. This is icing on the cake, but worth noting to show you how the entire ecosystem comes together. Ubic is pure pleasure to work with.

Complete code and further reading

The complete final example should look something like this:

use Modern::Perl;
use Kelp::Less;

module 'JSON';

sub api_call {
        my ($sub) = @_;
        return sub {
                return { response => $sub->(@_) };
        };
}

my @queue;

get '/push/:el' => sub {
        my ($self, $el) = @_;
        return { status => push @queue, $el };
};

get '/pop' => api_call sub {
        return { el => shift @queue };
};

get '/' => api_call sub {
        return {  greeting => "Hello" };
};

run;

See https://metacpan.org/pod/Kelp for more info and manual for this framework.

Also check out Dancer2 and Mojolicious for other options for this kind of prototyping.