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!