Model-View-Controller
for Raku.

MVC::Keayl is a Model-View-Controller web framework for Raku. Resourceful routing, controllers with filters and strong parameters, HAML views, sessions, signed and encrypted cookies, CSRF protection, background jobs, mailers, and caching. Conventional MVC patterns, expressed in Raku idioms.

It is the web layer only. Models are delegated to ORM::ActiveRecord and view rendering to Template::HAML, both pluggable. The HTTP server is reached through an abstract adapter, with a Cro-based default. Bring your own pieces, or take the stack whole.

config/routes.raku
// the stack

Every layer of the request, in one framework.

From the routes file to the rendered response, from the cookie jar to the job queue, Keayl ships the whole web tier so you can skip the wiring and write features.

/:

Resourceful routing

resources, namespace, scope, concern, constraints, redirects, mounts, and named URL helpers. One file maps every request.

Tour the routes DSL →
#

Controllers & actions

One instance per request. Filters with before-action, strong parameters with permit, implicit render, and rescue-from for exceptions.

Write an action →
%=

HAML views

Format and variant resolution, layouts, partials, collection and object partials, content-for / yield, and escape-by-default output safety.

Render a template →
<>

View helpers

content-tag and link-to, asset and URL helpers, form-with and simple_form inputs, select builders, and text, number, and date formatting.

Build some HTML →

Security built in

CSRF tokens, signed and encrypted cookies, sessions, parameter filtering, SSL enforcement, host authorization, and secure headers.

Lock it down →

Background jobs

perform-later with inline, async, test, and database queue adapters. GlobalID serialization, named queues, runnable without setup.

Queue some work →
@

Mailers

Email built like a response: an action renders HAML into the parts of a multipart message, a delivery method sends it. Class-level defaults included.

Send a message →
%

Caching & conditional GET

fresh-when and ETags, expires-in cache headers, a pluggable cache store with fetch, and fragment caching.

Cache responses →

Content negotiation

respond-to serves HTML, JSON, and your own formats from one action, with device variants and an API controller base.

Negotiate formats →
// routing

One routes file, the whole URL map.

Declare the seven REST routes with resources, nest them, scope them under a namespace, and constrain them. Every named route generates a path and URL helper, and recognition tells you whether a path was found, answered the wrong verb, or was unknown.

  • Resourceful by default. resources 'users' draws index, show, new, create, edit, update, destroy.
  • Composable scopes. namespace and scope prefix the path, module, and helper name together or apart.
  • Dynamic segments. :id, *glob, optional (.:format), and per-segment constraints.
  • URL helpers. path-for, url-for, and polymorphic dispatch by record class and state.
use MVC::Keayl::Routing;

routes {
  root to => 'home#index';

  resources 'articles', {
    resources 'comments';
  }

  namespace 'admin', {
    resources 'users', :except<destroy>;
  }

  get '/search', to => 'search#run',
    constraints => { q => /\w+/ };
}
// controllers

Actions that read like the request.

A controller is a class, each public method an action. The framework builds one instance per request, runs the before callbacks, dispatches the action, and renders. Strong parameters whitelist input before it reaches the model, and rescue-from turns exceptions into responses.

  • Per-request state. self.request, self.response, and merged self.params.
  • Filters. before-action, after-action, around-action, with only / except scoping.
  • Strong parameters. require, permit, and the strict expect.
  • Implicit render. Return a string, or render JSON, plain, HTML, or a template.
use MVC::Keayl::Controller;

class ArticlesController is MVC::Keayl::Controller {
  method authenticate
    is before-action(except => <index show>) {
      ...
    }

  method index {
    self.assign('articles', Article.all);
  }

  method create {
    my $attrs = self.params
      .require('article').permit('title', 'body');

    my $article = Article.create($attrs);
    self.redirect-to("/articles/{$article.id}");
  }
}
// views

Templates that compose.

Views resolve by name, format, and variant, render through a pluggable handler (HAML by default), and cache the compiled result. Wrap a body in a layout, capture named content with content-for, and break pages into partials rendered one-off, per object, or per collection. Interpolated values are escaped by default.

-# app/views/articles/index.html.haml
%h1= $title

%ul.articles
  != partial-each('articles/row', $articles)

-# app/views/articles/_row.html.haml
%li
  %a{href: "/articles/#{$row.id}"}= $row.title
  %span.meta= $row.published-at
// install

One command to install.

Pull MVC::Keayl from zef and draw your first route. The web layer is ready; add a controller and a view and you have a request cycle.

$ zef install MVC::Keayl
Full install guide → Browse the docs →