So far, there is a web server (servant
) that can send arbitrary HTML from Haskell (generated with blaze-html
); and there is a database of registrations, also accessible from Haskell.
It's time to tie them together.
In this post, I'll make read-only registrations available through a web interface. (but with not much protection, so don't put your real details in the database along with a column for the name of your first pet.)
I'm going to assume that there are already some records magically in the database - for example, by raw INSERT
statements from a previous post.
The URL space for the servant
webserver has two URL paths: /ping
and /htmlcode
. To those, I'm going to add new URLs that look like registration/1
, registration/2
, and so on.
Each URL will return a view of a registration in the database - that number on the end, /1
, /2
, ... will identify a record in the SQL database using the id
column.
First modify the API description to add in this new piece of URL space:
type RegistrationAPI = "registration" :> S.Capture "id" Integer :> S.Get '[SB.HTML] B.Html type API = PingAPI :<|> HtmlPingAPI :<|> RegistrationAPI
RegistrationAPI
then means something like: match a path segment called registration, then capture whatever the next path segment is, and this will return some HTML generated by blaze-html
.
Try compiling this and you'll get a lovely Haskell style type error, because this API
doesn't match up with the implementation, server
.
Here's a simple handleRegistration
that will return only the captured registration identifier, so you can see how the value flows from the URL to Haskell code:
handleRegistration :: Integer -> S.Handler B.Html handleRegistration identifier = return $ B.docTypeHtml $ do B.body $ B.p $ do "Registration ID is " B.toHtml (show identifier)
You can figure out how to splice that onto the end of server
yourself, and then try loading a URL
such as http://localhost:8080/registration/1 - in fact, you should be able to use any Integer
instead of that 1
.
Next, I want that handler to read in a Registration
from the database, and use that to generate the
response HTML.
There are a few imports needed at the top of app/Main.hs
to bring various needed pieces into scope. Also add postgresql-simple
to the dependencies for main executable - previously it was only needed on the library component, because it was only used there.
import Registration import Control.Exception (bracket) import Control.Monad.IO.Class (liftIO) import qualified Database.PostgreSQL.Simple as PG
Then handleRegistration can look like:
handleRegistration :: Integer -> S.Handler B.Html handleRegistration identifier = do [registration] <- liftIO $ bracket (PG.connectPostgreSQL "user='postgres'") PG.close $ \conn -> do PG.query conn "SELECT firstname, lastname, dob FROM registration WHERE id = ?" [identifier] :: IO [Registration] return $ B.docTypeHtml $ do B.body $ do B.h1 $ do "Registration " B.toHtml (show identifier) B.p $ do "First name: " B.toHtml (firstname registration) B.p $ do "Last name: " B.toHtml (lastname registration) B.p $ do "Date of Birth: " B.toHtml (dob registration)
Now, you can view registrations in a browser, using URLs like localhost:8080/registration/1 ... where /1 is the ID value for a registration from the database registration table. (Use SELECT
in psql
to see the available values for your particular database: they'll usually start at 1 and go upwards, but if you've deleted records then you might see other values.
If you use an invalid registration identifier (perhaps because it isn't a Haskell Integer
or because no such registration exists, you should get an HTTP 500 server error response.
Next, I'll talk about making these fields editable.
commit b5d29063 gives the changes for this post.
No comments:
Post a Comment