The first step with any new technology is getting it running. The goal of this chapter is to get you started with a simple Yesod application and cover some of the basic concepts and terminology.
Let’s get this book started properly with a simple web page that says “Hello, World”:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Yesod
data
HelloWorld
=
HelloWorld
mkYesod
"HelloWorld"
[
parseRoutes
|
/
HomeR
GET
|
]
instance
Yesod
HelloWorld
getHomeR
::
Handler
Html
getHomeR
=
defaultLayout
[
whamlet
|
Hello
,
World
!|
]
main
::
IO
()
main
=
warp
3000
HelloWorld
If you save the preceding code in helloworld.hs and run it with runhaskell
helloworld.hs
, you’ll get a web server running on port 3000. If you point your
browser to http://localhost:3000, you’ll get the following HTML:
<!DOCTYPE html>
<html><head><title></title></head><body>
Hello, World!</body></html>
We’ll refer back to this example throughout the rest of the chapter.
Like most modern web frameworks, Yesod follows a front controller pattern. This means that every request to a Yesod application enters at the same point and is routed from there. As a contrast, in systems like PHP and ASP, you usually create a number of different files, and the web server automatically directs requests to the relevant file.
In addition, Yesod uses a declarative style for specifying routes. In our earlier example, this looked like:
mkYesod
"HelloWorld"
[
parseRoutes
|
/
HomeR
GET
|
]
mkYesod
is a Template Haskell function, and parseRoutes
is a
quasiquoter.
In other words, the preceding code simply creates a route in the Hello, World application called HomeR
. It should listen for requests to /
(the root of
the application) and should answer GET
requests. We call HomeR
a resource,
which is where the R
suffix comes from.
The R
suffix on resource names is simply convention, but it’s a fairly
universally followed convention. It makes it just a bit easier to read and
understand code.
The mkYesod
TH function generates quite a bit of code here: a route data
type, parser/render functions, a dispatch function, and some helper types.
We’ll look at this in more detail in Chapter 7, but by using the -ddump-splices
GHC option we can get an immediate look at the generated code. Here’s a cleaned-up version of it:
instance
RenderRoute
HelloWorld
where
data
Route
HelloWorld
=
HomeR
deriving
(
Show
,
Eq
,
Read
)
renderRoute
HomeR
=
(
[]
,
[]
)
instance
ParseRoute
HelloWorld
where
parseRoute
(
[]
,
_
)
=
Just
HomeR
parseRoute
_
=
Nothing
instance
YesodDispatch
HelloWorld
where
yesodDispatch
env
req
=
yesodRunner
handler
env
mroute
req
where
mroute
=
parseRoute
(
pathInfo
req
,
textQueryString
req
)
handler
=
case
mroute
of
Nothing
->
notFound
Just
HomeR
->
case
requestMethod
req
of
"GET"
->
getHomeR
_
->
badMethod
type
Handler
=
HandlerT
HelloWorld
IO
In addition to using -ddump-splices
, it can often be useful to generate
Haddock documentation for your application to see which functions and data
types were generated for you.
We can see that the RenderRoute
class defines an associated data type
providing the routes for our application. In this simple example, we have just
one route: HomeR
. In real-life applications, we’ll have many more, and they
will be more complicated than our HomeR
.
renderRoute
takes a route and turns it into path segments and query string
parameters. Again, our example is simple, so the code is likewise simple: both
values are empty lists.
ParseRoute
provides the inverse function, parseRoute
. Here we see the first
strong motivation for our reliance on Template Haskell: it ensures that the
parsing and rendering of routes correspond correctly with each other. This kind
of code can easily become difficult to keep in sync when written by hand. By
relying on code generation, we’re letting the compiler (and Yesod) handle those
details for us.
YesodDispatch
provides a means of taking an input request and passing it to
the appropriate handler function. The process is essentially:
Parse the request.
Choose a handler function.
Run the handler function.
The code generation follows a simple format for matching routes to handler function names, which I’ll describe in the next section.
Finally, we have a simple type synonym defining Handler
to make our code a
little easier to write.
There’s a lot more going on here than we’ve described. The generated dispatch code actually uses the view patterns language extension for efficiency; also, more typeclass instances are created, and there are other cases to handle, such as subsites. We’ll get into the details later in the book, especially in Chapter 18.
So we have a route named HomeR
, and it responds to GET
requests. How do you
define your response? You write a handler function. Yesod follows a standard
naming scheme for these functions: it’s the lowercase method name (e.g., GET
becomes get
) followed by the route name. In this case, the function name
would be getHomeR
.
Most of the code you write in Yesod lives in handler functions. This is where
you process user input, perform database queries, and create responses. In our
simple example, we create a response using the defaultLayout
function. This
function wraps up the content it’s given in your site’s template. By default,
it produces an HTML file with a doctype and <html>
, <head>
, and <body>
tags. As
we’ll see in Chapter 6, this function can be overridden to do
much more.
In our example, we pass [whamlet|Hello, World!|]
to defaultLayout
.
whamlet
is another quasiquoter. In this case, it converts Hamlet syntax into
a widget. Hamlet is the default HTML templating engine in Yesod. Together with
its siblings Cassius, Lucius, and Julius, you can create HTML, CSS, and
JavaScript in a fully type-safe and compile-time-checked manner. We’ll see much
more about this in Chapter 4.
Widgets are another cornerstone of Yesod. They allow you to create modular components of a site consisting of HTML, CSS, and JavaScript and reuse them throughout your site. Widgets are covered in more depth in Chapter 5.
The string HelloWorld
shows up a number of times in our example. Every Yesod
application has a foundation data type. This data type must be an instance of the
Yesod
typeclass, which provides a central place for declaring a number of
different settings controlling the execution of our application.
In our case, this data type is pretty boring: it doesn’t contain any information. Nonetheless, the foundation is central to how our example runs: it ties together the routes with the instance declaration and lets it all be run. We’ll see throughout this book that the foundation pops up in a whole bunch of places.
But foundations don’t have to be boring. They can be used to store lots of useful information—usually stuff that needs to be initialized at program launch and used throughout. Here are some very common examples:
A database connection pool
Settings loaded from a config file
An HTTP connection manager
A random number generator
By the way, the word Yesod (יסוד) means foundation in Hebrew.
We mention HelloWorld
again in our main function. Our foundation
contains all the information we need to route and respond to requests in our
application; now we just need to convert it into something that can run. A
useful function for this in Yesod is warp
, which runs the Warp web server with
a number of default settings enabled on the specified port (here, it’s 3000).
One of the features of Yesod is that you aren’t tied down to a single deployment strategy. Yesod is built on top of the Web Application Interface (WAI), allowing it to run on FastCGI, SCGI, Warp, or even as a desktop application using the WebKit library. We’ll discuss some of these options in Chapter 11. And at the end of this chapter, we will explain the development server.
Warp is the premier deployment option for Yesod. It is a lightweight, highly efficient web server developed specifically for hosting Yesod. It is also used outside of Yesod for other Haskell development (both framework and nonframework applications), and as a standard file server in a number of production environments.
In our Hello, World application we defined just a single resource (HomeR
), but real-life web
applications are usually much more exciting and include more than one page. Let’s take a look at another example:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Yesod
data
Links
=
Links
mkYesod
"Links"
[
parseRoutes
|
/
HomeR
GET
/
page1
Page1R
GET
/
page2
Page2R
GET
|
]
instance
Yesod
Links
getHomeR
=
defaultLayout
[
whamlet
|<
a
href
=@
{
Page1R
}
>
Go
to
page
1
!|
]
getPage1R
=
defaultLayout
[
whamlet
|<
a
href
=@
{
Page2R
}
>
Go
to
page
2
!|
]
getPage2R
=
defaultLayout
[
whamlet
|<
a
href
=@
{
HomeR
}
>
Go
home
!|
]
main
=
warp
3000
Links
Overall, this is very similar to Hello, World. Our foundation is now Links
instead of HelloWorld
, and in addition to the HomeR
resource, we’ve added
Page1R
and Page2R
. As such, we’ve also added two more handler functions:
getPage1R
and getPage2R
.
The only truly new feature is inside the whamlet
quasiquotation. We’ll delve
into syntax in Chapter 4, but we can see the following creates a link to the Page1R
resource:
<
a
href
=@
{
Page1R
}
>
Go
to
page
1
!
The important thing to note here is
that Page1R
is a data constructor. By making each resource a data
constructor, we have a feature called type-safe URLs. Instead of splicing
together strings to create URLs, we simply create a plain old Haskell value. By
using at-sign interpolation (@{…}
), Yesod automatically renders those
values to textual URLs before sending things off to the user. We can see how
this is implemented by looking again at the -ddump-splices
output:
instance
RenderRoute
Links
where
data
Route
Links
=
HomeR
|
Page1R
|
Page2R
deriving
(
Show
,
Eq
,
Read
)
renderRoute
HomeR
=
(
[]
,
[]
)
renderRoute
Page1R
=
([
"page1"
],
[]
)
renderRoute
Page2R
=
([
"page2"
],
[]
)
In the Route
associated type for Links
, we have additional constructors for
Page1R
and Page2R
. We also now have a better glimpse of the return values
for renderRoute
. The first part of the tuple gives the path pieces for the
given route. The second part gives the query string parameters; for almost all
use cases, this will be an empty list.
It’s hard to overestimate the value of type-safe URLs. They give you a huge amount of flexibility and robustness when developing your application. You can move URLs around at will without ever breaking links. In Chapter 7, we’ll see that routes can take parameters, such as a blog entry URL taking the blog post ID.
Let’s say you want to switch from routing on the numerical post ID to a year/month/slug setup. In a traditional web framework, you would need to go through every single reference to your blog post route and update appropriately. If you miss one, you’ll have 404s at runtime. In Yesod, all you do is update your route and compile: GHC will pinpoint every single line of code that needs to be corrected.
Yesod can serve up any kind of content you want, and has first-class support
for many commonly used response formats. You’ve seen HTML so far, but JSON data
is just as easy, via the aeson
package:
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Yesod
data
App
=
App
mkYesod
"App"
[
parseRoutes
|
/
HomeR
GET
|
]
instance
Yesod
App
getHomeR
=
return
$
object
[
"msg"
.=
"Hello, World"
]
main
=
warp
3000
App
We’ll cover JSON responses in more detail in later chapters, including how to
automatically switch between HTML and JSON representations depending on the
Accept
request header.
Installing Yesod will give you both the Yesod library, and a yesod executable. This executable accepts a few commands, but the first one you’ll
want to be acquainted with is yesod init
. It will ask you some questions, and
then generate a folder containing the default scaffolded site. Inside that
directory, you can run cabal install --only-dependencies
to build any extra
dependencies (such as your database backends), and then yesod devel
to run
your site.
The scaffolded site gives you a lot of best practices out of the box, setting up files and dependencies in a time-tested approach used by most production Yesod sites. However, all this convenience can get in the way of actually learning Yesod. Therefore, most of this book will avoid the scaffolding tool, and instead deal directly with Yesod as a library. But if you’re going to build a real site, I strongly recommend using the scaffolding.
We will cover the structure of the scaffolded site in Chapter 15.
One of the advantages interpreted languages have over compiled languages is
fast prototyping: you save changes to a file and hit refresh. If we want to
make any changes to our Yesod apps, we’ll need to call runhaskell
from
scratch, which can be a bit tedious.
Fortunately, there’s a solution to this: yesod devel
automatically rebuilds
and reloads your code for you. This can be a great way to develop your Yesod
projects, and when you’re ready to move to production, you still get to compile
down to incredibly efficient code. The Yesod scaffolding automatically sets
things up for you. This gives you the best of both worlds: rapid prototyping
and fast production code.
It’s a little bit more involved to set up your code to be used by yesod devel
, so our examples will just use warp
. Fortunately, the scaffolded site
is fully configured to use the development server, so when you’re ready to move
over to the real world, it will be waiting for you.
Every Yesod application is built around a foundation data type. We associate some resources with that data type and define some handler functions, and Yesod handles all of the routing. These resources are also data constructors, which lets us have type-safe URLs.
By being built on top of WAI, Yesod applications can run with a number of
different backends. For simple apps, the warp
function provides a convenient
way to use the Warp web server. For rapid development, using yesod devel
is a
good choice. And when you’re ready to move to production, you have the full
power and flexibility to configure Warp (or any other WAI handler) to suit your
needs.
When developing in Yesod, we get a number of choices for coding style:
quasiquotation or external files, warp
or yesod devel
, and so on. The
examples in this book deliberately use the choices that are easiest to
copy and paste, but more powerful options will be available when you start
building real Yesod applications.