NAME

Raisin - REST-like API micro-framework for Perl.

SYNOPSIS

use Raisin::API;

my %USERS = (
    1 => {
        name => 'Darth Wader',
        password => 'death',
        email => 'darth@deathstar.com',
    },
    2 => {
        name => 'Luke Skywalker',
        password => 'qwerty',
        email => 'l.skywalker@jedi.com',
    },
);

namespace '/user' => sub {
    get params => [
        #required/optional => [name, type, default, regex]
        optional => ['start', 'Raisin::Types::Integer', 0],
        optional => ['count', 'Raisin::Types::Integer', 10],
    ],
    sub {
        my $params = shift;
        my ($start, $count) = ($params->{start}, $params->{count});

        my @users
            = map { { id => $_, %{ $USERS{$_} } } }
              sort { $a <=> $b } keys %USERS;

        $start = $start > scalar @users ? scalar @users : $start;
        $count = $count > scalar @users ? scalar @users : $count;

        my @slice = @users[$start .. $count];
        { data => \@slice }
    };

    post params => [
        required => ['name', 'Raisin::Types::String'],
        required => ['password', 'Raisin::Types::String'],
        optional => ['email', 'Raisin::Types::String', undef, qr/.+\@.+/],
    ],
    sub {
        my $params = shift;

        my $id = max(keys %USERS) + 1;
        $USERS{$id} = $params;

        { success => 1 }
    };

    route_param 'id' => 'Raisin::Types::Integer',
    sub {
        get sub {
            my $params = shift;
            %USERS{ $params->{id} };
        };
    };
};

run;

DESCRIPTION

Raisin is a REST-like API micro-framework for Perl. It's designed to run on Plack, providing a simple DSL to easily develop RESTful APIs. It was inspired by Grape.

KEYWORDS

namespace

Adds a route to application.

namespace user => sub { ... };

route_param

Define a route parameter as a namespace route_param.

route_param id => 'Raisin::Types::Integer', sub { ... };

delete, get, post, put

These are shortcuts to route restricted to the corresponding HTTP method.

Each method could consists of max three parameters:

  • params

  • subroutine

  • path

Where only subroutine is required.

get sub { 'GET' };

delete 'all' => sub { 'OK' };

get params => [
    required => ['id', 'Raisin::Types::Integer'],
    optional => ['key', 'Raisin::Types::String'],
],
sub { 'GET' };

put params => [
    required => ['id', 'Raisin::Types::Integer'],
    optional => ['name', 'Raisin::Types::String'],
],
'all' => sub {
    'GET'
};

req

An alias for $self->req, this provides quick access to the Raisin::Request object for the current route.

Use req to get access to the request headers, params, etc.

use DDP;
p req->headers;
p req->params;

say req->header('X-Header');

See also Plack::Request.

res

An alias for $self->res, this provides quick access to the Raisin::Response object for the current route.

Use res to set up response parameters.

res->status(403);
res->headers(['X-Application' => 'Raisin Application']);

See also Plack::Response.

params

An alias for $self->params that gets the GET and POST parameters. When used with no arguments, it will return an array with the names of all http parameters. Otherwise, it will return the value of the requested http parameter.

Returns Hash::MultiValue object.

say req->params->{name};

session

An alias for $self->session that returns (optional) psgix.session hash. When it exists, you can retrieve and store per-session data from and to this hash.

# store param
session->{hello} = 'World!';

# read param
say session->{name};

api_version

Set an API version header.

api_version 1.23;

api_format

Loads a plugin from Raisin::Plugin::Format namespace.

Already exists Raisin::Plugin::Format::JSON and Raisin::Plugin::Format::YAML.

api_format 'JSON';

plugin

Loads a Raisin module. The module options may be specified after the module name. Compatible with Kelp modules.

plugin 'Logger' => outputs => [['Screen', min_level => 'debug']];

middleware

Loads middleware to your application.

middleware '+Plack::Middleware::Session' => { store => 'File' };
middleware '+Plack::Middleware::ContentLength';
middleware 'Runtime'; # will be loaded Plack::Middleware::Runtime

mount

Mount multiple API implementations inside another one. These don't have to be different versions, but may be components of the same API.

In RaisinApp.pm:

package RaisinApp;

use Raisin::API;

api_format 'JSON';

mount 'RaisinApp::User';
mount 'RaisinApp::Host';

1;

run, new

Creates and returns a PSGI ready subroutine, and makes the app ready for Plack.

PARAMETERS

Request parameters are available through the params hash object. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

Parameters are automatically populated from the request body on POST and PUT for form input, JSON and YAML content-types.

