# [Slick](https://metacpan.org/pod/Slick) 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](https://metacpan.org/pod/DBI), [Plack](https://metacpan.org/pod/Plack), and [Moo](https://metacpan.org/pod/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 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 `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 - [x] Database management (auto-enabled) - [x] Migrations (auto-enabled) - [ ] CLI - [x] Caching via Redis (optional) - [x] Caching via Memcached (optional) - [x] 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 ```perl 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->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 ```perl ### 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` ```perl $s->app; ``` Then simply run with plackup (substitue `my_app.psgi` with whatever your app is called): ```bash plackup -a my_app.psgi ``` ### Changing PSGI backend Will run on the default [`HTTP::Server::PSGI`](https://metacpan.org/pod/HTTP::Server::PSGI). ```perl $s->run; ``` or In this example, running Slick with a [`Gazelle`](https://metacpan.org/pod/Gazelle) backend on port `8888` and address `0.0.0.0`. ```perl $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! ```perl 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 ```perl 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. ```perl $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`](https://metacpan.org/pod/SQL::Abstract), and most of the heavy lifting is done for you already! ```perl 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! ```perl $s->database('my_postgres')->dbi->execute('DROP TABLE users;'); ``` ## Caching Slick supports caching using [`Memcached`](https://memcached.org) or [`Redis`](https://redis.io). ```perl use 5.036; use Slick; 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`](https://nginx.org) or [`Caddy`](https://caddyserver.com) and using [`Docker`](https://www.docker.com) 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.