Crystal Cove 03: (M)VC-ing it up

Rishav Sharan
4 min readJul 3, 2018

--

Well hello there.
If you are here, I am assuming that you have endured the Intro to the series.
In this series we will try to create a minimalist web app using the Crystal language.

Previous Article in the series: Crystal Cove 02: It’s a Viewtiful World
Next Article in the series: None (to be updated on next publish)

The current state of the repo is here

Before we really begin, lets make some quality-of-life updates to our app. The first item in the agenda is the Logger. We want to setup a custom logger so that we can make our console outputs prettier.

For this we will steal shamelessly from the default loghandler class of Crystal. The code for that is here.

In src, add a new folder helpers Here we will stow all the stuff which is very specific to our application framework. Lets add the file logger.cr here;

If you have seen the default LogHandler, you would know that is almost an exact copy of that. I have made a few formatting changes here and there but I left the rest for future.
We will call the new og handler from our main cove.cr file

module Cove                                                                        
server = HTTP::Server.new([ HTTP::ErrorHandler.new, Cove::Logger.new,
]) do |context|

If you run the project now, you should see the new formatting in the console.

The 2nd thing we want to do is to update our router. We will start by adding a method for redirecting. The Router class, add a new def for;

def self.redirect(path, ctx)    ctx.response.headers.add "Location", path    ctx.response.status_code = 302end

This is something I just found out a few days ago. I can cause a redirect to a specific url path by just setting a “Location” variable the context and then setting the status code to 302.

And coming back to the main router, we want to use both the url/resource and the HTTP method to define our route. So, instead of a string, we will use a tuple.

We are ready to proceed now.

When we last stopped, we had created a simple view system. Ultimately, we want to end up with a basic Model, View & Controller kind of setup. One out of 3 is ready. The next one on the chopping blocks is the C in MVC. But since we are rebels, we will call ours “actions”. 😃

For our first action, we want to create a page “Register”. User can come here, fill in the username and the password. On clicking submit, the html form is submitted. The server extracts the form params and sends it to our action. For now, our action will be without logic and it will just return the params that it received from the client.

To create the Register page, in src/views/page add Register.cr as;

This page of ours contains a simple HTML form with 2 fields. There is also a cute JS script (specific to this page) which toggles the type of the password field, allowing the user to peek at the password. The HTML form is supposed to make a POST call to our /register route.

In src/actions we will add a new file called auth.cr as

And we will stitch it up in our route as;

when {"/register/", "GET"}    ctx.response.content_type = "text/html; charset=utf-8"    ctx.response.print Cove::Layout.render(Cove::Views.register)when {"/register/", "POST"}    ctx.response.content_type = "text/html; charset=utf-8"    store = Cove::Auth.register(ctx)    ctx.response.print store

So far, we have done nothing special but we have created a dummy action which will generate a store object, which can then we used to render the page.

If you run crystal now, and fill up the register form

Now to the most important part of the app’s scaffolding; the database connection. Again, Crytsal comes built-in with a lot of the database connection boilerplate. You should read mode about database support in here.

For our app, we will use Postgres as database and as such we will need to install the crystal-pg drivers. Now would also be a good time to install Dotenv as well. The plan is to keep our database URI (or if you have separate username/password) in a variable DATABASE_URL=postgres://username:pass@hostname:4321/dbname

Simply update your cove.cr to have;

module Cove    Dotenv.load!    DB     = PG.connect ENV["DATABASE_URL"]    puts "Connecting to Database..."    test_db = DB.scalar "SELECT 'Connection established! The DB sends its regards.'"    puts test_db

Do note, that our DB string here is a constant!

Now when you start your app, you should see something like this in your console;

Time to stop our current article. In the next one we will finally start with some Application code. The current state of the repo is here

--

--