The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

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

SYNOPSIS

    use strict;
    use warnings;

    use Raisin::API;
    use Types::Standard qw(Any Int Str);

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

    plugin 'Swagger', enable => 'CORS';
    api_format 'json';

    desc 'Actions on users';
    resource user => sub {
        desc 'List users';
        params(
            optional => { name => 'start', type => Int, default => 0, desc => 'Pager (start)' },
            optional => { name => 'count', type => Int, default => 10, desc => 'Pager (count)' },
        );
        get sub {
            my $params = shift;

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

            my $max_count = scalar(@users) - 1;
            my $start = $params->{start} > $max_count ? $max_count : $params->{start};
            my $count = $params->{count} > $max_count ? $max_count : $params->{count};

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

        desc 'List all users at once';
        get 'all' => sub {
            my @users
                = map { { id => $_, %{ $USERS{$_} } } }
                  sort { $a <=> $b } keys %USERS;
            { data => \@users }
        };

        desc 'Create new user';
        params(
            requires => { name => 'name', type => Str, desc => 'User name' },
            requires => { name => 'password', type => Str, desc => 'User password' },
            optional => { name => 'email', type => Str, default => undef, regex => qr/.+\@.+/, desc => 'User email' },
        );
        post sub {
            my $params = shift;

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

            { success => 1 }
        };

        desc 'Actions on the user';
        params(
            requires => { name => 'id', type => Int, desc => 'User ID' },
        );
        route_param 'id' => sub {
            desc 'Show user';
            get sub {
                my $params = shift;
                $USERS{ $params->{id} };
            };

            desc 'Delete user';
            del sub {
                my $params = shift;
                { success => delete $USERS{ $params->{id} } };
            };

            desc 'NOP';
            put sub { 'nop' };
        };
    };

    run;

DESCRIPTION

Raisin is a REST-like API web 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.

BACKWARD COMPATIBILITY

Since version 0.5000 Raisin was migrated to the new API syntax.

You could still use an old style API for a while by passing an -old key to the Raisin::API.

    use Raisin::API '-old';

See examples for more information.

KEYWORDS

resource

Adds a route to application.

    resource user => sub { ... };

route_param

Define a route parameter as a namespace route_param.

    route_param id => sub { ... };

del, get, patch, post, put

It's a shortcuts to route restricted to the corresponding HTTP method.

    get sub { 'GET' };

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

    params(
        requires => { name => 'id', type => Int },
        optional => { name => 'key', type => Str },
    );
    get sub { 'GET' };

    desc 'Put data';
    params(
        required => { name => 'id', type => Int },
        optional => { name => 'name', type => Str },
    );
    put 'all' => sub {
        'PUT'
    };

desc

Can be applied to resource or any of HTTP method to add description for operation or for resource.

    desc 'Some action';
    put sub { ... };

    desc 'Some operations group',
    resource => 'user' => sub { ... }

params

Here you can define validations and coercion options for your parameters. Can be applied to any HTTP method and/or route_param to describe parameters.

    params(
        requires => { name => 'key', type => Str }
    );
    get sub { ... };

    params(
        requires => { name => 'id', type => Int, desc => 'User ID' },
    );
    route_param 'id' => sub { ... };

For more see "Validation-and-coercion" in Raisin.

req

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

Use req to get access to a 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, which 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.

param

An alias for $self->params, which returns request parameters. Without arguments will return an array with request parameters. Otherwise it will return the value of the requested parameter.

Returns Hash::MultiValue object.

    say param('key'); # -> value
    say param(); # -> { key => 'value', foo => 'bar' }

session

An alias for $self->session, which returns psgix.session hash. When it exists, you can retrieve and store per-session data.

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

    # read param
    say session->{name};

api_default_format

Specify default API format when formatter doesn't specified. Default value: YAML.

    api_default_format 'json';

See also "API-FORMATS" in Raisin.

api_format

Restricts API to use only specified formatter for serialize and deserialize data.

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

    api_format 'json';

See also "API-FORMATS" in Raisin.

api_version

Setup an API version header.

    api_version 1.23;

plugin

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

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

middleware

Adds 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.

In RaisinApp.pm:

    package RaisinApp;

    use Raisin::API;

    api_format 'json';

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

    1;

new, run

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;

  • contents of 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 requires and optional. optional parameters can have a default value.

    params(
        requires => { name => 'name', type => Str },
        optional => { name => 'number', type => Int, default => 10 },
    );
    get sub {
        my $params = shift;
        "$params->{number}: $params->{name}";
    };

Available arguments:

  • name

  • type

  • default

  • desc

  • regex

Optional parameters can have a default value.

Types

Raisin supports Moo(se)-compatible type constraint so you can use any of the Moose, Moo or Type::Tiny type constraints.

By default Raisin depends on Type::Tiny and it's Types::Standard type contraint library.

You can create your own types as well. See Type::Tiny::Manual and Moose::Manual::Types.

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. Default format is YAML.

Response format can be determined by Accept header or route extension.

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

Your API can declare to support only one serializator by using "api_format" in Raisin.

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 YAML::Load.

TEXT

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

The order for choosing the format is the following.

  • Use the route extension.

  • Use the value of the Accept header.

  • Fallback to default.

LOGGING

Raisin has a built-in logger and support for Log::Dispatch. You can enable it by:

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

Or use Raisin::Logger with a fallback option:

    plugin 'Logger', fallback => 1;

Exports log subroutine.

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

See Raisin::Plugin::Logger.

API DOCUMENTATION

Raisin script

You can see application routes with the following command:

    $ raisin --routes examples/simple/routes.pl
      GET     /user
      GET     /user/all
      POST    /user
      GET     /user/{id}
      PUT     /user/{id}
      GET     /user/{id}/bump
      PUT     /user/{id}/bump
      GET     /failed

Verbose output with route parameters:

    $ raisin --routes --params examples/simple/routes.pl
      GET     /user
        optional: `start', type: Integer, default: 0
        optional: `count', type: Integer, default: 10

      GET     /user/all

      POST    /user
        required: `name', type: String
        required: `password', type: String
        optional: `email', type: String

      GET     /user/{id}
        required: `id', type: Integer

      PUT     /user/{id}
        optional: `password', type: String
        optional: `email', type: String
        required: `id', type: Integer

      GET     /user/{id}/bump
        required: `id', type: Integer

      PUT     /user/{id}/bump
        required: `id', type: Integer

      GET     /failed

      GET     /params

Swagger

Swagger compatible API documentations.

    plugin 'Swagger';

Documentation will be available on http://<url>/api-docs URL. So you can use this URL in Swagger UI.

For more see Raisin::Plugin::Swagger.

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.