Swagg::Blogg

noscript

Previously I wrote that I need to plumb my web app to a DB and was told that DBIish was the tool for the job. I was glad to see it has a migrations module to go along with it. I'll go ahead and add my first migration which will create the table for a pastebin. These simply consist of SQL statements in a file I creatively named 'migrations':

-- 1 up
CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );

-- 1 down
DROP TABLE pastes;

Now I'll need to connect to my DB. I honestly didn't have time to figure out how I'll pass this object to my model module(s) so for now I'm using a dynamic variable and rather than reading the credentials from a file I'll just use prompt() for now:

my $*dbh = DBIish.connect:
    'Pg',
    :host<devbussy.swagg.net>,
    :database<pastes_bin>,
    :user<pastes_bin>,
    password => prompt 'enter DB password: ';

my $m = DB::Migration::Simple.new:
    :$*dbh,
    :migration-file<migrations>;

$m.migrate: :version<1>;

Now if we run the web app we get prompted for the DB password and then we can see the migration be applied:

$ ./bin/pastes-bin
enter DB password: hogrider69
line: -- 1 up
version: 1, direction: up
line: CREATE TABLE IF NOT EXISTS pastes (
line:          paste_id SERIAL PRIMARY KEY,
line:        paste_body TEXT
line:        );
line: -- 1 down
version: 1, direction: down
line: DROP TABLE pastes;
initializing db-migrations-simple-meta
set initial version to 0
{1 => {down => DROP TABLE pastes;
, up => CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );
}}
migrating from version '0' to version '1'
True migrating 'up' from version '0' to version '1'
doing 'up' migrations for 1
executing CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       )
Humming-Bird listening on port http://localhost:3000

We can check the DB now and see our new table applied along with the metadata for the migrations module itself:

