Web Development

49.1 Servant: APIs as Types

From the same API type, Servant can derive clients (servant-client), OpenAPI/Swagger docs (servant-openapi3), and type-safe links. The handler types are checked against the API.

{-# LANGUAGE DataKinds, TypeOperators, DeriveGeneric, DeriveAnyClass #-}
import Servant
import Network.Wai.Handler.Warp (run)
import Data.Aeson (ToJSON, FromJSON)
import GHC.Generics (Generic)
import Data.IORef
import Control.Monad.IO.Class (liftIO)

data Todo = Todo {todoId :: Int, title :: String, done :: Bool}
  deriving (Show, Generic, ToJSON, FromJSON)

type TodoAPI =
       "todos" :> Get '[JSON] [Todo]
  :<|> "todos" :> Capture "id" Int :> Get '[JSON] Todo
  :<|> "todos" :> ReqBody '[JSON] Todo :> Post '[JSON] Todo
  :<|> "health" :> Get '[PlainText] String

server :: IORef [Todo] -> Server TodoAPI
server ref = listTodos :<|> getTodo :<|> createTodo :<|> health
  where
    listTodos = liftIO (readIORef ref)
    getTodo i = do
      todos <- liftIO (readIORef ref)
      case filter ((== i) . todoId) todos of
        (t : _) -> pure t
        [] -> throwError err404
    createTodo t = liftIO (modifyIORef' ref (t :)) >> pure t
    health = pure "ok"

main :: IO ()
main = do
  ref <- newIORef [Todo 1 "Learn Servant" False]
  putStrLn "Listening on :8080"
  run 8080 (serve (Proxy :: Proxy TodoAPI) (server ref))

49.2 Other Web Frameworks

Minimal, Sinatra-like (get "/hello/:name" $ do...) Full-stack, type-safe routes, Shakespearean templates, persistent integration

Batteries-included, Rails-like, live reload, PostgreSQL-centric

Type-level API specification (see above) Spock, Twain, Okapi

The common foundation (interface + fast HTTP server) Miso, Reflex-FRP, Obelisk

Frontend / full-stack functional reactive programming (GHCJS / GHC's JS & WASM backends)

HTTP clients: http-client, req, wreq, http-conduit. HTML: lucid, blaze-html. Logging: katip, co-log, monad-logger. Configuration: dhall, envy, yaml.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
main :: IO ()
main = scotty 3000 $ do
  get "/" $ text "Hello from Scotty"
  get "/hello/:name" $ do
    name <- pathParam "name"        -- older Scotty versions use `param`
    html ("<h1>Hello, " <> name <> "!</h1>")