routing

routing

  • Docs
  • GitHub

›Implementations

Documentation

  • Installation
  • Routes
  • Routing
  • Implementations

    • Choosing an implementation
    • http4s 0.23 (latest stable on cats effect 3)
    • http4s 1.0.0-M44 (latest development on cats effect 3)
    • Play Framework
  • Reverse Routing

More

  • Benchmarks
  • Example

Play Framework

To use your routes in a project using the Play framework, add the following to your build.sbt, choosing the correct minor version of Play that you're using:

libraryDependencies += "bondlink" %% "routing-play_3.0" % "5.0.0"

The currently supported versions of Play are:

  • 3.0.6 -- "bondlink" %% "routing-play_3.0" % "5.0.0"
  • 2.9.6 -- "bondlink" %% "routing-play_2.9" % "5.0.0"

Play handlers will be of the shape Params => play.api.mvc.Handler

First let's rebuild our example routes.

import routing._

val Login = Method.GET / "login"
// Login: Route[GET, Unit] = /login
val Hello = Method.GET / "hello" / pathVar[String]("name")
// Hello: Route[GET, String] = /hello/<name: String>
val BlogPost = Method.GET / "post" / pathVar[String]("slug") :? queryParam[Int]("id")
// BlogPost: Route[GET, Tuple2[String, Int]] = /post/<slug: String>?<id: Int>

Then we can define our handlers.

import _root_.play.api.mvc.{ActionBuilder, AnyContent, Request, Results}
import routing.play._
import scala.concurrent.ExecutionContext.Implicits.global

val action = new ActionBuilder.IgnoringBody()
// action: IgnoringBody = play.api.mvc.ActionBuilder$IgnoringBody@1919e594

val handledLogin = Login.handle(_ => action(Results.Ok("Login page")))
// handledLogin: Handled[Action[AnyContent]] {
  type M >: Method <: Method
  type P >: Params <: Params
  type R >: Login <: Login
} = routing.Route$$anon$3@29712e74
val handledHello = Hello.handle(name => action(Results.Ok(s"Hello, $name")))
// handledHello: Handled[Action[AnyContent]] {
  type M >: Method <: Method
  type P >: Params <: Params
  type R >: Hello <: Hello
} = routing.Route$$anon$3@6eccdc58
val handledBlogPost = BlogPost.handle { case (slug, id) =>
  action((req: Request[AnyContent]) => Results.Ok(s"Blog post with id: $id, slug: $slug found at ${req.uri}"))
}
// handledBlogPost: Handled[Action[AnyContent]] {
  type M >: Method <: Method
  type P >: Params <: Params
  type R >: BlogPost <: BlogPost
} = routing.Route$$anon$3@75a38463

Handled routes can be composed into a play Router by passing them to Route.router:

import _root_.play.api.routing.Router

val router1: Router = Route.router(
  handledLogin,
  handledHello,
  handledBlogPost
)
// router1: Router = play.api.routing.SimpleRouterImpl@2e29148

If you prefer, you can call Router.from with a partial function that matches on your Routes manually:

val router2: Router = Router.from {
  case Login(_) => action(Results.Ok("Login page"))
  case Hello(name) => action(Results.Ok(s"Hello, $name"))
  case BlogPost(slug, id) =>
    action(req => Results.Ok(s"Blog post with id: $id, slug: $slug found at ${req.uri}"))
}
// router2: Router = play.api.routing.SimpleRouterImpl@604365e5

You can confirm that routes are matched correctly by passing some test requests to the router:

import org.apache.pekko.actor.ActorSystem
import _root_.play.api.libs.typedmap.TypedMap
import _root_.play.api.mvc.{EssentialAction, Headers, RequestHeader}
import _root_.play.api.mvc.request.{RemoteConnection, RequestTarget}
import scala.concurrent.Await
import scala.concurrent.duration._

implicit val actorSystem: ActorSystem = ActorSystem.create()
// actorSystem: ActorSystem = pekko://default

def fakeRequest(u: ReverseUri): RequestHeader =
  new RequestHeader {
    def attrs: TypedMap = TypedMap.empty
    def connection: RemoteConnection = RemoteConnection("", false, None)
    def headers: Headers = Headers()
    def method: String = u.method.name
    def target: RequestTarget =
      RequestTarget(u.toString, u.path,
        u.query.groupBy(_._1).map { case (k, v) => k -> v.flatMap(_._2) })
    def version: String = ""
  }

def testRoute(router: Router, call: Call): String = {
  val request: RequestHeader = fakeRequest(call.uri)
  val handler: EssentialAction = router.handlerFor(request).collect { case a: EssentialAction => a }.get
  Await.result(handler(request).run().flatMap(_.body.consumeData).map(_.utf8String), 1.second)
}

testRoute(router1, Login())
// res0: String = "Login page"
testRoute(router1, Hello("world"))
// res1: String = "Hello, world"
testRoute(router1, BlogPost("my-slug", 1))
// res2: String = "Blog post with id: 1, slug: my-slug found at /post/my-slug?id=1"

testRoute(router2, Login())
// res3: String = "Login page"
testRoute(router2, Hello("world"))
// res4: String = "Hello, world"
testRoute(router2, BlogPost("my-slug", 1))
// res5: String = "Blog post with id: 1, slug: my-slug found at /post/my-slug?id=1"

You can also check that requests matching none of your routes are not handled by the router:

def unhandled(method: Method, path: String) =
  router1.handlerFor(fakeRequest(ReverseUri(method, path, Vector())))

unhandled(Method.GET, "/fake")
// res6: Option[Handler] = None

// Not handled by `Hello` because the method doesn't match
unhandled(Method.POST, "/hello/world")
// res7: Option[Handler] = None
← http4s 1.0.0-M44 (latest development on cats effect 3)Reverse Routing →
Docs
Installation
Community
More
GitHub
Copyright © 2025 BondLink