scratch/multi/blog/Yesod-tutorial-for-newbies.md
Yann Esposito (Yogsototh) b9ae700a15 Fixed some language issues
2012-01-05 16:49:30 +01:00

15 KiB

isHidden: false menupriority: 1 kind: article created_at: 2011-12-28T15:14:40+02:00 en: title: Yesod tutorial for newbies fr: title: Tutoriel Yesod pour les nuls author_name: Yann Esposito author_uri: yannesposito.com tags: - yesod - haskell - programming - web

<%= blogimage("flying_neo.jpg","Neo Flying at warp speed") %>

begindiv(intro)

en: <%= tldr %> A simple yesod tutorial. You shouldn't need to know Haskell very well.

fr: <%= tlal %> Un tutoriel pour yesod.

  • Table of Content (generated) {:toc}

enddiv

Haskell is incredible and you should consider to use it to make your next web application. Why?

<%= blogimage("warp_benchmark.png","Haskell Benchmark which destroy Ruby and Python") %>

My main reason to look at Haskell is its efficiency. Haskell is compiled and is incredibly faster than interpreted languages like Ruby and Python. Haskell handle parallel tasks perfectly. For example even better than node.js1.

Haskell remains generally slower than C and C++.

Its type system gives the feeling of using an interpreted language. Haskell has many more great properties, one of the best being:

"If your program compile it will be very close to what the programmer intended".

From the pure technical "point of vue", Haskell seems to be the perfect web development tool.

Weaknesses of Haskell certainly won't be technical but social:

  • Hard to grasp Haskell
  • Hard to find a Haskell programmer
  • The Haskell community is smaller than the community for X
  • There is no heroku for Haskell (even if Greg Weber did it, it was more a workaround).

<%= leftblogimage("thousands_smiths.jpg","Thousands of Agent Smith") %>

I don't say these are not important drawbacks. But if you want to use the best product which handle thousand of parallel just follow me. Haskell is certainly the best technical choice. I searched a long time and I considered efficiency, security and quality. In my humble opinion Haskell has the best balance.

Furthermore, the Haskell community is just an excellent one. Very helpful and smart.

Instead of going deep inside Haskell, we will simply start as straight as possible toward a real web application.

Instead of reinvent the wheel, we should choose a web framework in Haskell. Actually there are three choices:

  1. Happstack
  2. Snap
  3. Yesod

I don't think there is a real winner between these three framework. The choice I made for yesod is highly subjective. I had the feeling yesod help the newcomers the most. It also appears the yesod developer are the most active. But as I said before, I might be wrong has it was only feeling.

<%= blogimage("owl_draw.png","1. Draw some circles. 2. Draw the rest of the fucking owl") %>

Now, what this article is all about? A missing tutorial in the yesod documentation. I lacked an intermediate tutorial level. First, use the scaffolding site of yesod directly instead of using the framework to make minimal "one file only" tutorial. The goal is to go as straight as possible to the best practice. I did my best to remove all the hard part. And particularly, I tried to forget the Haskell language and focus on the Yesod framework. I wanted to make it easier to follow for people not used to Haskell.

If you are not used to Haskell, some syntax details may feel awkward. Please, don't try to understand it now. Just follow the flow of what you understand in the code and try to forget a bit about the details. Haskell is a very complex language and could suck all your energy if you want to dig too early.

You'll then first install, initialize and configure your first yesod project. Then a 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod. Then we clean up the 5 minutes tutorial to use the best practices.

Before the real start

Install

First you need to install Haskell. The recommended way to do this is to download the Haskell Platform.

Then you need to install yesod.

> cabal update > cabal install yesod cabal-dev

That is all. It should take some time to do this as cabal will download all package and then compile them.

Initialize

Open a terminal and type:

> yesod init

Enter your name, name the project yosog and the name of the Foundation as Yosog, then choose sqlite. Perfect. Now you can start the development cycle:

> cd yosog > cabal-dev install && yesod --dev devel

This will compile the entire project. Be patient it could take some time. Once finished a server is launched and you could visit it by clicking this link:

http://localhost:3000

Congratulation! Yesod works!

Note: if something is messed up use the following command line:

