A Basic Web Application
In this recipe, we’ll take a look at how we can get started with a simple web application with zero dependencies. The tools in the standard library covers most of the important parts of a web application other than a router. For that, we’ll have to write very rudimentary and naive router.
Let’s start with an ECR1 template and write a basic home page:
<!-- file: home.ecr -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Home</title>
</head>
<body>
<h1>Welcome to the Home page!</h1>
</body>
</html>
And an about page:
<!-- file: about.ecr -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>About</title>
</head>
<body>
<h1>About Us</h1>
<p>We use Crystal at work.</p>
</body>
</html>
Let’s define our handlers as classes that include HTTP::Handler2:
# file: handlers.cr
require "http/server"
# HomeHandler is the controller for the route:
# `GET: /`
class HomeHandler
include HTTP::Handler
def call(context : HTTP::Server::Context)
context.response.content_type = "text/html"
context.response.status_code = 200
# Respond with an ECR template:
ECR.embed "home.ecr", context.response
end
end
# AboutHandler is the controller for the route:
# `GET: /about`
class AboutHandler
include HTTP::Handler
def call(context : HTTP::Server::Context)
context.response.content_type = "text/html"
context.response.status_code = 200
# Respond with an ECR template:
ECR.embed "about.ecr", context.response
end
end
# ErrorNotFoundHandler is the controller for the case:
# `Error: 404 Page Not Found`
class ErrorNotFoundHandler
include HTTP::Handler
def call(context : HTTP::Server::Context)
context.response.content_type = "text/html"
context.response.status_code = 404
# Respond with a plain string:
context.response.print "No such route as #{context.request.path}"
end
end
# Router is a simple router that matches a handler based
# on the request path.
class Router
include HTTP::Handler
def route(path : String) : HTTP::Handler
case path
when "/"
HomeHandler.new
when "/about"
AboutHandler.new
else
ErrorNotFoundHandler.new
end
end
def call(context : HTTP::Server::Context)
handler = route(context.request.path)
handler.call(context)
end
end
And finally, let’s create an array of handlers and add a few HTTP handlers from the standard library and our custom router and run the server:
# file: server.cr
require "http/server"
require "./handlers"
HOST = "127.0.0.1"
PORT = 3000
handlers = [] of HTTP::Handler
handlers << HTTP::LogHandler.new
handlers << HTTP::ErrorHandler.new
handlers << HTTP::CompressHandler.new
handlers << Router.new
server = HTTP::Server.new(handlers)
server.bind_tcp HOST, PORT
p "Server starting to listen on http://#{HOST}:#{PORT}"
server.listen
A tiny Makefile to run our example:
# file: Makefile
.PHONY: run
run:
crystal run server.cr
You can now run the project with make run and your server should be listening. We can then
try the following:
curl localhost:3000
#OUTPUT:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Home</title>
</head>
<body>
<h1>Welcome to the Home page!</h1>
</body>
</html>
curl localhost:3000/about
#OUTPUT:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>About</title>
</head>
<body>
<h1>About Us</h1>
<p>We use Crystal at work.</p>
</body>
</html>
curl localhost:3000/gibberish
#OUTPUT:
No such route as /gibberish