Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fe848b9
Fix typos in "Persistent" chapter
menelaos Feb 1, 2026
3ef9f98
Unify spelling of "JavaScript" in the book
menelaos Feb 3, 2026
973945d
Reflect deprecation of `persistent-template`
menelaos Feb 3, 2026
705f85c
Update to new Conduit API
menelaos Feb 12, 2026
ec9e68b
Remove redundant imports
menelaos Feb 12, 2026
653fdec
Fix quoted field attributes
menelaos Feb 14, 2026
b34da93
Simplify imports for Conduit
menelaos Feb 15, 2026
b46431c
Remove obsolete Upstart example
menelaos Feb 15, 2026
50307b7
Fix typos in "RESTful Content" chapter
menelaos Feb 15, 2026
8ab17a5
Fix spelling of "MIME type"
menelaos Feb 15, 2026
6e38d3e
Fix link to `aeson` documentation
menelaos Feb 15, 2026
cb92975
Add missing language extensions
menelaos Feb 19, 2026
bf8ac56
Fix typo in "Sphinx-based Search" example
menelaos Feb 19, 2026
d59d246
Remove obsolete example in "Scaffolding" chapter
menelaos Feb 19, 2026
b00ff97
Replace `mappend` with `(<>)`
menelaos Feb 19, 2026
3671ba8
Fix typo in the "Understanding a Request" chapter
menelaos Feb 19, 2026
69e3747
Match qualifier names with `yesod-core`
menelaos Feb 19, 2026
aa1a9ff
Reflect updated `PageContent` type in examples
menelaos Feb 20, 2026
a9a347d
Update `PersistValue` to current implementation
menelaos Feb 22, 2026
b7d5270
Update generated code examples
menelaos Feb 22, 2026
a57e33f
Reflect the split of `PersistStore`
menelaos Feb 23, 2026
60a6b45
Reflect the change from `HandlerT` to `HandlerFor`
menelaos Feb 23, 2026
be58ca8
Remove obsolete section about Yesod monads
menelaos Feb 25, 2026
cb65fc3
Prefix unused variable with an underscore
menelaos Feb 25, 2026
d7b16ca
Remove redundant and deprecated `mpsGeneric`
menelaos Feb 25, 2026
1b88949
Add missing type signature
menelaos Feb 25, 2026
b5b9037
Update paragraph about `YesodRunnerEnv`
menelaos Feb 27, 2026
659ce96
Update paragraph about `neverExpires`
menelaos Feb 27, 2026
e1850a4
Unify spelling of "Accept" header
menelaos Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions book/asciidoc/authentication-and-authorization.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ Let's jump right in with an example of authentication. For the Google OAuth auth
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Default (def)
import Data.Text (Text)
import Network.HTTP.Client.Conduit (Manager, newManager)
import Yesod
Expand Down Expand Up @@ -239,15 +238,21 @@ verification link is printed in the console.

[source, haskell]
----
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Monad (join)
import Control.Monad.Logger (runNoLoggingT)
import Data.Maybe (isJust)
Expand All @@ -258,13 +263,12 @@ import Database.Persist.Sqlite
import Database.Persist.TH
import Network.Mail.Mime
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
import Text.Hamlet (shamlet)
import Text.Shakespeare.Text (stext)
import Yesod
import Yesod.Auth
import Yesod.Auth.Email