In the case of conflict between either of:

  • route string parameters

  • GET, POST and PUT parameters

  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Query string and body parameters will be merged (see "parameters" in Plack::Request)

Validation and coercion

You can define validations and coercion options for your parameters using a params block.

Parameters can be required and optional. optional parameters can have a default value.

get params => [
    required => ['name', 'Raisin::Types::String'],
    optional => ['number', 'Raisin::Types::Integer', 10],
],
sub {
    my $params = shift;
    "$params->{number}: $params->{name}";
};

Positional arguments:

  • name

  • type

  • default value

  • regex

Optional parameters can have a default value.

Types

Here is built-in types

You can create your own types as well. See examples in Raisin::Types. Also see Raisin::Types::Base.

HOOKS

This blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  • before

  • before_validation

  • after_validation

  • after

The block applies to every API call

before sub {
    my $self = shift;
    say $self->req->method . "\t" . $self->req->path;
};

after_validation sub {
    my $self = shift;
    say $self->res->body;
};

Steps 3 and 4 only happen if validation succeeds.

API FORMATS

By default, Raisin supports YAML, JSON, and TEXT content-types. The default format is TEXT.

Serialization takes place automatically. For example, you do not have to call encode_json in each JSON API implementation.

Your API can declare which types to support by using api_format.

api_format 'JSON';

Custom formatters for existing and additional types can be defined with a Raisin::Plugin::Format.

JSON

Call JSON::encode_json and JSON::decode_json.

YAML

Call YAML::Dump and JSON::Load.

TEXT

Call Data::Dumper->Dump if output data is not a string.

The order for choosing the format is the following.

  • Use the api_format if specified.

  • Fallback to TEXT.

AUTHENTICATION

TODO Raisin::Plugin::Auth Raisin::Plugin::Auth::Basic Raisin::Plugin::Auth::Token

LOGGING

Raisin has a built-in logger based on Log::Dispatch. You can enable it by

plugin 'Logger' => outputs => [['Screen', min_level => 'debug']];

Exports logger subroutine.

logger(debug => 'Debug!');
logger(warn => 'Warn!');
logger(error => 'Error!');

See Raisin::Plugin::Logger.

API DOCUMENTATION

Swagger compatible API documentations.

plugin 'APIDocs';

Documentation available on http://<url>/api-docs URL.

For more see Raisin::Plugin::APIDocs.

MIDDLEWARE

You can easily add any Plack middleware to your application using middleware keyword. See "middleware" in Raisin.

PLUGINS

Raisin can be extended using custom modules. Each new module must be a subclass of the Raisin::Plugin namespace. Modules' job is to initialize and register new methods into the web application class.

For more see "plugin" in Raisin and Raisin::Plugin.

TESTING

See Plack::Test, Test::More and etc.

my $app = Plack::Util::load_psgi("$Bin/../script/raisinapp.pl");

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->(GET '/user');

    subtest 'GET /user' => sub {
        if (!is $res->code, 200) {
            diag $res->content;
            BAIL_OUT 'FAILED!';
        }
        my $got = Load($res->content);
        isdeeply $got, $expected, 'Data!';
    };
};

DEPLOYING

Deploying a Raisin application is done the same way any other Plack application is deployed:

> plackup -E deployment -s Starman app.psgi

Kelp

use Plack::Builder;
use RaisinApp;
use KelpApp;

builder {
    mount '/' => KelpApp->new->run;
    mount '/api/rest' => RaisinApp->new;
};

Dancer

use Plack::Builder;
use Dancer ':syntax';
use Dancer::Handler;
use RaisinApp;

my $dancer = sub {
    setting appdir => '/home/dotcloud/current';
    load_app "My::App";
    Dancer::App->set_running_app("My::App");
    my $env = shift;
    Dancer::Handler->init_request_headers($env);
    my $req = Dancer::Request->new(env => $env);
    Dancer->dance($req);
};

builder {
    mount "/" => $dancer;
    mount '/api/rest' => RaisinApp->new;
};

Mojolicious::Lite

use Plack::Builder;
use RaisinApp;

builder {
    mount '/' => builder {
        enable 'Deflater';
        require 'my_mojolicious-lite_app.pl';
    };

    mount '/api/rest' => RaisinApp->new;
};

Also see Plack::Builder, Plack::App::URLMap.

EXAMPLES

See examples.

GITHUB

https://github.com/khrt/Raisin

AUTHOR

Artur Khabibullin - rtkh <at> cpan.org

ACKNOWLEDGEMENTS

This module was inspired both by Grape and Kelp, which was inspired by Dancer, which in its turn was inspired by Sinatra.

LICENSE

This module and all the modules in this package are governed by the same license as Perl itself.