Skip to content

Show me the code!

This is a whirlwind tour of WebGear. If you want to learn more, go to the user guide.

Building Request Handlers

A request handler is just a function that takes a request value and produces a response value in a monadic context. This is where the business logic of your application lives.

-- The Has (JSONRequestBody Widget) r constraint indicates that this handler
-- can only be called if the request body is a valid JSON that can be parsed
-- to a Widget value.
putWidget :: Has (JSONRequestBody Widget) r => Handler r Widget
putWidget = Kleisli $ \req -> do
  let widget = get (Proxy @(JSONRequestBody Widget)) req
  saveToDB widget
  return (ok200 widget)

Accessing Request Traits

Traits are attributes associated with the request. Header values, query parameters, path components, request body are all examples of traits.

The trait values are accessed using the get function.

someHandler :: Have [ PathVar "widgetId" UUID
                    , JSONRequestBody Widget
                    , QueryParam "limit" Integer
                    ] r
            => Handler r a
someHandler = Kleisli $ \req -> do
  let
    widget = get (Proxy @(JSONRequestBody Widget)) req     -- widget :: Widget
    wid = get (Proxy @(PathVar "widgetId" UUID)) req       -- wid :: UUID
    limit = get (Proxy @(QueryParam "limit" Integer)) req  -- limit :: Integer
  ...
  return noContent204

Sending Responses

The response value returned contains the HTTP status code, response headers, and optionally a body. The body can be any type that can be converted to a lazy ByteString using the ToByteString type class.

createWidget = Kleisli $ \req -> do
  ...
  return (created201 widget)

In addition to functions such as ok200 and created201, there is a generic respond function that accepts an HTTP status code, response headers and the body to generate a response.

Routing without TemplateHaskell

You can use regular functions instead of TemplateHaskell QuasiQuoters for routing if you prefer that.

-- matches GET /hello/name:String
helloRoute :: Handler '[] String
helloRoute = method @GET
             $ path @"hello" $ pathVar @"name" @String $ pathEnd
             $ Kleisli $ \req -> do
                 let name = get (Proxy @(PathVar "name" String)) req
                 return $ ok200 $ "Hello, " ++ name

Composable routes

Compose routes with <|>. During request routing, each of these routes will be tried sequentially and the first matching handler will be called.

allRoutes :: Handler r a
allRoutes = createWidget <|> getWidget <|> updateWidget <|> deleteWidget

Since handlers are functions, you can refactor and compose them as you wish. The below example shows an application with v1 and v2 path prefixes.

allRoutes :: Handler r a
allRoutes = v1Routes <|> v2Routes

-- matches any path starting with /v1
v1Routes :: Handler r a
v1Routes = [match| /v1 ]
           $ v1CreateWidget <|> v1GetWidget <|> v1UpdateWidget <|> v1DeleteWidget

-- matches any path starting with /v2
v2Routes :: Handler r a
v2Routes = [match| /v2 ]
           $ v2CreateWidget <|> v2GetWidget <|> v2UpdateWidget <|> v2DeleteWidget

Middlewares

Middlewares are handlers that wrap another handler. They usually modify or enhance the behaviour of the inner handler. For example, the queryParam middleware in the example below ensures that the search handler is invoked only when the request contains a query parameter named limit.

searchWidget :: Handler '[] a
searchWidget = queryParam @"limit" @Int searchHandler

searchHandler :: Has (QueryParam "limit" Int) r => Handler r a
searchHandler = ...

Typically, you will compose many middlewares to form a route handler. The user guide explains how you can build your own middlewares.

First-class JSON support

Use JSON input and output in your handlers without any boilerplate. WebGear uses the aeson library to handle JSON values. The request body can be read as a JSON value using jsonRequestBody middleware. The response can be converted to a lazy ByteString using the jsonResponseBody middleware.

updateWidget :: Handler '[] ByteString
updateWidget = jsonRequestBody @Widget
               $ jsonResponseBody @Widget
               $ updateWidgetHandler

updateWidgetHandler :: Has (JSONRequestBody Widget) r => Handler r Widget
updateWidgetHandler = Kleisli $ \req -> do
  let widget = get (Proxy @(JSONRequestBody Widget)) req   -- widget :: Widget
  ...
  return $ ok200 widget