Medium-Scale Dancer, Part 5: REST APIs and JSON

In previous parts of this article series, I defined each route handler as a one-liner, like so:

prefix '/mf' => sub {
    get '' => \&retrieve;
    prefix '/sf' => sub {
        get  ''     => \&_get;
        post ''     => \&_post;
        put  ''     => \&_put;
        del  '/:id' => \&_del;
    };
};

This is very much on purpose, despite the fact that Dancer lets you define all of the code for each route handler within an anonymous code reference (e.g. sub { ... }) block. What do we get by moving those functions elsewhere, and what do we gain that makes it worth tolerating the ugly \& syntax?

The first benefit we get from doing that is that named subroutines make stack traces easier to read.

Second — and far more important to our purposes here in this article series — defining your routes this way makes the URL structure of the web app explicit in the code. You are more likely to notice design problems by studying the indented route definitions in the Perl code than by looking at your app's views subdirectory, even if it is structured exactly the same way as recommended in an earlier article in this series, simply because it is atypical to be looking at a full tree view of a filesystem. You end up with a kind of tunnel vision when looking at one directory at a time.

This practice of defining Dancer route handlers as one-liners solves that. It ensures that every time you adjust the route handlers for your site, you're doing so while looking at the neighboring routes; you will be defining new routes in context, not independently. This counteracts the entropic forces that result in API design drift because it implicitly encourages you to define new routes that fit logically into the scheme you defined previously. Without this extra Perl function call layer, the route definitions are visually broken up, so that you cannot see the whole design on a single screen. The resulting URL scheme can turn into a mess, as developers hack on the app over the course of years.

This is also why I have carefully lined up the blocks and fat commas in the route definitions: I want patterns in the URL scheme to jump out at me. Left-justifying everything obscures these patterns.

Now you may be asking, why does all this matter?

The reason is simple: When you make patterns in the URL scheme graphically apparent, you may discover some previously hidden practical value that was just sitting there waiting to be exploited. It works for the same reason that seeing a plot for a numeric data set for the first time sometimes creates a clear call to action.

It turns out that the example route scheme we've been defining throughout this article series includes a web API; it's just been sitting there, waiting to be discovered. With a bit of polishing, it might even turn into a proper REST API. Let's try.

Look back up at that route definition block above. What does it remind you of? If you said CRUD, go to the head of the class.

Now, HTTP verbs do not correspond directly to CRUD because HTTP was not designed to be a CRUD API. There are competing design philosophies when it comes to mapping HTTP verbs to the CRUD model of persistent data storage.

I prefer this mapping:

  DB term    | HTTP verb
+------------+----------
| C = create | POST
| R = read   | GET
| U = update | PUT
| D = delete | DELETE

The R = GET and D = DELETE rules are obvious, but it may not be clear to you why the other two rules are correct.

The HTTP spec defines POST and PUT so that they're nearly interchangeable, which is how we ended up with competing REST/CRUD API design philosophies.

Some web API designers like to use POST for both create and update operations since you can distinguish the cases based on whether the HTTP request contains a database row ID: if so, it's a request to update an existing record, else it's a record creation request.

That justification is fine in a web framework that defines the URL scheme in the file system, such as PHP, ASP, or JSP since it is normal to handle all four verbs in a single file in that sort of web framework. In Dancer, my preferred scheme works better since it defines one route per CRUD operation.

Although the HTTP spec defines PUT and POST as partially interchangeable, you must not swap the PUT and POST verbs from the way I've defined them above. The HTTP spec says PUT is idempotent, meaning that the browser is allowed to cache PUT requests, suppressing subsequent identical calls if the request parameters do not change. In CRUD terms, this means that if you define "create" in terms of PUT, the browser might not let you create two records with identical contents but different IDs, which means you risk losing data. The HTTP spec does not allow browsers to assume that identical POST calls are idempotent, however, so we use that verb for CRUD's create operation, leaving PUT = update.

That's enough design philosophy; how does all that affect our Dancer code? We might decide that since we've almost got a REST API here that we might want to push it the rest of the way.

Let's move the CRUD-like route handlers in lib/App/MajorFeature/SubFeature.pm to lib/App/API/MajorFeature/SubFeature.pm. We can also refine the programming interface a bit:

prefix '/api' => sub {
    prefix '/mf' => sub {
        prefix '/sf' => sub {
            post ''     => \&_post;
            get  ''     => \&_get;
            put  '/:id' => \&_put;
            del  '/:id' => \&_del;
        };
    };
};

My choice to use leading underscores in these module-internal route handler function names lets us parallel the Dancer DSL keyword naming scheme.

You might instead prefer to make the HTTP-to-CRUD mapping more explicit:

prefix '/api' => sub {
    prefix '/mf' => sub {
        prefix '/sf' => sub {
            post ''     => \&_create;
            get  ''     => \&_read;
            put  '/:id' => \&_update;
            del  '/:id' => \&_delete;
        };
    };
};

