The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Slick

SYNOPSIS

Slick is an Object-Oriented Perl web-framework for building performant, and easy to refactor REST API's. Slick is built on top of DBI, Plack, and Moo and fits somewhere in-between the realms of Dancer and Mojolicious.

Slick has everything you need to build a Database driven REST API, including built in support for Database connections, Migrations, Event logic, and soon, route based Caching via Redis or Memcached. Since Slick is a Plack application, you can also take advantage of swappable backends and Plack middlewares extremely simply.

Currently, Slick supports MySQL and PostgreSQL but there are plans to implement Oracle and MS SQL Server as well.

Examples

A Simple App

This is a simple example app that takes advantage of 2 databases, has a migration, and also serves some JSON.

    use 5.036;

    use Slick;

    my $s = Slick->new;

    # Both MySQL and Postgres are supported databases
    # Slick will create the correct DB object based on the connection URI
    # [{mysql,postgres,postgresql}://][user[:[password]]@]host[:port][/schema]
    $s->database(my_db => 'postgresql://user:password@127.0.0.1:5432/schema');
    $s->database(corporate_db => 'mysql://corporate:secure_password@127.0.0.1:3306/schema');

    $s->database('my_db')->migration(
        'create_user_table', # id
        'CREATE TABLE user ( id SERIAL PRIMARY KEY AUTOINCREMENT, name TEXT, age INT );', #up
        'DROP TABLE user;' # down
    );

    $s->database('my_db')->migrate_up; # Migrates all pending migrations

    $s->get('/users/{id}' => sub {
        my $app = shift;
        my $context = shift;

        # Queries follow SQL::Abstract's notations
        my $user = $app->database('my_db')->select_one('user', { id => $context->params('id') });

        # Render the user hashref as JSON.
        $context->json($user);
    });

    $s->post('/users' => sub {
        my $app = shift;
        my $context = shift;

        my $new_user = $context->content; # Will be decoded from JSON, YAML, or URL encoded (See JSON::Tiny, YAML::Tiny, and URL::Encode)

        $app->database('my_db')->insert('user', $new_user);

        $context->json($new_user);
    });

    $s->run; # Run the application.

A Multi-file application

This is a multi-file application that uses Slick::Router.

    ### INSIDE OF YOUR ROUTER MODULE lib/MyApp/ItemRouter.pm

    package MyApp::ItemRouter;

    use base qw(Slick::Router);

    my $router = __PACKAGE__->new(base => '/items');

    $router->get('/{id}' => sub {
        my ($app, $context) = @_;
        my $item = $app->database('items')->select_one({ id => $context->param('id') });
        $context->json($item);
    });

    $router->post('' => sub {
        my ($app, $context) = @_;
        my $new_item = $context->content;

        # Do some sort of validation
        if (not $app->helper('item_validator')->($new_item)) {
            $context->status(400)->json({ error => 'Bad Request' });
        } 
        else {
            $app->database('items')->insert('items', $new_item);
            $context->json($new_item);
        }
    });

    sub router {
        return $router;
    }

    1;

    ### INSIDE OF YOUR RUN SCRIPT 'app.pl'

    use 5.036;
    use lib 'lib';

    use Slick;
    use MyApp::ItemRouter;

    my $slick = Slick->new;

    $slick->helper(item_validator => sub {
        return exists shift->{name};
    });

    # Register as many routers as you want!
    $slick->register(MyApp::ItemRouter->router);

    $slick->run;

Running with plackup

If you wish to use plackup you can change the final call to run to a call to app:

    $s->app;

Then simply run with plackup (substitue `my_app.psgi` with whatever your app is called):

    plackup -a my_app.psgi

Changing PSGI backend

Will run on the default HTTP::Server::PSGI.

    $s->run;

Or,

In this example, running Slick with a Gazelle backend on port 8888 and address 0.0.0.0.

    $s->run(server => 'Gazelle', port => 8888, addr => '0.0.0.0'); 

Using Plack Middlewares

