TL;DR

Enabling content negotiation in Mojolicious is extremely easy.

A couple of years ago I wrote about ordeal, a simple web application to help invent stories. The code is available, of course.

The web application generates HTML pages, which are useful for immediate consumption. I was playing with the idea to transform it into a webservice, i.e. serve JSON answers for programs.

This is the whole modification I had to introduce:

diff --git a/app b/app
index 7915482..abecba8 100755
--- a/app
+++ b/app
@@ -55,10 +55,13 @@ get '/e' => sub ($c) {
    return $err
      ? $c->redirect_to(
       $c->url_for('emod')->query(expression => $expr, error => 1))
-     : $c->render(
-      template   => 'expression',
-      cards      => \@cards,
-      expression => $expr,
+     : $c->respond_to(
+         html => {
+            template   => 'expression',
+            cards      => \@cards,
+            expression => $expr,
+         },
+         json => {json => {cards => \@cards, expression => $expr}},
      );
 };

So, instead of the previous call to render:

$c->render(
   template   => 'expression',
   cards      => \@cards,
   expression => $expr,
);

we now have a call to respond_to, providing the alternatives to use in render depending on the specific request from the client:

$c->respond_to(
   html => {
      template   => 'expression',
      cards      => \@cards,
      expression => $expr,
   },
   json => {json => {cards => \@cards, expression => $expr}},
);

The html alternative is the same as in the previous call, to preserve the interface for humans. The json alternative takes care to pack a hash with the cards and the expression as a JSON file. The first json is for selection by content negotiation, the second one is for render to figure out that JSON encoding is required.

While more standard content negotiation usually relies on HTTP headers (e.g. see Content negotiation as explained in MDN), Mojolicious makes it easier to the API consumer by also allowing a format parameter in the POST or GET request, like this:

$ curl 'https://ordeal.introm.it/e?expression=d6g@1&format=json'
{"cards":[{"id":"d6_green_1_svg","url":"cards\/d6-green\/1.svg"}],"expression":"d6g@1"}

Very good!

For all the details, look in the detailed documentation.