Greetings and salutations!

Todays’ post will be about – you guessed it – typesafe routes in Yesod. First, let’s talk a little about what does a “typesafe route” actually mean. Type safety is related to compile time (perhaps with exception for dependent types, but let’s not dig into it for now), so all guarantees you can get are compile-time guarantees. Compile time guarantees are related to the structure of data rather than the data itself. So, for example, if we used a typesafe route for the example from previous post (/project/#Int) it would prevent us from directing to /project/phew, but not from /project/423, regardless whether /project/423 did actually exist – it is not possible to decide in compile time, and it might exist, so the route is allowed.

And there is one more drawback – typesafe routes are type-safe only in scope of your site (and of course do not guarantee reachability, network connection etc.). It might be a lot or a little, depending on your use case – still, it’s more than other frameworks offer out-of-the-box. What’s important is to remember that – while they might simplify your life – they are no magical way to solve dead link problems. And of course 404s will still occur, so usual testing is still needed.

Enough talking, let’s write some code. Today we’re going to make a link-per-project on the /projects route. We already have the routes both for main projects page and for each project (no content though!), so today we’re just going to link them together.

First, let’s extract the projects to a separate file – in the end it’ll be a database, but for now a separate module is sufficient. Let’s create a new top-level directory, Database, a module Projects there and export projects constant from there:

module Database.Projects (projects) where
import Import

projects::[(Int, Text)]
projects = [(1, "Project 1"), (2, "Project 2"), 
  (4, "My extra project"), (5, "Project 10")]::[(Int, Text)]

then add an import to both Projects.hs and Project.hs:
import Database.Projects(projects)
.

In Projects.hs remove projects = ["Project 1", "Project 2", "My extra project", "Project 10"]::[Text] (it is loaded from Database.Projects module) and in projects.hamlet change projectName variable assignment to a tuple: (projectId, projectName) (compiler will complain that projectId is not used, but we’ll use it in a minute). You can check that everything still works fine.

Now let’s change Project.hs file. We’re going to display both title and id of the project. Database.Projects is already loaded, but there is a little trick: it might be that requested project does not exist. Ideally we should return a 404: NotFound in such case, but for now we can live with some default behavior (no worries – we’re gonna raise a 404 in the next post)

getProjectR projectId = defaultLayout $ do
  setTitle $ ("Project " ++ (createTitle projectId))
  $(widgetFile "project")
  where
    createTitle projId = (toHtml projId) ++ projectName
    projectName = toHtml $ maybe "" snd projectEntry
    projectEntry = find (\x -> (fst x) == projectId) projects

this might look a little complicated, but the toughest thing here is handling the default case. Let’s also add 

Name: #{projectName}

to project.hamlet, so that it’ll be easier to see if it works. Check that everything works fine – it should.

And now, time for the clue – routes. They’re quite simple – just change

<li .list-group-item>#{projectName}

to

<li .list-group-item>
  <a href="@{ProjectR projectId}">#{projectName}</a>

in projects.hamlet.

And that’s it. Congratulations, you’ve just created a set of type safe routes in Yesod! Next episode – about handling failure cases – comes still this week.

Stay tuned!