As in the previous article in this series, the leading underscore convention saves us some aggravation here since delete is a Perl keyword, and read is a core Perl function.

The only other thing to change here is that REST APIs normally return JSON or XML. I prefer JSON since that's directly usable by the JavaScript code that made the Ajax call to one of these APIs. One of the best features of Dancer is that automatic JSON encoding is built right in.

First, enable the feature by adding this to your config.yml file:

serializer: "JSON"

That lets us write the _create() function referenced above as:

sub _create {
    my $params = request->params;
    my $db     = get_db_conn;
    
    if ($db->create_something($params->{arg1}, $params->{arg2})) {
        return {
            success => JSON::true,
            id      => $db->last_insert_id(),
        };
    }
    else {
        return {
            success => JSON::false,
            error   => 'failed to create record: ' . $db->last_error(),
        };
    }
}

By telling Dancer that we want all routes to return JSON unless explicitly told otherwise, we can simply return Perl data structures and have Dancer translate them into JSON form for us. It will also automatically set the response's HTTP Content-Type header to application/json. Parsing and using that reply on the client side in JavaScript is therefore trivial. Many web frameworks can consume such APIs directly, such as jQuery via $.getJSON().

Doing so does mean that your existing HTML-returning route handlers have to be explicitly marked to return HTML instead:

get '/some/html/returning/route' => sub {
    send_as html => template '/some/template' => {
        some => 'parameters',
    };
};

Returning the Same Data in Both HTML and JSON Forms

You might now be asking why you would arrange things as above, where the default serializer is JSON and HTML-returning routes have to be explicitly marked. If your application returns HTML more often than JSON, this may seem backwards. Wouldn't it be simpler to leave the default serializer unset, so that template calls and such return normally, and mark the JSON-returning API route handlers explicitly instead via send_as JSON => ...?

You could indeed do that, but then you lose a nice side benefit of this arrangement: it makes it easy to define parallel route handlers for returning HTML-rendered versions of APIs that return raw JSON data.

Consider this addition to the route handlers defined above:

package App::Parts::MajorFeature::SubFeature
use Dancer2 appname => 'App';

sub _get {
    send_as html => template 'parts/mf/sf' => {
        content => App::API::MajorFeature::SubFeature::_get(),
    }
}

prefix '/parts' => sub {
    prefix '/mf' => sub {
        prefix '/sf' => sub {
            get '' => \&_get;
        };
    };
};

Briefly, what this does is define a /parts route hierarchy that is parallel to the /api hierarchy, with the former implemented in terms of the latter. That is, the application's API returns JSON, and the applications "parts" subsystem returns HTML renditions of the same data returned by the JSON API. This works because the API route handlers are defined to return raw Perl data structures, which we can consume directly from other route handlers as above, or let Dancer render them to JSON when those route handlers are called by a web client.

While you could consume these HTML fragments ("parts") via Ajax, you can also save an HTTP round trip by textually including them into complete web pages assembled by Dancer's template mechanism:

package App::MajorFeature::SubFeature
use Dancer2 appname => 'App';

sub _get {
    send_as html => template 'mf/sf' => {
        some => 'parameters',
        go   => 'here',
        part => App::Parts::MajorFeature::SubFeature::_get(),
    }
}

prefix '/mf' => sub {
    prefix '/sf' => sub {
        get '' => \&_get;
    };
};

The /views/mf/sf.tt template can then reference part to insert the HTML fragment into the larger web page, without duplicating the HTML code for that fragment.

This gives us three different ways to get the same data: as part of a complete web page (GET /mf/sf), as an HTML fragment that we can insert into an existing web page via Ajax on the browser side or textual inclusion on the Dancer side (GET /parts/mf/sf), and as raw JSON data that we could process on the browser side (GET /api/mf/sf).

This layered design pattern has two additional benefits besides avoiding violations of the DRY principle:

First, textually including an HTML rendering of the raw data on the server side as part of the initial HTTP page load saves an HTTP round trip over the common alternative design, where the server simply returns an empty HTML container which must be filled by JavaScript code with a subsequent JavaScript Ajax call. HTTP round-trips are usually expensive enough to add human-scale amounts of delay to your web app's response time, so any time you spend to remove unnecessary round-trips usually pays immediate tangible benefits.

Second, this design ensures that your web app returns an initial HTML view of the data even to clients not running JavaScript. Even if you can ensure that your normal web users will run JavaScript, you may gain some SEO benefit by returning an HTML form of your web app's dynamic content to web spiders that refuse to run embedded and referenced JavaScript code.

This article series concludes with a scheme for hot code reloading, a very useful feature in any Dancer app, but especially helpful as your app grows larger.

Author

This article has been written by Warren Young for the Perl Dancer Advent Calendar 2016.

Copyright

© 2015, 2016 by Educational Technology Resources, licensed under Creative Commons Attribution-ShareAlike 4.0