Cies Breijs
09/22/2025, 1:58 PMCies Breijs
09/22/2025, 2:01 PMCies Breijs
09/22/2025, 2:03 PMCies Breijs
09/22/2025, 2:03 PMCies Breijs
09/22/2025, 2:05 PMCies Breijs
09/22/2025, 2:07 PMCies Breijs
09/22/2025, 2:11 PMCies Breijs
09/22/2025, 2:12 PMCies Breijs
09/22/2025, 2:33 PMPaths object (added some functions to generate paths with parameters with a clean API). I use these paths in my routes definitions and in my navigation menus + HTML/email templates. This way I can CTRL-click to know where a path is used and change it in one place. This has worked really nice for me.Andrew O'Hara
09/22/2025, 7:26 PMAndrew O'Hara
09/22/2025, 7:28 PMCies Breijs
09/22/2025, 7:46 PMval db = dbRequestKey(req) and use that for several calls instead of looking through the lens every call) or does it not matter since the overhead is little or even less then the overhead of the local val?dave
09/23/2025, 8:01 AMrequest.json<T>() and others like that (where json is an inlined function with a lens construction inside), I don't really bother TBH.Cies Breijs
09/23/2025, 3:23 PMAndrew O'Hara
09/24/2025, 8:39 PMjson function on ConfigurableMoshi doesn't properly reify the type paramter. It eventually resolves it into a T::class in autoBody, which breaks generics.dave
09/24/2025, 8:43 PMAndrew O'Hara
09/24/2025, 8:44 PMCies Breijs
10/03/2025, 2:03 AMdave
10/03/2025, 4:55 AMdave
10/03/2025, 5:44 AMCies Breijs
10/03/2025, 11:42 AMAndrew O'Hara
10/03/2025, 12:45 PMfun createApp(
env: Environment,
internet: HttpHandler
) = App(
storage = JdbcStorage(env[Config.jdbcUri]),
pageSize = env[Config.pageSize])
)
fun App.toApi() = contract {
routes += ...
}
fun main(
createApp(Environment.ENV, JavaHttpClient())
.toHttp()
.asServer(Undertow(80))
.start()
)
fun testApp() = createApp(
env = Environment.withDefaults(
Config.jdbcUri of h2DatabaseUri(),
Config.pageSize of 10
),
internet = reverseProxy(
"service1" to FakeService(),
"service2" to ClientFilters.SetHost(dockerContainer.uri).then(JavaHttpClient())
)
)
I have a relatively short youtube on building an api with http4k and an example repo, with the main function being a good place to start.Cies Breijs
10/03/2025, 12:47 PMAndrew O'Hara
10/03/2025, 12:56 PMHttpHandler? There's no need in a layered architecture, because it's only a concern of your business logic (i.e. the App), and the HttpHandler has the App because you can build a function called fun App.toApi , which builds to HttpHandler.
class App(
private val pageSize: Int,
private val storage: Storage
) {
fun listWidgets(accountId: String, cursor: String?) = storage.listWidgets(
accountId = accountId,
pageSize = pageSize,
cursor = cursor
)
}
// Note: this is using the httpk4-api-openapi syntax to define a handler
fun App.toApi() = contract {
routes += "/v1/accounts" / accountIdLens / "widgets" {
queries += cursorLens
} bindContract Get to { accountId, _ -> {
{ request ->
val result = listWidgets(accountId, cursorLens(request))
Response(OK).with(widgetsLens of result)
}
}
}
and in a functional architecture, you could do something like
fun listWidgetsFactory(
env: Environment
): HttpHandler {
val storage = JdbcStorage(env[Config.dataSource])
return { request ->
val result = storage.listWidgets(
accountId = accountIdLens(request),
pageSize = env[Config.pageSize],
cursor = cursorLens(request)
)
Response(OK).with(widgetsLens of result)
}
}
fun createApp(env: Environment): HttpHandler {
return routes(
"accounts/$accountIdLens/widgets" bind GET to listWisgetsFactory(env)
)
}
So how do I get the pageSize in? Well, there's a lens for that in http4k-config, and you just make sure you set the ENV when you run the program. It also supports parsing YAML files. Or just use System.getEnv to get properties directly from the system env.
object Config {
val pageSizeLens = EnvironmentKey.int().required("PAGE_SIZE")
}Andrew O'Hara
10/03/2025, 1:01 PMCies Breijs
10/03/2025, 1:08 PMCies Breijs
10/03/2025, 1:08 PMAndrew O'Hara
10/03/2025, 1:10 PMhttp4k-config. Keeping config as a global may work most of the time, but if you ever run into a scenario where it's a problem, then it's real tough to unwind.Andrew O'Hara
10/03/2025, 1:10 PMConfig.pageSize as pageSizeConfigLens , but instead of extracting things from a request or response, it's from an Environment from http4k-config.Andrew O'Hara
10/03/2025, 1:11 PMEnvironment.ENV, it's actually just a helper function for Environment(System.getEnv())Andrew O'Hara
10/03/2025, 1:14 PMCies Breijs
10/04/2025, 7:18 PMAndrew O'Hara
10/06/2025, 1:57 PMCies Breijs
10/06/2025, 1:59 PM