scratch/multi/blog/Yesod-tutorial-for-newbies.md

476 lines
15 KiB
Markdown
Raw Normal View History

2011-12-28 16:15:55 +00:00
-----
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
2012-01-06 14:04:34 +00:00
macros:
html5: '<span class="sc">html5</span>'
html: '<span class="sc">html</span>'
2011-12-28 16:15:55 +00:00
-----
2012-01-05 14:50:50 +00:00
<%= blogimage("flying_neo.jpg","Neo Flying at warp speed") %>
2011-12-28 16:15:55 +00:00
begindiv(intro)
2012-01-06 15:38:52 +00:00
en: <%= tldr %> A simple yesod tutorial. Yesod is an Haskell web framework. You shouldn't need to know Haskell.
2011-12-28 16:15:55 +00:00
2012-01-06 15:38:52 +00:00
fr: <%= tlal %> Un tutoriel pour yesod, un framework web Haskell. Vous ne devriez pas avoir besoin de savoir programmer en Haskell. Par contre je suis désolé pour les francophones, mais je n'ai pas eu le courage de traduire cet article en Français.
2011-12-28 16:15:55 +00:00
2012-01-06 15:38:52 +00:00
> <center><sc><b>Table of content</b></sc></center>
>
2011-12-30 22:03:41 +00:00
> * Table of Content (generated)
2011-12-29 16:05:05 +00:00
> {:toc}
2011-12-28 16:15:55 +00:00
2011-12-29 16:05:05 +00:00
enddiv
2011-12-28 16:15:55 +00:00
2012-01-04 15:43:59 +00:00
Haskell is incredible and you should consider to use it to make your next web application. Why?
2011-12-28 16:15:55 +00:00
2012-01-06 14:04:34 +00:00
<%= blogimage("haskell-benchmark.png","Impressive Haskell Benchmark") %>
2012-01-05 14:50:50 +00:00
2012-01-06 15:38:52 +00:00
My main reason to look at Haskell is its efficiency (see [Snap Benchmark][snapbench] _&_ [Warp Benchmark][warpbench][^benchmarkdigression]).
2012-01-06 14:04:34 +00:00
Haskell is compiled and is an order of magnitude faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython][^speeddigression].
2012-01-06 15:38:52 +00:00
Haskell handle parallel tasks perfectly. For example even better than node.js[^nodejstroll].
2012-01-05 14:50:50 +00:00
2012-01-04 15:43:59 +00:00
Its type system gives the feeling of using an interpreted language.
Haskell has many more great properties, one of the best being:
2012-01-05 15:30:24 +00:00
> "If your program compile it will be very close to what the programmer intended".
2012-01-04 15:43:59 +00:00
2012-01-05 15:30:24 +00:00
From the pure technical "point of vue", Haskell seems to be the perfect web development tool.
2012-01-04 15:43:59 +00:00
2012-01-06 15:38:52 +00:00
Weaknesses of Haskell certainly won't be technical but human:
2012-01-04 15:43:59 +00:00
- Hard to grasp Haskell
2012-01-05 15:30:24 +00:00
- 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") %>
2012-01-04 15:43:59 +00:00
I don't say these are not important drawbacks.
2012-01-06 15:38:52 +00:00
But Haskell is certainly the best choice to create your new web application which could handle thousands of connexions in parallel.
Considering efficiency, security and quality, I believe Haskell is the best choice.
Furthermore, not only the Haskell community is excellent, but Haskell is a great language and learning it will certainly make you a better programmer.
2012-01-05 15:30:24 +00:00
2012-01-06 15:38:52 +00:00
Haskell is not the only choice to make.
The easiest path to create a web application is certainly to choose a web framework which has made a lot of work for us.
Actually there are three main choices:
2011-12-28 16:15:55 +00:00
1. [Happstack](http://happstack.com)
2. [Snap](http://snapframework.com)
3. [Yesod](http://yesodweb.com)
2011-12-30 22:03:41 +00:00
I don't think there is a real winner between these three framework.
The choice I made for yesod is highly subjective.
2012-01-06 15:38:52 +00:00
I had the feeling yesod make a better job at helping newcomers.
Furthermore, apparently the yesod team is the most active.
But as I said before, I might be wrong has it is a matter of feeling.
2011-12-29 16:05:05 +00:00
2012-01-05 14:50:50 +00:00
<%= blogimage("owl_draw.png","1. Draw some circles. 2. Draw the rest of the fucking owl") %>
2012-01-06 15:38:52 +00:00
Why did I write this article?
The yesod documentation does an excellent job to explain you both some very minimal example and digging into the details.
But I missed an intermediate tutorial.
I tried to pass on the Haskell language and focus on the Yesod framework.
If you are not used to Haskell, some syntax details may feel strange.
Please, don't try to understand the details right now.
2012-01-05 14:50:50 +00:00
Haskell is a very complex language and could suck all your energy if you want to dig too early.
2011-12-28 16:15:55 +00:00
2012-01-06 15:38:52 +00:00
During this tutorial you'll install, initialize and configure your first yesod project.
2011-12-30 22:03:41 +00:00
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.
2012-01-06 15:38:52 +00:00
Just after there will be a more standard real world example. A minimal blog system.
Good read.
2011-12-29 16:05:05 +00:00
[warpbench]: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks
2012-01-06 14:04:34 +00:00
[snapbench]: http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks
2012-01-06 15:38:52 +00:00
[^benchmarkdigression]: There are many to say about these benchmarks. But in the end Haskell should be far faster.
2012-01-06 14:04:34 +00:00
[^speeddigression]: Generally _high level_ Haskell is slower than C, but _low level_ Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Nonetheless it is not comparable to Ruby/Python.
[^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
2011-12-28 16:15:55 +00:00
2012-01-02 14:39:00 +00:00
## Before the real start
### Install
2011-12-28 16:15:55 +00:00
2011-12-30 22:03:41 +00:00
First you need to install [Haskell][haskell]. The recommended way to do this is to download the [Haskell Platform][haskellplatform].
2011-12-28 16:15:55 +00:00
[haskell]: http://www.haskell.org
[haskellplatform]: http://www.haskell.org/platform
2011-12-30 22:03:41 +00:00
Then you need to install yesod.
2011-12-28 16:15:55 +00:00
<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.
2012-01-02 14:39:00 +00:00
### Initialize
2011-12-28 16:15:55 +00:00
2011-12-30 22:03:41 +00:00
Open a terminal and type:
2011-12-28 16:15:55 +00:00
<code class="zsh">
> yesod init
</code>
2011-12-30 22:03:41 +00:00
Enter your name, name the project `yosog` and the name of the Foundation as `Yosog`, then choose `sqlite`.
2011-12-28 16:15:55 +00:00
Perfect. Now you can start the development cycle:
<code class="zsh">
> cd yosog
> cabal-dev install && yesod --dev devel
</code>
2011-12-30 22:03:41 +00:00
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:
2011-12-28 16:15:55 +00:00
[`http://localhost:3000`](http://localhost:3000)
2012-01-05 15:49:30 +00:00
Congratulation! Yesod works!
2011-12-30 22:03:41 +00:00
2012-01-05 15:49:30 +00:00
<blockquote>
2012-01-06 15:38:52 +00:00
Note: if something is messed up use the following command line inside the project directory.
2012-01-05 15:49:30 +00:00
<code class="zsh">
\rm -rf dist/* ; cabal-dev install && yesod --dev devel
</code>
</blockquote>
2012-01-02 14:39:00 +00:00
2011-12-30 22:03:41 +00:00
Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.
2011-12-28 16:15:55 +00:00
2012-01-02 14:39:00 +00:00
### Configure git
2011-12-28 16:15:55 +00:00
2012-01-05 15:49:30 +00:00
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`.
2011-12-28 16:15:55 +00:00
2011-12-30 22:03:41 +00:00
Copy this `.gitignore` file into the `yosog` folder.
2011-12-28 16:15:55 +00:00
<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>
2011-12-30 22:03:41 +00:00
Now we are almost ready to start.
2011-12-28 16:15:55 +00:00
2012-01-02 14:39:00 +00:00
### A last point
2011-12-28 16:15:55 +00:00
2012-01-05 15:49:30 +00:00
Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000.
2011-12-28 16:15:55 +00:00
If we modify a file inside this directory, yesod should try
2011-12-30 22:03:41 +00:00
to recompile as fast as possible the site.
2011-12-28 16:15:55 +00:00
Instead of explaining the role of every file,
2012-01-06 15:38:52 +00:00
let's focus only on the important files/directories for this tutorial:
2011-12-28 16:15:55 +00:00
1. `config/routes`
2. `Handler/`
3. `templates/`
2011-12-30 22:03:41 +00:00
4. `config/models`
2011-12-28 16:15:55 +00:00
Obviously:
2012-01-05 16:05:56 +00:00
| `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). |
2011-12-28 16:15:55 +00:00
2012-01-05 15:49:30 +00:00
During this tutorial we'll modify other files as well,
2012-01-06 14:04:34 +00:00
but we won't explore them in detail.
Now, it is the time to start the interesting part.
2011-12-28 16:15:55 +00:00
2012-01-02 14:39:00 +00:00
## Echo
2011-12-29 16:05:05 +00:00
2011-12-30 22:03:41 +00:00
To verify the quality of the security of the yesod framework, let's make a minimal echo application.
2011-12-29 16:05:05 +00:00
Our goal:
2012-01-06 15:38:52 +00:00
Make a server that when accessed `/echo/[some text]` should return a web page containing "some text" inside an `h1` bloc.
2011-12-30 22:03:41 +00:00
2012-01-06 15:38:52 +00:00
First, we must declare URL of the form `/echo/...` are meaningful.
2011-12-28 16:15:55 +00:00
2011-12-29 16:05:05 +00:00
Let's take a look at the file `config/routes`:
2011-12-28 16:15:55 +00:00
<code class="zsh">
2011-12-29 16:05:05 +00:00
/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.
2011-12-30 22:03:41 +00:00
But this is the standard convention, then let's use it.
2011-12-29 16:05:05 +00:00
2011-12-30 22:03:41 +00:00
If you save `config/routes`, you should see your terminal in which you launched `yesod devel` activate and certainly displaying an error message.
2011-12-29 16:05:05 +00:00
<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}|]
2011-12-28 16:15:55 +00:00
</code>
2011-12-30 22:03:41 +00:00
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.
2011-12-29 16:05:05 +00:00
After saving the file, you should see yesod recompile the application.
When the compilation is finished you'll see the message: `Starting devel application`.
2011-12-30 22:03:41 +00:00
Now you can visit: [`http://localhost:3000/echo/Yesod%20rocks!`](http://localhost:3000/echo/Yesod%20rocks!)
2011-12-29 16:05:05 +00:00
TADA! It works.
2011-12-30 22:03:41 +00:00
### Secure?
2012-01-05 14:50:50 +00:00
<%= blogimage("neo_bullet_proof.jpg","Neo stops a myriad of bullets") %>
2011-12-30 22:03:41 +00:00
Let's try to attack our website by entering a text with special characters:
2011-12-29 16:05:05 +00:00
2011-12-30 00:18:14 +00:00
[`http://localhost:3000/echo/<a>I'm <script>alert("Bad!");`](http://localhost:3000/echo/<a>I'm <script>alert("Bad!");)<% "</script>" %>
2011-12-29 16:05:05 +00:00
2011-12-30 22:03:41 +00:00
All should work better than expected.
2011-12-29 16:05:05 +00:00
The special characters are protected for us.
2011-12-30 22:03:41 +00:00
If you have a malicious user, he could not hide some bad script inside his login name for example.
2011-12-29 16:05:05 +00:00
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 "`&lt;`".
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 &lt;a&gt;" :: HTML
</code>
That was the first very minimal example, and we already
verified Yesod protect us from many common errors.
2011-12-30 22:03:41 +00:00
Then not only Yesod is fast, it is also relatively secure.
2011-12-29 16:05:05 +00:00
2012-01-02 14:39:00 +00:00
### Cleaning up
2011-12-29 16:05:05 +00:00
2011-12-30 16:14:57 +00:00
This first example was nice, but for simplicity reason we didn't used best practices.
2011-12-29 16:05:05 +00:00
2011-12-30 16:14:57 +00:00
First we will separate the handler code into different files.
After that we will use `Data.Text` instead of `String`.
2011-12-30 22:03:41 +00:00
Finally we'll use a template file to better separate our view.
2011-12-29 16:05:05 +00:00
2011-12-30 16:14:57 +00:00
### 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>
2012-01-02 14:39:00 +00:00
### `Data.Text`
2011-12-30 16:14:57 +00:00
Now our handler is separated in another file.
2012-01-02 14:39:00 +00:00
It is a good practice to use `Data.Text` instead of `String`.
2011-12-30 16:14:57 +00:00
2012-01-02 14:39:00 +00:00
To declare we will use the type `Data.Text` we modify the file `Foundation.hs`.
2011-12-30 16:14:57 +00:00
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>
2011-12-29 16:05:05 +00:00
2012-01-02 14:39:00 +00:00
At this point our code is clean.
Handler are grouped, we use `Data.Text` and our views are in templates.
2011-12-30 22:03:41 +00:00
It is now time to make a slightly more complex example.
2012-01-02 14:39:00 +00:00
## 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.
2011-12-29 16:05:05 +00:00
---
<%= 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.
2012-01-06 14:04:34 +00:00
</li><li> Change template to %html5 boilerplate.
2011-12-29 16:05:05 +00:00
</li><li> Use Authentification.
</li>
2011-12-28 16:15:55 +00:00
</ul>
2011-12-29 16:05:05 +00:00
<%= endTodo %>