back-end

$ cd examples/mojo_ajax
$ morbo app.pl
examples/mojo_ajax/app.pl
use Mojolicious::Lite;
use Mojo::JSON qw(decode_json);
use FindBin;
use DBI;
use DBIx::Simple;

get '/' => sub {
    my $c = shift;
    $c->stash( urls => [
        [ '/api/v1/greeting' => 'GET no CORS'],
        [ '/v1'              => 'HTML demo page'],
        [ '/api/v2/greeting' => 'GET woth CORS'],
        [ '/v2'              => 'HTML demo page'],

        [ '/api/v2/reverse'  => 'GET with ?text=TEXT'],

        [ '/api/v2/items'    => 'GET returning list of items'],
        [ '/api/v2/item'     => 'POST expecting {text => TEXT}'],
        [ '/api/v2/item/ID'  => 'DELETE where ID is the id number of an item'],
    ]);
    $c->render( template => 'index' );
};

get '/v1' => sub {
    my $c = shift;
    $c->render( template => 'greeting', version => 'v1' );
};

get '/v2' => sub {
    my $c = shift;
    $c->render( template => 'greeting', version => 'v2' );
};


get '/api/v1/greeting' => {
    json => {
        text => 'Hello World from Mojolicious'
    }
};

under sub {
    my $c = shift;

    # 'Access-Control-Allow-Origin' => '*';
    $c->res->headers->access_control_allow_origin('*');

    # 'Access-Control-Allow-Headers' => 'Content-Type';
    $c->res->headers->append('Access-Control-Allow-Headers' => 'Content-Type');

    # 'Access-Control-Allow-Methods' => 'POST';
    $c->res->headers->append('Access-Control-Allow-Methods' => 'GET, POST, OPTIONS, DELETE');
};

get '/api/v2/greeting' => sub {
    my $c = shift;
    $c->render( json => {
        text => 'Hello World from Mojolicious with CORS enabled'
    });
};

get '/api/v2/reverse' => sub {
    my $c = shift;
    $c->render( json => {
        text => scalar reverse $c->param('str'),
    });
};

options '/api/v2/item' => sub {
    my $c = shift;
    $c->render( json => {nothing => ''} );
};
options '/api/v2/item/:id' => {
    json => {nothing => ''}
};

post '/api/v2/item' => sub {
    my $c = shift;
    my $data = decode_json $c->req->body;
    
    get_db()->query('INSERT INTO items (text, date) VALUES (?, ?)', $data->{text}, time);
    $c->render( json => {
        text => $data->{text},
    });
};

del '/api/v2/item/:id' => sub {
    my $c = shift;
    my $id = $c->param('id');
    $c->app->log->debug("delete $id");
    my $res = get_db()->query('DELETE FROM items WHERE id=?', $id)->rows;
    $c->app->log->debug("deleted $id: $res");
    if ($res) {
        $c->render( json => {
            ok => 1
        });
    } else {
        #$c->reply->not_found;
        $c->render( json => {
            text => 'No such item',
                },
            status => 404,
        )
    }
};


get '/api/v2/items' => sub {
    my $c = shift;
    my @items = get_db()->query('SELECT * FROM items')->hashes;
    $c->render( json => {
        items => \@items,
    });
};

sub get_db {
    my $dbfile = "$FindBin::Bin/items.db";
    if (not -e $dbfile) {
        my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","");
        $dbh->do(q{CREATE TABLE items (
            id      INTEGER PRIMARY KEY,
            text    VARCHAR(255),
            date    VARCHAR(10)
        )
        });
    }
    return DBIx::Simple->connect("dbi:SQLite:dbname=$dbfile","","", {
        RaiseError => 1,
        PrintError => 0,
        AutoCommit => 1,
    });
}

app->secrets(['My very secret passphrase.']);
app->start;

__DATA__

@@ index.html.ep

<html>
 <head>
   <title>Back-end for building Single Page Applications</title>
 </head>
<body>
<h1>Back-end for building Single Page Application</h1>

<table>
  <tr><th>URL</th><th>Explanation</th></tr>
% for my $u (@$urls) {
  <tr><td><a href="<%= $u->[0] %>"><%= $u->[0] %></a></td><td><%= $u->[1] %></td></tr>
% } 
</table>
</body>
</html>

@@ greeting.html.ep
 
<html ng-app="DemoApp" ng-controller="DemoController">
  <head>
      <script src="angular.min.js"></script>
      <title><%= $version %></title>

<script>
angular.module('DemoApp', [])
.controller('DemoController', ['$scope', '$http', function($scope, $http) {
    $scope.title = 'Static Title';
    $http({
        method: 'GET',
        url: '/api/<%= $version %>/greeting'
    }).then(function(response) {
        console.log('success', response);
        $scope.greeting = response.data.text;
    }, function(response) {
        console.log('failure', response);
    });
}]);
</script>

  </head>
<body>
<h1>{{title}}</h1>
<h2>{{greeting}}</h2>
</body>
</html>