Simple webapp built with Crystal and Kemal

Browsing through Kickstarter, I saw a lot of great ideas but also many silly ones. So I built a webapp to help people come up with more great ideas!

screenshot of app

It’s really simple and fairly limited in the available responses but I’m proud of it.

Check it out!

Or take a look at the source code

Introducing Crystal

The one interesting thing about this webapp is the stack it’s running on. Instead of using Php, Python, or NodeJS as the framework to run the app on, I decided to use something more uncommon.

A while back, I discovered Crystal. A statically typed language that the creators claim to be “fast as C, slick as Ruby”. While it’s been around for around four years, Crystal Language is still in the early phases of development and hasn’t seen too much use in production. Being the band-wagoner I am, I immediately jumped at the chance to be one of the early adopters of this language. (I will admit I am also just a fan of the name of the language don’t judge me!)

Shards and Kemal

Crystal has an interesting dependency/package manager called Shards. While it isn’t as robust as NodeJS’s NPM and Python’s PIP, I was surprised at how fleshed out it was. The webapp itself is built on a framework named Kemal.

To add Kemal to my project, I created a file called shard.yml in the project root and added a dependency to Kemal like so.

name: startup_idea_generator
version: 0.1.0
        github: kemalcr/kemal 

To get the files I then run

$ shards install

After that, I can start using Kemal by requiring it at the top of my code! A basic example of a Kemal app looks like this

require "kemal"

get "/" do
  "Hello World!"

This would simply print Hello World! to the screen when visiting the site’s root path.

How it works!

The app I coded builds on the previous example to take advantage of Kemal’s views and layouts functionality alongside adding more routes to handle errors and what not.

require "kemal"
require "./startup/*"
include Startup

Kemal.config.port = ****

error 404 do
  "404 Error"

error 403 do
  "Access Forbidden!"

get "/" do |env|
    render "src/views/main.ecr", "src/views/layouts/layout.ecr"

get "/new" do |env|
    env.response.content_type = "application/json"

One of the great things about Crystal (and Ruby for that matter) is the legibility of the code. It’s pretty easy to understand even for those unfamiliar with the syntax.

The get code blocks specify the path and http request type while the error blocks handle what to render for each error code.

The / path handles rendering the main page. I pass it the main view as the first argument and the layout as the secondary argument. Kemal will then render these pages on the browser.

def get_response(tech, response, languages, clients)
    first = get_random(tech)
    second = get_random(response)
    if second == " coded using "
        second += get_random(languages)
    elsif second == " for "
        second += get_random(clients)
    {"first" => first, "second" => second}

In this app, the /new path calls a crystal function get_response, which randomly selects the terms to be displayed before sending it back as an http response.

function generate(){
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      res = JSON.parse(this.responseText)
      document.getElementById("first").innerHTML = res["first"]
      document.getElementById("second").innerHTML = res["second"]
  };"GET", "/new", true);

generate() is a function in script.js that makes a request to the /new path and listens for the response. It then updates the html with the terms found in the response.


Since I largely use NodeJS for any web development work that needs to be done, this was my first time integrating multiple languages into a web stack. It was a good learning experience to see how to handle communication between the front and back end of a webapp. I really enjoyed coding with Crystal and am working on a few other small projects with it that I hope to share!