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:
okTextokHtmlokJsonbadRequestnotFoundmethodNotAllowedserverErrorredirectToseeOthercreatedrespondNoContent
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:
htmxTriggerhtmxCloseModalhtmxHardRedirecthtmxRefreshhtmxRetarget
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:
respondNowredirectNowabortBadRequestabortNotFoundabortForbiddenfailValidationfailDbrequireRightrequireFoundrequireValid
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.