The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

package Kelp::Less;
use Kelp;
use Kelp::Base -strict;
our @EXPORT = qw/
app
attr
config
del
debug
error
get
module
named
param
post
put
req
res
route
run
session
stash
template
view
/;
our $app;
sub import
{
my $class = shift;
my $caller = caller;
no strict 'refs';
for my $sub (@EXPORT) {
*{"${caller}::$sub"} = eval("\\\&$sub");
}
strict->import;
warnings->import;
feature->import(':5.10');
$app = Kelp->new(config_module => 'Config::Less', @_);
$app->routes->base('main');
}
sub route
{
my ($path, $to) = @_;
$app->add_route($path, $to);
}
sub get
{
my ($path, $to) = @_;
route ref($path) ? $path : [GET => $path], $to;
}
sub post
{
my ($path, $to) = @_;
route ref($path) ? $path : [POST => $path], $to;
}
sub put
{
my ($path, $to) = @_;
route ref($path) ? $path : [PUT => $path], $to;
}
sub del
{
my ($path, $to) = @_;
route ref($path) ? $path : [DELETE => $path], $to;
}
sub run
{
# If we're running a test, then return the entire app,
# otherwise return the PSGI subroutine
return $ENV{KELP_TESTING} ? $app : $app->run;
}
sub app { $app }
sub attr { Kelp::Base::attr(ref($app), @_) }
sub param { $app->param(@_) }
sub session { $app->session(@_) }
sub stash { $app->stash(@_) }
sub named { $app->named(@_) }
sub req { $app->req }
sub res { $app->res }
sub template { $app->res->template(@_) }
sub view { $app->res->template(@_) }
sub debug { $app->debug(@_) if $app->can('debug') }
sub error { $app->error(@_) if $app->can('error') }
sub module { $app->load_module(@_) }
sub config { $app->config(@_) }
1;
__END__
=pod
=head1 NAME
Kelp::Less - Quick prototyping with Kelp
=head1 SYNOPSIS
use Kelp::Less;
get '/person/:name' => sub {
"Hello " . named 'name';
};
run;
=head1 DESCRIPTION
This class exists to provide a way for quick and sloppy prototyping of a web
application. It is a wrapper for L<Kelp>, which imports several keywords, making
it easier and less verbose to create a quick web app.
It's called C<Less>, because there is less typing involved, and
because it is suited for smaller, less complicated web projects. We encourage
you to use it anywhere you see fit, however for mid-size and big applications we
recommend that you use the fully structured L<Kelp>. This way you can take
advantage of its powerful router, initialization and testing capabilities.
=head1 QUICK START
Each web app begins with C<use Kelp::Less;>. This automatically imports C<strict>,
C<warnings>, C<v5.10> as well as several useful functions. You can pass any
parameters to the constructor at the C<use> statement:
use Kelp::Less mode => 'development';
The above is equivalent to:
use Kelp;
my $app = Kelp->new( mode => 'development' );
After that, you could add any initializations and attributes. For example, connect
to a database or setup cache. C<Kelp::Less> exports L<attr|Kelp::Base/attr>,
so you can use it to register attributes to your app.
# Connect to DBI and CHI right away
attr dbh => sub {
DBI->connect( @{ app->config('database') } );
};
attr cache => sub {
CHI->new( @{ app->config('cache') } );
};
# Another lazy attribute.
attr version => sub {
app->dbh->selectrow_array("SELECT version FROM vars");
};
# Later:
app->dbh->do(...);
app->cache->get(...);
if ( app->version ) { ... }
Now is a good time to add routes. Routes are added via the L</route> keyword and
they are automatically registered in your app. A route needs two parameters -
C<path> and C<destination>. These are exactly equivalent to L<Kelp::Routes/add>,
and you are encouraged to read its POD to get familiar with how to define routes.
Here are a few examples for the impatient:
# Add a 'catch-all-methods' route and send it to an anonymous sub
route '/hello/:name' => sub {
return "Hello " . named('name');
};
# Add a POST route
route [ POST => '/edit/:id' ] => sub {
# Do something with named('id')
};
# Route that runs an existing sub in your code
route '/login' => 'login';
sub login {
...
}
Each route subroutine receives C<$self> and all named placeholders.
route '/:id/:page' => sub {
my ( $self, $id, $page ) = @_;
};
Here, C<$self> is the app object and it can be used the same way as in a full
L<Kelp> route. For the feeling of magic and eeriness, C<Kelp::Lite> aliases
C<app> to C<$self>, so the former can be used as a full substitute to the
latter. See the exported keywords section for more information.
After you have added all of your routes, it is time to run the app. This is done
via a single command:
run;
It returns PSGI ready subroutine, so you can immediately deploy your new app via
Plack:
> plackup myapp.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/
=head1 KEYWORDS
The following list of keywords are exported to allow for less typing in
C<Kelp::Less>:
=head2 app
This a full alias for C<$self>. It is the application object, and an
instance of the C<Kelp> class. You can use it for anything you would use
C<$self> inside a route.
route '/die' => sub {
app->res->code(500);
};
=head2 attr
Assigns lazy or active attributes (using L<Kelp::Base>) to C<app>. Use it to
initialize your application.
attr mongo => MongoDB::MongoClient->new( ... );
=head2 route
Adds a route to C<app>. It is an alias to C<$self-E<gt>routes-E<gt>add>, and requires
the exact same parameters. See L<Kelp::Routes> for reference.
route '/get-it' => sub { "got it" };
=head2 get, post, put, del
These are shortcuts to C<route> restricted to the corresponding HTTP method.
get '/data' => sub { "Only works with GET" };
post '/data' => sub { "Only works with POST" };
put '/data' => sub { "Only works with PUT" };
del '/data' => sub { "Only works with DELETE" };
=head2 param
An alias for C<$self-E<gt>param> that gets the GET or 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.
get '/names' => sub {
my @names = param;
# Now @names contains the names of the params
};
get '/value' => sub {
my $id = param 'id';
# Now $is contains the value of 'id'
};
=head2 stash
An alias for C<$self-E<gt>stash>. The stash is a concept originally conceived by the
developers of L<Catalyst>. It's a hash that you can use to pass data from one
route to another.
# Create a bridge route that checks if the user is authenticated, and saves
# the username in the stash.
get '/user' => { bridge => 1, to => sub {
return stash->{username} = app->authenticate();
}};
# This route is run after the above bridge, so we know that we have an
# authenticated user and their username in the stash.
get '/user/welcome' => sub {
return "Hello " . stash 'username';
};
With no arguments C<stash> returns the entire stash hash. A single argument is
interpreted as the key to the stash hash and its value is returned accordingly.
=head2 named
An alias for C<$self-E<gt>named>. The C<named> hash contains the names and values of
the named placeholders from the current route's path. Much like the C<stash>,
with no arguments it returns the entire C<named> hash, and with a single
argument it returns the value for the corresponding key in the hash.
get '/:name/:id' => sub {
my $name = named 'name';
my $id = name 'id';
};
In the above example a GET request to C</james/1000> will initialize C<$name>
with C<"james"> and C<$id> with C<1000>.
=head2 req
An alias for C<$self-E<gt>req>, this provides quick access to the
L<Kelp::Request> object for the current route.
# Inside a route
if ( req->is_ajax ) {
...
}
=head2 res
An alias for C<$self-E<gt>res>, this is a shortcut for the L<Kelp::Response>
object for the current route.
# Inside a route
res->code(403);
res->json->render({ message => "Forbidden" });
=head2 template
A shortcut to C<$self-E<gt>res-E<gt>template>. Renders a template using the
currently loaded template module. Note that a Kelp::Less application does not
by default load a template module, so you will have to load it yourself.
use Kelp::Less;
module 'Template', path => 'views';
get '/hello/:name' => sub {
template 'hello.tt', { name => named 'name' };
};
=head2 view
A shortcut for L</template>.
get '/hello/:name' => sub {
view 'hello.tt', { name => named 'name' };
};
=head2 run
Creates and returns a PSGI ready subroutine, and makes the app ready for C<Plack>.
=head2 module
Loads a Kelp module. The module options may be specified after the module name.
module 'JSON::XS', pretty => 1;
=head2 config
Provides procedural interface to the configuration.
get '/hello' => sub {
my $baz = config('bar.foo.baz');
};
=head1 TESTING
When writing a C<Kelp::Less> app, we don't have a separate class to initialize and
feed into a L<Kelp::Test> object, because all of our code is contained in the
C<app.psgi> file. In this case, the C<Kelp::Test> object can be initialized
with the name of the C<PSGI> file in the C<psgi> argument.
# t/main.t
use Kelp::Test;
my $t = Kelp::Test->new( psgi => 'app.psgi' );
# Do some tests ...
Since you don't have control over the creation of the C<Kelp> object, if you
need to specify a different mode for testing, you can use the C<PLACK_ENV>
environmental variable:
> PLACK_ENV=test prove -l
This will enable the C<conf/test.pl> configuration, which you should
tailor to your testing needs.
=head1 ACKNOWLEDGEMENTS
This module's interface was inspired by L<Dancer>, which in its turn was
inspired by Sinatra, so Viva La Open Source!
=cut