yesod-examples-0.4.0: yesod/tutorial/chat.lhs
---
title: Chat -- Tutorials -- Yesod
---
**NOTE: This tutorial requires the development version of Yesod (version 0.4.0). The [tutorial main page]($root/yesod/tutorial/) has instructions on setting up your environment.**
OK, maybe chat is a little over-reaching for this tutorial... but I like typing less, and chat is shorter than message board ;).
In this tutorial, we'll create an app that let's you log in and add messages. The server will display all messages to logged-in users.
> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell #-}
> import Yesod
> import Yesod.Helpers.Auth
> import Control.Concurrent.MVar
> import Data.Monoid (mempty)
> data Message = Message
> { messageAuthor :: String
> , messageContent :: String
> }
Since we want to alter the list of messages, we're going to need a mutable variable. Note that this application- for simplicity- does not store any information on disk, so you'll lose your history on a server restart.
> data Chat = Chat
> { chatMsgs :: MVar [Message]
> , chatAuth :: Auth
> }
Yesod comes baked in with three different authentication methods: OpenId, Rpxnow and e-mail based. For those not familiar with Rpxnow, it's a service that makes it easy to log in through multiple backends, such as Google, Twitter, Yahoo, etc.
The e-mail method allows users to register an e-mail address, get a verification key by e-mail, set password and log in. As you might imagine, this requires some setup in general: usually, you'll have to add some database tables and configure e-mail sending. For testing, Yesod includes inMemoryEmailSettings, which uses an in-memory database and simply outputs verification information to standard error.
> loadChat :: IO Chat
> loadChat = do
> msgs <- newMVar []
> aes <- inMemoryEmailSettings
> return $ Chat msgs Auth
> { authIsOpenIdEnabled = True
> , authRpxnowApiKey = Just "c8043882f14387d7ad8dfc99a1a8dab2e028f690"
> , authEmailSettings = Just aes
> , authFacebook = Just ("134280699924829", "a7685e10c8977f5435e599aaf1d232eb", ["email"])
> }
There are three resource patterns: the homepage, the auth subsite, and the messages page. When you GET the messages page, it will give you the history. POSTing will allow you to add a message. The homepage will have login information.
> mkYesod "Chat" [$parseRoutes|
> / HomeR GET
> /auth AuthR Auth chatAuth
> /messages MessagesR GET POST
> |]
And now let's hit the typeclasses; as usual, we need the Yesod typeclass. Now, we'll also add the YesodAuth typeclass. Like Yesod, it provides default values when possible. Since we need to provide a return URL for OpenID, we now need a valid value for approot instead of just an empty string.
> instance Yesod Chat where
> approot _ = "http://localhost:3000"
> instance YesodAuth Chat where
> defaultDest _ = MessagesR
> defaultLoginRoute _ = HomeR
This basically says "send users to MessagesR on login, and to HomeR when they *need* to login."
Now we'll write the homepage; that funny iframe bit at the bottom comes straight from RPXnow.
> getHomeR :: Handler Chat RepHtml
> getHomeR = applyLayout "Chat Home" mempty [$hamlet|
> %h1 OpenID
> %form!action=@AuthR.OpenIdForward@
> %input!type=text!name=openid
> %input!type=submit!value=Login
> %h1 Email (well, sort of)
> %form!method=post!action=@AuthR.EmailRegisterR@
> %input!type=email!name=email
> %input!type=submit!value=Register
> %h1 Facebook
> %a!href=@AuthR.StartFacebookR@ Facebook Connect
> %h1 Rpxnow
> <iframe src="http://yesod-test.rpxnow.com/openid/embed?token_url=@AuthR.RpxnowR@" scrolling="no" frameBorder="no" allowtransparency="true" style="width:400px;height:240px"></iframe>
> |]
Next, we'll write the GET handler for messages. We use the "requireCreds" to get the user's credentials. If the user it not logged in, they are redirected to the homepage.
> getMessagesR :: Handler Chat RepHtml
> getMessagesR = do
> creds <- requireCreds -- now we know we're logged in
> msgs' <- chatMsgs `fmap` getYesod
> msgs <- liftIO $ readMVar msgs'
> hamletToRepHtml $ template msgs creds
> where
> template msgs creds = [$hamlet|
> !!!
> %html
> %head
> %title Silly Chat Server
> %body
> %p Logged in as $credsIdent.creds$
> %p Your full creds: $show.creds$
> %form!method=post!action=@MessagesR@
> Enter your message:
> %input!type=text!name=message!width=400
> %input!type=submit
> %h1 Messages
> %dl
> $forall msgs msg
> %dt $messageAuthor.msg$
> %dd $messageContent.msg$
> |]
Pretty straight-forward. Now we'll add the post handler.
> postMessagesR :: Handler Chat ()
> postMessagesR = do
> creds <- requireCreds -- now we know we're logged in
> message <- runFormPost' $ stringInput "message"
> msgs <- chatMsgs `fmap` getYesod
> let msg = Message (credsIdent creds) message
> liftIO $ modifyMVar_ msgs $ return . (:) msg
> redirect RedirectTemporary MessagesR
This includes a minor introduction to the Yesod.Form module. The second line in the do block essentially says to get the "message" POST parameter; there must be precisely one parameter and cannot be empty (ie, ""). The modifyMVar_ line simply tacks the new message onto the message MVar, and then we redirect to view the messages.
Finally, we'll do our standard main function.
> main :: IO ()
> main = do
> chat <- loadChat
> basicHandler 3000 chat