omhs 0.1.0 — back after ~5 years.
A long time ago I wrote a tiny Netty wrapper because I didn’t want to pull heavy dependencies into a project — just Sinatra-like routing. And now, five years later, it’s on Scala 3.
I tried before and dropped it (life got busy). This time it’s done.
Get Mike’s stories in your inbox
Join Medium for free to get updates from this writer.
What’s in this release:
- Scala 3 alongside 2.13 — same DSL
- Netty 4.1.137, refreshed play-json / circe / jsoniter modules
- Maven Central (
omhs-dsl+ JSON support) - CI on both Scala versions
The hard part was the routing macros. On Scala 2, ~> expanded into an anonymous ExecutableRule subclass. Scala 3 quoted macros don’t generate that cleanly, so ExecutableRule now takes a runner: List[ParamDef[_]] => AsyncResult, and the macro only builds that function. Same DSL, different codegen.
Artefacts are here.
How to use:
// scala
import com.github.fntz.omhs._
import com.github.fntz.omhs.playjson.JsonSupport
import RoutingDSL._
import play.api.libs.json.{Format, Json}
import scala.concurrent.Future
case class Person(id: Int, name: String)
@main def main(): Unit = {
implicit val personJson: Format[Person] = Json.format[Person]
implicit val personBodyReader: BodyReader[Person] = JsonSupport.reader[Person]()
implicit val personBodyWriter: BodyWriter[Person] = JsonSupport.writer[Person]()
import AsyncResult.Implicits._
import scala.concurrent.ExecutionContext.Implicits.global
val simpleGetRoute = get("ping") ~> { () =>
"pong"
}
val getWithParamsRoute = get("bar" / string) ~> { (x: String) =>
s"given: $x"
}
val postPersonRoute = post("person" <<< body[Person]) ~> { (p: Person) =>
val x = p.copy(name = "new")
Future(x)
}
val route1 = new Route().addRules(
simpleGetRoute,
getWithParamsRoute,
postPersonRoute
)
val server = OMHSServer.init(9000, route1.toHandler(Setup.default), None)
server.start()
sys.addShutdownHook(server.stop())
new java.util.concurrent.CountDownLatch(1).await()
}





