Scaffolding and the Site Template
So you’re tired of running small examples, and ready to write a real site? Then you’re at the right chapter. Even with the entire Yesod library at your fingertips, there are still a lot of steps you need to go through to get a production-quality site setup:
Config file parsing
Signal handling (*nix)
More efficient static file serving
A good file layout
The scaffolded site is a combination of many Yesoders' best practices combined together into a ready-to-use skeleton for your sites. It is highly recommended for all sites. This chapter will explain the overall structure of the scaffolding, how to use it, and some of its less-than-obvious features.
For the most part, this chapter will not contain code samples. It is recommended that you follow along with an actual scaffolded site.
How to Scaffold
The yesod-bin package installs an executable (conveniently named
yesod as well). This executable provides a few commands (run stack
exec — yesod --help to get a list). In order to generate a
scaffolding, the command is stack new my-project
yesod-postgres. This will generate a scaffolding site with a postgres
database backend in a directory named
my-project. You can see the
other available templates using the command stack templates.
The key thing differing in various available templates (from the stack templates command) is the database backend. You get a few choices here, including SQL and MongoDB backends, as well as "yesod-simple" template to include no database support. This last option also turns off a few extra dependencies, giving you a leaner overall site. The remainder of this chapter will focus on the scaffoldings for one of the database backends. There will be minor differences for the yesod-simple backend.
After creating your files, install the yesod command line tool inside the project: stack build yesod-bin cabal-install. Then do a stack build inside the directory. In particular, the commands provided will ensure that any missing dependencies are built and installed.
Finally, to launch your development site, you would use
stack exec — yesod devel. This site will automatically rebuild and reload
whenever you change your code.
The scaffolded site is built as a fully cabalized Haskell package. In addition to source files, config files, templates, and static files are produced as well.
Whether directly using
stack, or indirectly using
stack exec — yesod devel, building
your code will always go through the cabal file. If you open the file, you’ll
see that there are both library and executable blocks. If the
flag is turned on, then the executable block is not built. This is how
devel calls your app. Otherwise, the executable is built.
library-only flag should only be used by
yesod devel; you should never
be explicitly passing it into
cabal. There is an additional flag,
allows cabal to build an executable, but turns on some of the same features as
the library-only flag, i.e., no optimizations and reload versions of the
Shakespearean template functions.
In general, you will build as follows:
When developing, use
When building a production build, perform
stack clean && stack build. This will produce an optimized executable in your
distfolder. (You can also use the
yesod ketercommand for this.)
You might be surprised to see the
NoImplicitPrelude extension. We turn this
on since the site includes its own module,
Import, with a few changes to the
Prelude that make working with Yesod a little more convenient.
The last thing to note is the exposed-modules list. If you add any modules to your application, you must update this list to get yesod devel to work correctly. Unfortunately, neither Cabal nor GHC will give you a warning if you forgot to make this update, and instead you’ll get a very scary-looking error message from yesod devel.
Routes and entities
Multiple times in this book, you’ve seen a comment like “We’re declaring our routes/entities with quasiquotes for convenience. In a production site, you should use an external file.” The scaffolding uses such an external file.
Routes are defined in
config/routes, and entities in
have the exact same syntax as the quasiquoting you’ve seen throughout the book,
yesod devel knows to automatically recompile the appropriate modules when
these files change.
models files is referenced by
Model.hs. You are free to declare
whatever you like in this file, but here are some guidelines:
Any data types used in entities must be imported/declared in
Model.hs, above the
Helper utilities should either be declared in
Import.hsor, if very model-centric, in a file within the
Modelfolder and imported into
Foundation and Application modules
mkYesod function which we have used throughout the book declares a few
Route render function
The dispatch function refers to all of the handler functions, and therefore all of those must either be defined in the same file as the dispatch function, or be imported into the module containing the dispatch function.
Meanwhile, the handler functions will almost certainly refer to the route type. Therefore, they must be either in the same file where the route type is defined, or must import that file. If you follow the logic here, your entire application must essentially live in a single file!
Clearly this isn’t what we want. So instead of using
mkYesod, the scaffolding
site uses a decomposed version of the function.
mkYesodData, which declares the route type and render function. Since it does
not declare the dispatch function, the handler functions need not be in scope.
Foundation.hs, and all the handler modules import
Application.hs, we call
mkYesodDispatch, which creates our dispatch
function. For this to work, all handler functions must be in scope, so be sure
to add an import statement for any new handler modules you create.
Other than that,
Application.hs is pretty simple. It provides two primary
getApplicationDev is used by
yesod devel to launch your app, and
makeApplication is used by the executable to launch.
Foundation.hs is much more exciting. It:
Declares your foundation datatype
Declares a number of instances, such as
Imports the messages files. If you look for the line starting with
mkMessage, you will see that it specifies the folder containing the messages (
messages/) and the default language (
en, for English).
This is the right file for adding extra instances for your foundation, such as
We’ll be referring back to this file later, as we discussed some of the special
Yesod typeclass methods.
Import module was born out of a few commonly recurring patterns.
I want to define some helper functions (maybe the
<> = mappendoperator) to be used by all handlers.
I’m always adding the same five import statements (
Control.Applicative, etc) to every handler module.
I want to make sure I never use some evil function (
readFile, …) from
The solution is to turn on the
NoImplicitPrelude language extension,
re-export the parts of
Prelude we want, add in all the other stuff we want,
define our own functions as well, and then import this file in all handlers.
Handler modules should go inside the
Handler folder. The site template
includes one module:
Handler/Home.hs. How you split up your handler functions
into individual modules is your decision, but a good rule of thumb is:
Different methods for the same route should go in the same file, e.g.
Related routes can also usually go in the same file, e.g.,
Of course, it’s entirely up to you. When you add a new handler file, make sure you do the following:
Add it to version control (you are using version control, right?).
Add it to the cabal file.
Add it to the
Put a module statement at the top, and an
import Importline below it.
You can use the
stack exec — yesod add-handler command to automate the last three steps.
don’t want to have to remember to include those Lucius and Julius files
manually every time you refer to a Hamlet file. For this, the site template
If you have a handler function:
getHomeR = defaultLayout $(widgetFile "homepage")
, Yesod will look for the following files:
If any of those files are present, they will be automatically included in the output.
One of the first things you’re going to want to customize is the look of your site. The layout is actually broken up into two files:
templates/default-layout-wrapper.hamletcontains just the basic shell of a page. This file is interpreted as plain Hamlet, not as a Widget, and therefore cannot refer to other widgets, embed i18n strings, or add extra CSS/JS.
templates/default-layout.hamletis where you would put the bulk of your page. You must remember to include the
widgetvalue in the page, as that contains the per-page contents. This file is interpreted as a Widget.
Also, since default-layout is included via the
widgetFile function, any
Lucius, Cassius, or Julius files named
default-layout.* will automatically be
included as well.
The scaffolded site automatically includes the static file subsite, optimized for serving files that will not change over the lifetime of the current build. What this means is that:
When your static file identifiers are generated (e.g.,
mylogo_png), a query-string parameter is added to it with a hash of the contents of the file. All of this happens at compile time.
yesod-staticserves your static files, it sets expiration headers far in the future, and includes an etag based on a hash of your content.
Whenever you embed a link to
mylogo_png, the rendering includes the query-string parameter. If you change the logo, recompile, and launch your new app, the query string will have changed, causing users to ignore the cached copy and download a new version.
Additionally, you can set a specific static root in your
Settings.hs file to
serve from a different domain name. This has the advantage of not requiring
transmission of cookies for static file requests, and also lets you offload
static file hosting to a CDN or a service like Amazon S3. See the comments in
the file for more details.
Caching works properly.
The purpose of this chapter was not to explain every line that exists in the scaffolded site, but instead to give a general overview to how it works. The best way to become more familiar with it is to jump right in and start writing a Yesod site with it.