Skip to content

rawleyfowler/Slick

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Slick has everything you need to build a Database driven REST API, including built in support for Database connections, Migrations, and 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, Redis, Memcached and Postgres but there are plans to implement Oracle and MS SQL Server.

Philosophy

Slick is aiming to become a "Batteries Included" framework for building REST API's and Micro-Services in Perl. This will include tooling for all sorts of Micro-Service concerns like Databases, Caching, Queues, User-Agents, and much more.

Goals

  • Database management (auto-enabled)
  • Migrations (auto-enabled)
  • CLI
  • Caching via Redis (optional)
  • Caching via Memcached (optional)
  • Sub-routine based caching for routes (optional)
  • RabbitMQ built-ins (optional)
  • AWS S3 support (optional)
  • User-Agents, including Client API exports
  • AWS SQS support (optional)

Note: All of these features excluding database stuff will be enabled optionally at run-time.

Examples

Single File App

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->param('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.

See the examples directory for this example.

Multi-file Router App

### INSIDE 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')->validate($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

use 5.036;
use lib 'lib';

use Slick;
use MyApp::ItemRouter;

my $slick = Slick->new;

$slick->register(MyApp::ItemRouter->router);

$slick->run;

See the examples directory for this example.

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 => 'Plack::Handler::Gazelle', port => 8888, addr => '0.0.0.0'); 

Using Plack Middlewares

You can register more Plack middlewares with your application very easily!

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.

Managing Your Database(s)

Slick allows you to easily connect databases to your applications.

Creating a database

my $s = Slick->new;
$s->database(my_postgres => 'postgresql://username:password@127.0.0.1:5432/db_name');

Migrations

Migrations are built using the migration method on Slick::Database. You provide 1, an ID for the migration, 2, the runnable/happy side of the migrations, and 3, the down or reverse of the migration.

$s->database('my_postgres')
  ->migration('create_users_table',
  'CREATE TABLE users ( id INT PRIMARY KEY, name TEXT, age INT );',
  'DROP TABLE user;')
  ->migration('create_pets_table',
  'CREATE TABLE pets ( id INT PRIMARY KEY, name TEXT, owner INT FOREIGN KEY REFERENCES users (id) );',
  'DROP TABLE pets;');

Queries

Queries in Slick are built with SQL::Abstract, and most of the heavy lifting is done for you already!

my $users = $s->database('my_postgres')
              ->select('users', [ 'id', 'name' ]); # SELECT id, name FROM users;

my $user = $s->database('my_postgres')
             ->select_one('users', [ 'id', 'name', 'age' ], { id => 1 }); # SELECT id, name, age FROM users WHERE id = 1;
             
$s->database('my_postgres')
  ->insert('users', { name => 'Bob', age => 23 }); # INSERT INTO users (name, age) VALUES ('Bob', 23);
  
$s->database('my_postgres')
  ->update('users', { name => 'John' }, { id => 2 }); # UPDATE users SET name = 'John' WHERE id = 2;

If you can't do what you want with SQL::Abstract helpers, you can certainly do it with DBI!

$s->database('my_postgres')->dbi->execute('DROP TABLE users;');

Caching

Slick supports caching using Memcached or Redis.

use 5.036;

use Slick;
use Slick::Annotation qw(cacheable);

my $s = Slick->new;

# 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'    # Cache::Memcached arguments
);

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

$s->cache('my_redis')->set( something => 'awesome' );

$s->get(
    '/foo' => sub {
        my ( $app, $context ) = @_;
        my $value = $app->cache('my_redis')->get('something');  # Use your cache
        return $context->text($value);
    }
);

# Use your cache to cache a route
$s->get(
    '/foobar' => cacheable(
        'my_redis',
        sub {
            my ( $app, $context ) = @_;
            return $context->json( { foo => 'bar' } );
        }
    )
);

$s->run;

Deployment

Please follow a standard Plack application deployment. Reverse-proxying your application behind NGiNX or Caddy and using Docker can drastically improve your deployment.

An example Dockerfile can be found in the examples directory.

Contributing

Slick is open to any and all contributions.

Code Standards:

  • Always format with the provided .perltidyrc
  • Always use Perl::Critic set to severity 3
  • Unpack subroutine arguments using array-destructuring when there are greater than 2 arguments

License

Slick is provided under the Artistic 2.0 license.