Iwf Guide

Iwf Guide

Controllers

Controllers are ordinary Idris functions. Iwf does not require controller classes or fixed action constructors. The canonical app groups handlers under Web/Controller/* because that is easy to navigate.

1. A Handler

A normal HTML handler takes a ControllerContext and returns a Response:

home : ControllerContext -> Response
home context =
  render context homePage

A route with decoded input receives the input first:

articleShow : ArticleInput -> ControllerContext -> Response
articleShow input context =
  render context (articlePage input.slug)

2. ControllerContext

ControllerContext contains request-level framework state:

  • the original request.
  • the database pool for request-scoped database helpers.
  • signed session data.
  • flash messages.
  • CSRF token.
  • the current user, when the app runner can load one from the session.
  • login path.
  • access-denied handler.

You pass this context to helpers such as render, renderFragment, auth checks, flash helpers, and CSRF helpers.

3. Render A Page

Use render for normal pages:

settings : ControllerContext -> Response
settings context =
  render context (settingsPage context emptySettingsForm [])

For direct browser requests, render returns the full HTML document. For htmx HTML requests, it returns only the #main-content fragment plus out-of-band flash updates. Requests that prefer JSON do not receive HTML fragments.

Use renderFragment for endpoints that are fragment-only but still need flash and Auto Refresh handling.

Views return page HTML. The framework shell is added by render for full browser requests.

4. Layouts

Shared layouts are configured once on the app. They receive the normalized page and can add head entries, body attributes, or shell HTML around the main content:

siteLayout : PageLayout
siteLayout context page =
  { pageHead := page.pageHead ++ [descriptionMeta page.pageTitle]
  , pageShellBefore := page.pageShellBefore ++ [<nav><a href="/login">Sign in</a></nav>]
  } page

dashboardView : ControllerContext -> Html
dashboardView context =
  fragment
    [ <title>Dashboard</title>
    , <section class="page">
        <h1>Dashboard</h1>
      </section>
    ]

dashboard : ControllerContext -> Response
dashboard context =
  render context (dashboardView context)

main : IO ()
main =
  runApp (app "My App" appRoutes |> withPageLayout siteLayout)

The layout is applied to full browser responses. HTMX responses return only the page's main content plus out-of-band shell updates.

5. Responses

Common response helpers:

  • okText
  • okHtml
  • okJson
  • badRequest
  • notFound
  • methodNotAllowed
  • serverError
  • redirectTo
  • seeOther
  • created
  • respondNoContent

Use seeOther for normal POST/redirect/GET:

saveSettings : SettingsForm -> ControllerContext -> ControllerApp Response
saveSettings input context =
  pure
    (flashInContext "notice" "Settings saved" context
      (seeOther (pathTo settingsRoute)))

Use HTMX helpers when the browser needs a client-side event or target change:

  • htmxTrigger
  • htmxCloseModal
  • htmxHardRedirect
  • htmxRefresh
  • htmxRetarget

Use htmxHardRedirect only when the whole shell must reload, for example after logout.

6. Early Returns

Controller actions run in ControllerApp, so guards and failure helpers can stop the action without hand-written Either branches:

showArticle : ArticleSlugInput -> ControllerContext -> ControllerApp Response
showArticle input context = do
  result <- findArticle input.slug context
  maybeArticle <- requireRight result
  article <- requireFound maybeArticle
  pure (render context (articlePage article))

Common helpers:

  • respondNow
  • redirectNow
  • abortBadRequest
  • abortNotFound
  • abortForbidden
  • failValidation
  • failDb
  • requireRight
  • requireFound
  • requireValid

7. Access Control

For one handler:

settings : ControllerContext -> ControllerApp Response
settings context = do
  user <- requireCurrentUser context
  pure (render context (settingsPage context user))

For grouped route hooks, import the advanced controller and route modules explicitly so raw dispatch policy stays out of ordinary handlers.

Next

Read Views and IDRX.