Welcome to a walkthrough of Raku Codeboard — a small but complete full-stack web application built entirely in Raku. It lets you submit, store, and discuss code or comments under different topics. This blog post explains how it works using your actual code and shows how it leverages Red, Cromponent, and HTMX together.

👉 Source Code: https://github.com/FCO/Discuss


💡 What is Raku Codeboard?

Raku Codeboard is a lightweight app where users can:

  • Create topics
  • Post messages under each topic (text or code)
  • See a list of all people who participated

The stack:

  • Red for ORM/database mapping
  • Cromponent for reusable web components
  • HTMX for declarative interactivity

🧱 app.raku

This is your full app.raku, setting up Cro and wiring routes to Cromponents:

#!/usr/bin/env raku

use lib "lib";
use lib "bin/lib";
use Cro::HTTP::Router;
use Cro::HTTP::Server;
use Cro::WebApp::Template;
use Red:api<2>;

use Topic;
use Topics;
use People;
use Person;
use Message;

my $routes = route {
    red-defaults "SQLite";
    my $schema = schema(Topic, Person, Message).create;

    Topic.^add-cromponent-routes;
    Topics.^add-cromponent-routes;
    People.^add-cromponent-routes;

    template-location "resources/";
    get  -> {
        template "index.crotmp", %(
            topics => Topic.^all,
            people => Person.^all,
        )
    }
}

my Cro::Service $http = Cro::HTTP::Server.new(
    http => <1.1>,
    host => "0.0.0.0",
    port => 3013,
    application => $routes,
);

$http.start;
say "Listening at http://0.0.0.0:3013";
react {
    whenever signal(SIGINT) {
        say "Shutting down...";
        $http.stop;
        done;
    }
}

🧩 index.crotmp

Your template renders two Cromponents: <&People> and <&Topics>:

<:use Boilerplate>

<|Boilerplate(:title('Todo - test cromponent'), :htmx)>
    
         div.topics:empty:before {
            content: "No topics yet. Be the first to start one!";
        }
         div.messages:empty:before {
            content: "No messages yet. Be the first to create one!";
        }
         div.people:empty:before {
            content: "No people yet. Be the first one!";
        }
    
    
        href="/topics"
        hx-get="/topics"
        hx-target=".main"
    >
        Topics
    

    
        href="/people"
        hx-get="/people"
        hx-target=".main"
    >
        People
    

    Raku Codeboard

     class="main" hx-trigger="load" hx-get="/topics">
|>

📦 Cromponents and Models

Topics.rakumod

use Cromponent;
use Topic;

unit class Topics does Cromponent;

has @.topics = Topic.^all;

method LOAD { ::?CLASS.new }

method RENDER {
    Q:to/HTML/;
    Create a new topic
    
        
        
        Start Topic
    

    Topics
    <@.topics.Seq><&HTML(.Card)>@>
    HTML
}

sub EXPORT { Topics.^exports }

Topic.rakumod

use Red:api<2>;
use Cromponent;
use Person;
use Topic::Card;

unit model Topic does Cromponent is table;

has UInt $.id        is serial;
has Str  $.title     is unique{ :nullable };
has      @.messages  is relationship(*.topic-id, :model);
has UInt $.author-id is referencing(*.id, :model);
has UInt $.author    is relationship(*.author-id, :model);

method LOAD(UInt() $id) { ::?CLASS.^load: $id }
method DELETE           { $.^delete }
method CREATE(Str :$nick, Str :$title) {
    Topic.^create(:$title, :author{ :$nick }).Card
}

method message(Str :$body!, Str :$nick!, Bool :$code = False) is accessible{ :http-method } {
    $.messages.create: :$body, :$code, :author{ :$nick }
}

method RENDER {
    Q:to/HTML/;
    
        <.title> by <.author.nick>
        <@.messages.Seq><&HTML($_)>@>
        
            User name: 
             code
            
            Send
        
    
    HTML
}

method Card {
    Topic::Card.new: :$!id, :$!title, :$!author
}

sub EXPORT { Topic.^exports }

Topic::Card.rakumod

use Red;
use Cromponent;

unit model Topic::Card does Cromponent;

has UInt $.id     is required;
has Str  $.title  is required;
has      $.author is required;

method RENDER {
    Q:to/HTML/
    
        
            <.title>
        
        by <.author.nick>
    
    HTML
}

Message.rakumod

use Red;
use Cromponent;

unit model Message does Cromponent is table;

has UInt $.id;
has UInt $.topic-id is referencing(*.id, :model);
has $.topic is relationship(*.topic-id, :model);
has Str $.body;
has UInt $.author-id is referencing(*.id, :model);
has $.author is relationship(*.author-id, :model);
has Bool $.code;

method LOAD(Str() $id) { ::?CLASS.^load: $id }
method DELETE          { $.^delete }

method RENDER {
    Q:to/HTML/
    
        <.author.nick>
        <.body>
    
    HTML
}

subset Text is sub-model of Message where *.code.not;
subset Code is sub-model of Message where *.code.so;



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  People.rakumod

use Cromponent;
use Person;

unit class People does Cromponent;

has @.people = Person.^all;

method LOAD { ::?CLASS.new }

method RENDER {
    Q:to/HTML/;
    People
    <@.people.Seq><&HTML($_)>@>
    HTML
}

sub EXPORT { People.^exports }



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  🔁 HTMX + Cromponent + Red
Everything works together like this:
Forms use hx-post to Cromponent route methods (e.g., CREATE, message)
Cromponent methods use Red to insert into the database
They return a Cromponent that gets rendered to HTML
HTMX places that HTML back into the DOM (e.g., .main or .topics)
All routes are declared using .^add-cromponent-routes, so you don’t need to write routing logic by hand.
  
  
  ✅ Summary
This app is fully functional and real — and it’s all in Raku. It combines:

Red for database modeling

Cromponent for reusable UI

HTMX for user interactivity

Cro for the HTTP layer
🔗 https://github.com/FCO/DiscussLet me know what you’d like to add!