share [mkPersist sqlSettings { mpsGeneric = False }, mkMigrate "migrateAll"] [persistLowerCase|
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mpsGeneric is deprecated and the default value is False anyway.
Cf. yesodweb/persistent#1348

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
email Text
password Text Maybe -- Password may not be set yet
Expand Down Expand Up @@ -370,7 +374,7 @@ instance YesodAuthEmail App where
mu <- get uid
case mu of
Nothing -> return Nothing
Just u -> do
Just _u -> do
update uid [UserVerified =. True, UserVerkey =. Nothing]
return $ Just uid
getPassword = liftHandler . runDB . fmap (join . fmap userPassword) . get
Expand Down Expand Up @@ -422,7 +426,6 @@ your Yesod typeclass instance. Let's see an example.
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Default (def)
import Data.Text (Text)
import Network.HTTP.Conduit (Manager, newManager, tlsManagerSettings)
import Yesod
Expand All @@ -449,6 +452,7 @@ instance Yesod App where
-- anyone can access other pages
isAuthorized _ _ = return Authorized

isAdmin :: Handler AuthResult
isAdmin = do
mu <- maybeAuthId
return $ case mu of
Expand Down
23 changes: 17 additions & 6 deletions book/asciidoc/blog-example-advanced.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,22 @@ your individual Haskell files.

[source, haskell]
----
{-# LANGUAGE OverloadedStrings, TypeFamilies, QuasiQuotes,
TemplateHaskell, GADTs, FlexibleContexts,
MultiParamTypeClasses, DeriveDataTypeable,
GeneralizedNewtypeDeriving, ViewPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
----

Now our imports.
Expand All @@ -37,7 +49,6 @@ import Database.Persist.Sqlite
, createSqlitePool, runSqlPersistMPool
)
import Data.Time (UTCTime, getCurrentTime)
import Control.Applicative ((<$>), (<*>), pure)
import Data.Typeable (Typeable)
import Control.Monad.Logger (runStdoutLoggingT)
----
Expand Down Expand Up @@ -252,7 +263,7 @@ good idea to always check for pending messages in your defaultLayout function.
mmsg <- getMessage
----

We use widgets to compose together HTML, CSS and Javascript. At the end of the
We use widgets to compose together HTML, CSS and JavaScript. At the end of the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transition from "Javascript" to "JavaScript" was started in #284.

day, we need to unwrap all of that into simple HTML. That's what the
+widgetToPageContent+ function is for. We're going to give it a widget consisting
of the content we received from the individual page (inside), plus a standard
Expand Down
44 changes: 23 additions & 21 deletions book/asciidoc/case-study-sphinx.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ several hundred kilobytes. If we take a non-streaming approach, this can lead
to huge memory usage and slow response times.

So how exactly do we create a streaming response? Yesod provides a helper
function for this case: +responseSourceDB+. This function takes two arguments:
a content type, and a conduit +Source+ providing a stream of blaze-builder
function for this case: +respondSourceDB+. This function takes two arguments:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was probably a mixup between Yesod's respondSource and responseSource in wai-conduit.

a content type, and a Conduit providing a stream of blaze-builder
++Builder++s. Yesod then handles all of the issues of grabbing a database
connection from the connection pool, starting a transaction, and streaming the
response to the user.
Expand All @@ -387,7 +387,7 @@ is:

[source, haskell]
----
renderBuilder :: Monad m => RenderSettings -> Conduit Event m Builder
renderBuilder :: Monad m => RenderSettings -> ConduitT Event Builder m ()
----

In plain English, that means +renderBuilder+ takes some settings (we'll just use
Expand Down Expand Up @@ -472,7 +472,7 @@ We start the document element with an +id+ attribute, start the content, insert
the content, and then close both elements. We use +toPathPiece+ to convert a
+DocId+ into a +Text+ value. Next, we need to be able to convert a stream of
these entities into a stream of events. For this, we can use the built-in
+concatMap+ function from +Data.Conduit.List+: +CL.concatMap entityToEvents+.
+concatMapC+ function from +Conduit+: +concatMapC entityToEvents+.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to functions from the Conduit module since the use of Data.Conduit.List seems to be discouraged these days.


But what we _really_ want is to stream those events directly from the database.
For most of this book, we've used the +selectList+ function, but Persistent
Expand All @@ -482,18 +482,18 @@ the function:

[source, haskell]
----
docSource :: Source (YesodDB Searcher) X.Event
docSource = selectSource [] [] $= CL.concatMap entityToEvents
docSource :: ConduitT () X.Event (YesodDB Searcher) ()
docSource = selectSource [] [] .| concatMapC entityToEvents
----

The $= operator joins together a source and a conduit into a new source. Now
The .| operator combines two Conduits together into a new Conduit. Now
that we have our +Event+ source, all we need to do is surround it with the
document start and end events. With +Source+'s +Monad+ instance, this is a
document start and end events. With +ConduitT+'s +Monad+ instance, this is a
piece of cake:

[source, haskell]
----
fullDocSource :: Source (YesodDB Searcher) X.Event
fullDocSource :: ConduitT () X.Event (YesodDB Searcher) ()
fullDocSource = do
mapM_ yield startEvents
docSource
Expand All @@ -508,33 +508,35 @@ getXmlpipeR :: Handler TypedContent
getXmlpipeR =
respondSourceDB "text/xml"
$ fullDocSource
$= renderBuilder def
$= CL.map Chunk
.| renderBuilder def
.| mapC Chunk
----

=== Full code

[source, haskell]
----
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
import Control.Applicative ((<$>), (<*>))
import Conduit
import Control.Monad (forM)
import Control.Monad.Logger (runStdoutLoggingT)
import Data.Conduit
import qualified Data.Conduit.List as CL
import Data.Maybe (catMaybes)
import Data.Monoid (mconcat)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Lazy.Encoding (decodeUtf8)
import qualified Data.XML.Types as X
import Database.Persist.Sqlite
import Text.Blaze.Html (preEscapedToHtml)
Expand Down Expand Up @@ -719,8 +721,8 @@ getXmlpipeR :: Handler TypedContent
getXmlpipeR =
respondSourceDB "text/xml"
$ fullDocSource
$= renderBuilder def
$= CL.map Chunk
.| renderBuilder def
.| mapC Chunk

entityToEvents :: (Entity Doc) -> [X.Event]
entityToEvents (Entity docid doc) =
Expand All @@ -731,14 +733,14 @@ entityToEvents (Entity docid doc) =
, X.EventEndElement document
]

fullDocSource :: Source (YesodDB Searcher) X.Event
fullDocSource :: ConduitT () X.Event (YesodDB Searcher) ()
fullDocSource = do
mapM_ yield startEvents
docSource
mapM_ yield endEvents

docSource :: Source (YesodDB Searcher) X.Event
docSource = selectSource [] [] $= CL.concatMap entityToEvents
docSource :: ConduitT () X.Event (YesodDB Searcher) ()
docSource = selectSource [] [] .| concatMapC entityToEvents

toName :: Text -> X.Name
toName x = X.Name x (Just "http://sphinxsearch.com/") (Just "sphinx")
Expand Down
24 changes: 7 additions & 17 deletions book/asciidoc/deploying-your-webapp.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ out a safe storage solution for the client session key.
There are two commonly used features in the Yesod world: serving your site over
HTTPS, and placing your static files on a separate domain name. While both of
these are good practices, when combined they can lead to problems if you're not
careful. In particular, most web browsers will not load up Javascript files
careful. In particular, most web browsers will not load up JavaScript files
from a non-HTTPS domain name if your HTML is served from an HTTPS domain name.
In this situation, you'll need to do one of two things:

Expand Down Expand Up @@ -233,23 +233,13 @@ responsible to run your own process. I strongly recommend a monitoring utility
which will automatically restart your application in case it crashes. There are
many great options out there, such as angel or daemontools.

To give a concrete example, here is an Upstart config file. The file must be
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstart is discontinued so I guess it doesn't make much sense to keep this example.

placed in +/etc/init/mysite.conf+:

----
description "My awesome Yesod application"
start on runlevel [2345];
stop on runlevel [!2345];
respawn
chdir /home/michael/sites/mysite
exec /home/michael/sites/mysite/dist/build/mysite/mysite
----

Once this is in place, bringing up your application is as simple as
+sudo start mysite+. A similar systemd configuration file placed in
To give a concrete example, here is a systemd configuration file placed in
+/etc/systemd/system/yesod-sample.service+:

----
[Unit]
Description=My awesome Yesod application

[Service]
ExecStart=/home/sibi/.local/bin/my-yesod-executable
Restart=always
Expand Down Expand Up @@ -344,7 +334,7 @@ A similar approach, without requiring the QtWebkit library, is
wai-handler-launch, which launches a Warp server and then opens up the user's
default web browser. There's a little trickery involved here: in order to know
that the user is still using the site, +wai-handler-launch+ inserts a "ping"
Javascript snippet to every HTML page it serves. It +wai-handler-launch+
JavaScript snippet to every HTML page it serves. It +wai-handler-launch+
doesn't receive a ping for two minutes, it shuts down.

=== CGI on Apache
Expand Down Expand Up @@ -381,7 +371,7 @@ and the last line does the actual rewriting.
=== FastCGI on lighttpd

For this example, I've left off some of the basic FastCGI settings like
mime-types. I also have a more complex file in production that prepends "www."
MIME types. I also have a more complex file in production that prepends "www."
when absent and serves static files from a separate domain. However, this
should serve to show the basics.

Expand Down
22 changes: 9 additions & 13 deletions book/asciidoc/forms.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ solution that addresses the following problems:

* Generate HTML code for displaying the form.

* Generate Javascript to do clientside validation and provide more
* Generate JavaScript to do clientside validation and provide more
user-friendly widgets, such as date pickers.

* Build up more complex forms by combining together simpler forms.
Expand All @@ -20,7 +20,7 @@ solution that addresses the following problems:

The yesod-form package provides all these features in a simple, declarative
API. It builds on top of Yesod's widgets to simplify styling of forms and
applying Javascript appropriately. And like the rest of Yesod, it uses
applying JavaScript appropriately. And like the rest of Yesod, it uses
Haskell's type system to make sure everything is working correctly.

=== Synopsis
Expand All @@ -32,9 +32,8 @@ Haskell's type system to make sure everything is working correctly.
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative ((<$>), (<*>))
import Data.Text (Text)
import Data.Time (Day)
import Data.Text (Text)
import Data.Time (Day)
import Yesod
import Yesod.Form.Jquery

Expand Down Expand Up @@ -555,8 +554,7 @@ we could code something like this.
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative
import Data.Text (Text)
import Data.Text (Text)
import Yesod

data App = App
Expand Down Expand Up @@ -663,7 +661,7 @@ monadic form with an applicative one and the code would still work.
Applicative and monadic forms handle both the generation of your HTML code and
the parsing of user input. Sometimes, you only want to do the latter, such as
when there's an already-existing form in HTML somewhere, or if you want to
generate a form dynamically using Javascript. In such a case, you'll want input
generate a form dynamically using JavaScript. In such a case, you'll want input
forms.

These work mostly the same as applicative and monadic forms, with some differences:
Expand Down Expand Up @@ -693,8 +691,7 @@ you need to correct. With input forms, the user simply gets an error message.
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative
import Data.Text (Text)
import Data.Text (Text)
import Yesod

data App = App
Expand Down Expand Up @@ -853,8 +850,7 @@ Let's see an example of using these two functions:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative
import Data.Text (Text)
import Data.Text (Text)
import Data.Time
import Yesod

Expand Down Expand Up @@ -928,7 +924,7 @@ whether the field is required or optional. The result is six helper functions:
+areq+, +aopt+, +mreq+, +mopt+, +ireq+, and +iopt+.

Forms have significant power available. They can automatically insert
Javascript to help you leverage nicer UI controls, such as a jQuery UI date
JavaScript to help you leverage nicer UI controls, such as a jQuery UI date
picker. Forms are also fully i18n-ready, so you can support a global community
of users. And when you have more specific needs, you can slap on some
validation functions to an existing field, or write a new one from scratch.
5 changes: 2 additions & 3 deletions book/asciidoc/json-web-service.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ This plays out as:
[source, haskell]
----
{-# LANGUAGE OverloadedStrings #-}
import Conduit (runConduit, (.|))
import Control.Exception (SomeException)
import Control.Exception.Lifted (handle)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (Value, encode, object, (.=))
import Data.Aeson.Parser (json)
import Data.ByteString (ByteString)
import Data.Conduit (($$))
import Data.Conduit.Attoparsec (sinkParser)
import Network.HTTP.Types (status200, status400)
import Network.Wai (Application, Response, responseLBS)
Expand All @@ -34,7 +33,7 @@ main = run 3000 app

app :: Application
app req sendResponse = handle (sendResponse . invalidJson) $ do
value <- sourceRequestBody req $$ sinkParser json
value <- runConduit $ sourceRequestBody req .| sinkParser json
newValue <- liftIO $ modValue value
sendResponse $ responseLBS
status200
Expand Down
Loading