$ psql -h devbussy.swagg.net -U pastes_bin
Password for user pastes_bin: 
psql (15.3 (Debian 15.3-0+deb12u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

pastes_bin=> \d
                     List of relations
 Schema |           Name            |   Type   |   Owner    
--------+---------------------------+----------+------------
 public | db-migrations-simple-meta | table    | pastes_bin
 public | pastes                    | table    | pastes_bin
 public | pastes_paste_id_seq       | sequence | pastes_bin
(3 rows)

pastes_bin=> \d pastes;
                                 Table "public.pastes"
   Column   |  Type   | Collation | Nullable |                 Default                  
------------+---------+-----------+----------+------------------------------------------
 paste_id   | integer |           | not null | nextval('pastes_paste_id_seq'::regclass)
 paste_body | text    |           |          | 
Indexes:
    "pastes_pkey" PRIMARY KEY, btree (paste_id)

Now I wanted to have separate model and controller modules but I'm still working on how to pass $dbh around sanely so for now I'm going to just add this to test:

use Pastes-Bin::Model::Paste;

# No routes yet just prompt to 'fake it'
my $new-paste = prompt 'enter a new paste: ';
Pastes-Bin::Model::Paste.create: $new-paste;

Here's the model code:

unit class Pastes-Bin::Model::Paste;

submethod create(Str $new-paste) {
    $*dbh.execute(q:to/END_SQL/, $new-paste)
        INSERT INTO pastes (paste_body)
        VALUES (?);
       END_SQL
}

And now we run it again:

$ ./bin/pastes-bin 
enter DB password: hogrider69
line: -- 1 up
version: 1, direction: up
line: CREATE TABLE IF NOT EXISTS pastes (
line:          paste_id SERIAL PRIMARY KEY,
line:        paste_body TEXT
line:        );
line: -- 1 down
version: 1, direction: down
line: DROP TABLE pastes;
current-version: allrows: [[1]]
{1 => {down => DROP TABLE pastes;
, up => CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );
}}
migrating from version '1' to version '1'
DB already at version 1
enter a new paste: testing 123...
Humming-Bird listening on port http://localhost:3000
^C

And now in the DB:

pastes_bin=> SELECT * FROM pastes;
 paste_id | paste_body 
----------+------------
(0 rows)

pastes_bin=> SELECT * FROM pastes;
 paste_id |   paste_body   
----------+----------------
        1 | testing 123...
(1 row)

It's nice to see I can talk to a DB with ease with DBIish and it handles migrations. I need to next dig into how I can organize my code in Raku where $dbh can be 'passed around'. I tried creating a separate controller module that calls the model class but in that configuration it did not see the $*dbh dynamic variable... If/when I figure that out then that'll be the next blog entry on my journey into Raku.

#webDev #noScript #RakuLang

Now that my little textboard project is in a decent state I've begun thinking about what I wanted to try next. I knew it'll be some sort of webbed site and there'd be a database but other than that it was up in the air. Mojo.js appealed to me for obvious reasons but honestly I found the Node/JavaScript conventions and ecosystem pretty intimidating. I've never been any good at JS and the whole front-end thing is an enigma to me, as is probably obvious if you've seen my prior art.

Thankfully I discovered Humming-Bird which I like because it reminds me of Mojo or Sinatra. It's also a great excuse to use Raku, a language I also find intimidating but at least feels more 'fun' and less 'enterprise' than JS or even Perl/Ruby. If you already have a nice little CPAN/Perl set-up then getting Raku is easy enough:

cpanm App::Rakubrew && rakubrew download

If not then Debian ships it:

apt install rakudo

Finally we can use zef to install Humming-Bird:

zef install Humming-Bird

Humming-Bird is quite minimal like Sinatra is so I want to install something for templates. Template::Mustache was recommended to me after I had trouble getting others to work:

zef install Template::Mustache

To get started let's import Humming-Bird::Core and our template module; I'm going to keep my template in a directory called templates:

#!/usr/bin/env raku

use v6.d;
use Humming-Bird::Core;
use Template::Mustache;

my $template = Template::Mustache.new: :from<./templates>;

I'll go ahead and throw in a basic template named index.mustache:

<!DOCTYPE html>
<html>
<head>
  <title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<p>We will we will... RAKU!!</p>
</body>
</html>

To render this template we only need to add the following to our script, I've named mine basic-site.raku if you're playing along at home:

get '/', -> $request, $response {
    my Str %stash = title => 'Hello, web!';

    $response.html($template.render: 'index', %stash);
};

listen 3000;

That is literally all you need to take flight:

$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Content-Length: 144
Date: Fri, 10 Nov 2023 02:29:44 +0000
X-Server: Humming-Bird (Raku)
content-type: text/html; charset=utf8

<!DOCTYPE html>
<html>
<head>
  <title>Hello, web!</title>
</head>
<body>
<h1>Hello, web!</h1>
<p>We will we will... RAKU!!</p>
</body>
</html>

This is pretty decent as-is but if there's anything I create more than silly websites, it's compiler errors and warnings. I'd like them to be more verbose so I can also add this:

# Add the 'middleware' and 'advice' parts
use Humming-Bird::Middleware;
use Humming-Bird::Advice;

# All you need to use 'em out-of-the-box
middleware &middleware-logger;
advice     &advice-logger;

Our entire script now looks like this:

#!/usr/bin/env raku

use v6.d;
use Humming-Bird::Core;
use Humming-Bird::Middleware;
use Humming-Bird::Advice;
use Template::Mustache;

middleware &middleware-logger;
advice     &advice-logger;

my $template = Template::Mustache.new: :from<./templates>;

get '/', -> $request, $response {
    my Str %stash = title => 'Hello, web!';

    $response.html($template.render: 'index', %stash);
};

listen 3000;

You can take a better look at it in my git repo along with my other Raku experiments so far. I'm glad Humming-Bird arrived on the scene as it's left me with no more excuses to not at least try Raku and so far I'm really diggin it. I've found the best sources of Raku halp to be Reddit and their Discord (which is bridged to IRC as well); you can get your hyperlinks on their website.

#webDev #noScript #RakuLang