483 lines
15 KiB
Markdown
483 lines
15 KiB
Markdown
-----
|
|
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][warpbench].
|
|
Haskell is compiled and is incredibly faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython].
|
|
Haskell handle parallel tasks perfectly. For example even better than `node.js`[^nodejstroll].
|
|
|
|
[^nodejstroll]: If you are curious, you can search about [the Fibonacci node.js troll](http://www.unlimitednovelty.com/2011/10/nodejs-has-jumped-shark.html). Without any tweaking, [Haskell handled this problem perfectly](http://mathias-biilmann.net/posts/2011/10/is-haskell-the-cure). I tested it myself using yesod instead of Snap.
|
|
|
|
[haskellvsruby]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv
|
|
[haskellvspython]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3
|
|
|
|
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](http://heroku.com) for Haskell (even if [Greg Weber did it](http://www.yesodweb.com/blog/2011/07/haskell-on-heroku), 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](http://happstack.com)
|
|
2. [Snap](http://snapframework.com)
|
|
3. [Yesod](http://yesodweb.com)
|
|
|
|
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.
|
|
|
|
[warpbench]: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks
|
|
|
|
## Before the real start
|
|
|
|
### Install
|
|
|
|
First you need to install [Haskell][haskell]. The recommended way to do this is to download the [Haskell Platform][haskellplatform].
|
|
|
|
[haskell]: http://www.haskell.org
|
|
[haskellplatform]: http://www.haskell.org/platform
|
|
|
|
Then you need to install yesod.
|
|
|
|
<code class="zsh">
|
|
> cabal update
|
|
> cabal install yesod cabal-dev
|
|
</code>
|
|
|
|
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:
|
|
|
|
<code class="zsh">
|
|
> yesod init
|
|
</code>
|
|
|
|
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:
|
|
|
|
<code class="zsh">
|
|
> cd yosog
|
|
> cabal-dev install && yesod --dev devel
|
|
</code>
|
|
|
|
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`](http://localhost:3000)
|
|
|
|
Congratulation! Yesod works!
|
|
|
|
<blockquote>
|
|
|
|
Note: if something is messed up use the following command line:
|
|
|
|
<code class="zsh">
|
|
\rm -rf dist/* ; cabal-dev install && yesod --dev devel
|
|
</code>
|
|
|
|
</blockquote>
|
|
|
|
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.
|
|
|
|
<code class="zsh" file=".gitignore">
|
|
cabal-dev
|
|
dist
|
|
.static-cache
|
|
static/tmp
|
|
*.sqlite3
|
|
</code>
|
|
|
|
Then initialize your git repository:
|
|
|
|
<code class="zsh">
|
|
> git init .
|
|
> git add .
|
|
> git commit -a -m "Initial yesod commit"
|
|
</code>
|
|
|
|
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`](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`:
|
|
|
|
<code class="zsh">
|
|
/static StaticR Static getStatic
|
|
/auth AuthR Auth getAuth
|
|
|
|
/favicon.ico FaviconR GET
|
|
/robots.txt RobotsR GET
|
|
|
|
/ RootR GET
|
|
</code>
|
|
|
|
We want to add a route of the form `/echo/[anything]` somehow and do some action with this.
|
|
We add the following:
|
|
|
|
<pre>
|
|
/echo/#String EchoR GET
|
|
</pre>
|
|
|
|
This line contains three elements: the <sc>url</sc> 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.
|
|
|
|
<pre>
|
|
Application.hs:31:1: Not in scope: `getEchoR'
|
|
</pre>
|
|
|
|
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:
|
|
|
|
<code class="haskell">
|
|
getEchoR :: String -> Handler RepHtml
|
|
getEchoR theText = do
|
|
defaultLayout $ do
|
|
[whamlet|<h1>#{theText}|]
|
|
</code>
|
|
|
|
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!`](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/<a>I'm <script>alert("Bad!");)<% "</script>" %>
|
|
|
|
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 "<code><</code>" by "`<`".
|
|
Thanks to yesod, most of tedious string transformation job is done for us.
|
|
|
|
<code class="zsh">
|
|
"http://localhost:3000/echo/some%20text<a>" :: URL
|
|
↓
|
|
"some text<a>" :: String
|
|
↓
|
|
"some text <a>" :: HTML
|
|
</code>
|
|
|
|
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:
|
|
|
|
<code class="haskell">
|
|
module Handler.Echo where
|
|
|
|
import Import
|
|
|
|
getEchoR :: String -> Handler RepHtml
|
|
getEchoR theText = do
|
|
defaultLayout $ do
|
|
[whamlet|<h1>#{theText}|]
|
|
</code>
|
|
|
|
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:
|
|
|
|
<pre>
|
|
Handler.Echo
|
|
</pre>
|
|
|
|
We must also declare the new Handler module inside `Application.hs`.
|
|
Just after the "`import Handler.Root`", add:
|
|
|
|
<code class="haskell">
|
|
import Handler.Echo
|
|
</code>
|
|
|
|
### `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:
|
|
|
|
<code class="diff">
|
|
import Data.Text
|
|
</code>
|
|
|
|
And also we must modify `config/routes` and our handler accordingly. Replace `#String` by `#Text` in `config/routes`:
|
|
|
|
<pre>
|
|
/echo/#Text EchoR GET
|
|
</pre>
|
|
|
|
And do the same in `Handler/Echo.hs`:
|
|
|
|
<code class="haskell" file="Echo.hs">
|
|
module Handler.Echo where
|
|
|
|
import Import
|
|
|
|
getEchoR :: Text -> Handler RepHtml
|
|
getEchoR theText = do
|
|
defaultLayout $ do
|
|
[whamlet|<h1>#{theText}|]
|
|
</code>
|
|
|
|
### 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:
|
|
|
|
<code class="haskell" file="echo.hamlet">
|
|
<h1> #{theText}
|
|
</code>
|
|
|
|
and modify the handler `Handler/Echo.hs`:
|
|
|
|
<code class="haskell">
|
|
getEchoR :: Text -> Handler RepHtml
|
|
getEchoR theText = do
|
|
defaultLayout $ do
|
|
$(widgetFile "echo")
|
|
</code>
|
|
|
|
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:
|
|
|
|
<code class="zsh">
|
|
/new NewR GET POST
|
|
</code>
|
|
|
|
This time the path /new will accept GET and POST requests. Add the corresponding new Handler file:
|
|
|
|
<code class="haskell" file="New.hs">
|
|
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")
|
|
</code>
|
|
|
|
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](http://www.yesodweb.com/book/forms).
|
|
|
|
Create the two corresponding templates:
|
|
|
|
<code class="html" file="new.hamlet">
|
|
<h1> Enter your text
|
|
<form method=post action=@{NewR}>
|
|
<input type=text name=content>
|
|
<input type=submit>
|
|
</code>
|
|
|
|
<code class="html" file="posted.hamlet">
|
|
<h1>You've just posted
|
|
<p>#{postedText}
|
|
<hr>
|
|
<p><a href=@{NewR}>Get back
|
|
</code>
|
|
|
|
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](http://localhost:3000/new).
|
|
|
|
Hey! That was easy!
|
|
|
|
## Blog
|
|
|
|
Now it is time to create a minimal blog.
|
|
|
|
|
|
---
|
|
|
|
<%= startTodo %>
|
|
|
|
<ul>
|
|
<li> Display something, show it is protected.
|
|
</li><li> Make the same as before, but with an input.
|
|
</li><li> Create a minimal blog system.
|
|
</li><li> Change template to html5 boilerplate.
|
|
</li><li> Use Authentification.
|
|
</li>
|
|
</ul>
|
|
|
|
<%= endTodo %>
|