sup has modules for http4s, http4s-client and circe:

libraryDependencies ++= Seq(
  "com.kubukoz" %% "sup-http4s" % "0.6.0",
  "com.kubukoz" %% "sup-http4s-client" % "0.6.0",
  "com.kubukoz" %% "sup-circe" % "0.6.0"
)

Imports:

import sup._
import sup.modules.http4s._, sup.modules.http4sclient._, sup.modules.circe._

What’s included

statusCodeHealthCheck, healthCheckRoutes, Encoder/Decoder

In the http4s module, you get a router for (almost) free. You just need some instances, a healthcheck and an EntityEncoder[F, HealthResult[H]].

The circe module provides circe Encoder/Decoder instances for all the important bits, so you can use them together with http4s-circe and bundle the whole thing up:

import io.circe._, org.http4s.circe._, org.http4s._, org.http4s.implicits._, org.http4s.client._
// import io.circe._
// import org.http4s.circe._
// import org.http4s._
// import org.http4s.implicits._
// import org.http4s.client._

import cats.effect._, cats._, cats.data._, sup.data._
// import cats.effect._
// import cats._
// import cats.data._
// import sup.data._

implicit val client: Client[IO] = Client.fromHttpApp(HttpApp.notFound[IO])
// client: org.http4s.client.Client[cats.effect.IO] = org.http4s.client.Client$$anon$1@3a467470

//you'll most likely define this implicit only once per app
implicit def entityEncoder[A: Encoder]: EntityEncoder[IO, A] = jsonEncoderOf
// entityEncoder: [A](implicit evidence$1: io.circe.Encoder[A])org.http4s.EntityEncoder[cats.effect.IO,A]

val healthcheck: HealthCheck[IO, Id] = statusCodeHealthCheck(Request[IO]())
// healthcheck: sup.HealthCheck[cats.effect.IO,cats.Id] = sup.HealthCheck$$anon$1@4fa8a0fb

val routes = healthCheckRoutes(healthcheck)
// routes: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.HttpRoutes$$$Lambda$8774/608747353@42df112c)

//also works with reports!

val report = HealthReporter.fromChecks(
  healthcheck.through(mods.tagWith("foo")),
  HealthCheck.const[IO, Id](Health.Healthy).through(mods.tagWith("bar"))
)
// report: sup.HealthReporter[cats.effect.IO,cats.data.NonEmptyList,[H]sup.data.Tagged[String,H]] = sup.HealthCheck$$anon$1@29a3359d

val reportRoutes = healthCheckRoutes(report)
// reportRoutes: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.HttpRoutes$$$Lambda$8774/608747353@3952e05e)

reportRoutes.orNotFound.run(Request(uri = Uri.uri("/health-check"))).flatMap(_.as[String]).unsafeRunSync()
// res4: String = {"health":"Sick","checks":[{"tag":"foo","health":"Sick"},{"tag":"bar","health":"Healthy"}]}

remoteHealthCheck

There’s a way to build a healthcheck out of a request to a remote one:

implicit val decoder: EntityDecoder[IO, HealthResult[Id]] = jsonOf
// decoder: org.http4s.EntityDecoder[cats.effect.IO,sup.HealthResult[cats.Id]] = org.http4s.EntityDecoder$$anon$2@19e3552

val remoteCheck = remoteHealthCheck(Request[IO]())
// remoteCheck: sup.HealthCheck[cats.effect.IO,cats.Id] = sup.HealthCheck$$anon$1@3a33266c