\rm -rf dist/* ; cabal-dev install && yesod --dev devel

Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.

Configure git

This step is not mandatory for a tutorial, but I wanted to jump directly to good practice. There are many different choice of CVS, but for this tutorial I'll use git.

Copy this .gitignore file into the yosog folder.

cabal-dev dist .static-cache static/tmp *.sqlite3

Then initialize your git repository:

> git init . > git add . > git commit -a -m "Initial yesod commit"

Now we are almost ready to start.

A last point

Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000.

If we modify a file inside this directory, yesod should try to recompile as fast as possible the site.

Instead of explaining the role of every file, let's get straight to the point.

Inside the yosog the important files/directories for this tutorial are:

  1. config/routes
  2. Handler/
  3. templates/
  4. config/models

Obviously:

  • config/routes is where you'll configure the map URL → Code.
  • Handler/ contains the files that will contain the code called when a URL is accessed.
  • templates/ contains HTML, JS and CSS templates.
  • config/models is where you'll configure the persistent objects (database tables).

During this tutorial we'll modify other files as well, but we won't look them in detail. Also we didn't even typed any line of Haskell. But now, it is the time to start the interesting part.

Echo

To verify the quality of the security of the yesod framework, let's make a minimal echo application.

Our goal:

Make a server that when accessed /echo/[some text] should return a web page containing "some text" inside an h1 bloc.

For example, accessing http://localhost:3000/echo/some%20text, should display "some text" in an %html web page.

First, we must declare URL of the form /echo/... are meaningful.

Let's take a look at the file config/routes:

/static StaticR Static getStatic /auth AuthR Auth getAuth

/favicon.ico FaviconR GET /robots.txt RobotsR GET

/ RootR GET

We want to add a route of the form /echo/[anything] somehow and do some action with this. We add the following:

/echo/#String EchoR GET

This line contains three elements: the url pattern, a handler name, an HTTP method. I am not particularly fan of the big R in the end of handler names. But this is the standard convention, then let's use it.

If you save config/routes, you should see your terminal in which you launched yesod devel activate and certainly displaying an error message.

Application.hs:31:1: Not in scope: `getEchoR'

Why? Simply because we didn't written the code for the handler EchoR. Now, let's do this. Edit the file Handler/Root.hs and append this:

getEchoR :: String -> Handler RepHtml getEchoR theText = do defaultLayout $ do [whamlet|

#{theText}|]

Don't worry if you find all of this a bit cryptic. This is normal when learning a new framework. In short it just declare a function named getEchoR with one argument (theText) of type String. When this function is called, it return a "Handler RepHtml" whatever it is. But mainly this will encapsulate our expected result inside an HTML text.

After saving the file, you should see yesod recompile the application. When the compilation is finished you'll see the message: Starting devel application.

Now you can visit: http://localhost:3000/echo/Yesod%20rocks!

TADA! It works.

Secure?

<%= blogimage("neo_bullet_proof.jpg","Neo stops a myriad of bullets") %>

Let's try to attack our website by entering a text with special characters:

[http://localhost:3000/echo/<a>I'm <script>alert("Bad!");](http://localhost:3000/echo/I'm " %>

All should work better than expected.

The special characters are protected for us. If you have a malicious user, he could not hide some bad script inside his login name for example.

This is a direct consequence of type safety. The URL string is put inside a URL type. Then the interesting part in the URL is put inside a String type. To pass from URL type to String type some transformation are made. For example, replace all "%20" by space characters. Then to show the String inside an HTML document, the string is put inside an HTML type. Some transformations occurs like replace "<" by "&lt;". Thanks to yesod, most of tedious string transformation job is done for us.

"http://localhost:3000/echo/some%20text" :: URL ↓ "some text" :: String ↓ "some text <a>" :: HTML

That was the first very minimal example, and we already verified Yesod protect us from many common errors.

Then not only Yesod is fast, it is also relatively secure.

Cleaning up

This first example was nice, but for simplicity reason we didn't used best practices.

First we will separate the handler code into different files. After that we will use Data.Text instead of String. Finally we'll use a template file to better separate our view.

Separate handlers

In a first time create a new file Handler/Echo.hs containing:

module Handler.Echo where

import Import

getEchoR :: String -> Handler RepHtml getEchoR theText = do defaultLayout $ do [whamlet|

#{theText}|]

Do not forget to remove the getEchoR function inside the Handler/Root.hs file.

We must declare the file inside the cabal configuration file yosog.cabal. Just after Handler.Root add:

    Handler.Echo

We must also declare the new Handler module inside Application.hs. Just after the "import Handler.Root", add:

import Handler.Echo

Data.Text

Now our handler is separated in another file.

It is a good practice to use Data.Text instead of String.

To declare we will use the type Data.Text we modify the file Foundation.hs. Add an import directive just after the last one:

import Data.Text

And also we must modify config/routes and our handler accordingly. Replace #String by #Text in config/routes:

/echo/#Text EchoR GET

And do the same in Handler/Echo.hs:

module Handler.Echo where

import Import

getEchoR :: Text -> Handler RepHtml getEchoR theText = do defaultLayout $ do [whamlet|

#{theText}|]

Use a new template file

The last thing to change in order to do things like in a real project is to use another template file.

Just create a new file template/echo.hamlet containing:

#{theText}

and modify the handler Handler/Echo.hs:

getEchoR :: Text -> Handler RepHtml getEchoR theText = do defaultLayout $ do $(widgetFile "echo")

At this point our code is clean. Handler are grouped, we use Data.Text and our views are in templates. It is now time to make a slightly more complex example.

Repeat

Let's make another minimal application. You should see a form containing a text field and a validation button. When you click, the next page present you the content you entered in the field.

First, add a new route:

/new NewR GET POST

This time the path /new will accept GET and POST requests. Add the corresponding new Handler file:

module Handler.New where

import Import

getNewR :: Handler RepHtml getNewR = do defaultLayout $ do $(widgetFile "new")

postNewR :: Handler RepHtml postNewR = do postedText <- runInputPost $ ireq textField "content" defaultLayout $ do $(widgetFile "posted")

Don't forget to declare it inside yosog.cabal and Application.hs.

The only new thing here is the line that get the POST parameter named "content". If you want to know more detail about it and form in general you can take look at the yesod book.

Create the two corresponding templates:

Enter your text

You've just posted

#{postedText}


Get back

And that is all. This time, we used most good practices. We may have used another way to generate the form but this is beyond the scope of this tutorial.

Just try it by clicking here.

Hey! That was easy!

Blog

Now it is time to create a minimal blog.


<%= startTodo %>

  • Display something, show it is protected.
  • Make the same as before, but with an input.
  • Create a minimal blog system.
  • Change template to html5 boilerplate.
  • Use Authentification.

<%= endTodo %>


  1. If you are curious, you can search about the Fibonacci node.js troll. Without any tweaking, Haskell handled this problem perfectly. I tested it myself using yesod instead of Snap. ↩︎