You can register more Plack middlewares with your application using the "middleware" method.

    my $s = Slick->new;

    $s->middleware('Deflater')
    ->middleware('Session' => store => 'file')
    ->middleware('Debug', panels => [ qw(DBITrace Memory) ]);

    $s->run; # or $s->app depending on if you want to use plackup.

API

app

    $s->app;

Converts the Slick application to a PSGI runnable app.

register

    my $router = Slick::Router->new(base => '/foo');
    
    $s->register($router);

Registers a Slick::Router to the application. This includes calling merge on the app's Slick::RouteMap.

    $s->banner;

Returns the Slick banner.

You can overwrite the banner with something else if you like via:

    $s->{banner} = 'My Cool Banner!';

database

    $s->database(my_db => 'postgresql://foo:bar@127.0.0.1:5432/database');

Creates and registers a database to the Slick instance. The connection string should be a fully-qualified URI based DSN.

    $s->database('my_db');

Retrieves the database if it exists, otherwise returns undef.

cache

    # See Redis and Cache::Memcached on CPAN for arguments

    # Create a Redis instance
    $s->cache(
        my_redis => type => 'redis',    # Slick Arguments
        server   => '127.0.0.1:6379'    # Redis arguments
    );

    # Create a Memcached instance
    $s->cache(
        my_memcached => type          => 'memcached',   # Slick Arguments
        servers      => ['127.0.0.1'] => debug => 1     # Cache::Memcached arguments
    );

Retrieves the cache if it exists, otherwise returns undef. Basically "database" but for caches. Note type is a required argument, it should either be 'redis' or 'memcached'.

helper

    $s->helper(printer => sub { print "Hi!"; });

Registers a Perl object with the Slick instance.

    $s->helper('printer')->();

Retrieves the helper from the Slick instance if it exists, otherwise returns undef.

middleware

    $s->middleware('Deflater');

Registers a Plack::Middleware with the Slick instance. Always returns the Slick instance so you can chain many middlewares in one sitting.

    $s->middleware('Deflater')
    ->middleware('Session' => store => 'file')
    ->middleware('Debug', panels => [ qw(DBITrace Memory) ]);

run

    $s->run;

Runs the Slick application on port 8000 and address 127.0.0.1 atop HTTP::Server::PSGI by default.

You can change this via parameters passed to the run method:

    $s->run(
        server => 'Gazelle',
        port => 9999,
        addr => '0.0.0.0'
    );

get, post, put, patch, delete

Methods to handle requests to your server. Takes a location, and a CodeRef that expects two arguments, app (Slick) and context (Slick::Context).

    $s->get('/foo', sub {
        my ($app, $context) = @_;
        # ... do stuff
    });

You can also have path parameters that are replaced with the values in the URI:

    $s->get('/foo/{bar}', sub {
        my ($app, $context) = @_;
        $context->params('bar'); # Resolves to the parameters value.
        # ... do stuff
    });

You may also register local events for the route like so:

    $s->get('/foo/{bar}', sub {
        my ($app, $context) = @_;
        $context->params('bar'); # Resolves to the parameters value.
        # ... do stuff
    },
    { before_dispatch => [
         sub {
             my ($app, $context) = @_;
             # Do some before dispatch logic
         }
    ],
      after_dispatch => [
         sub {
             my ($app, $context) = @_;
             # Do some after dispatch logic
             return 1;
         }
    ]);

See Slick::Events for a list of all of the events.

See Slick::Route on method for more information about route event handling.

See Slick::Context for more ways to handle routing and HTTP lifecycles.

on

    $s->on(before_dispatch => sub {
        my ($app, $context) = @_;
        # do stuff before routing logic has been handled
        return 1;
    });

    $s->on(after_dispatch => sub {
        my ($app, $context) = @_;
        # do stuff after routing logic has been handled
        return 1;
    });

Registers an event listener globally across the entire application. If a registered event handler returns a falsy value, the route chain will stop and the current context will be returned as the response.

See Slick::Events for a list of available events.

Inherited from Slick::EventHandler.

See also