Skip to main content

Route Directives

Route directives are terminal directives that produce a StandardRoute -- a route that does not pass the request to an inner route. They complete, reject, redirect, or fail the request.

complete

Completes the request with a response. Several overloads are available.

Complete with a string:

val route: Route = path("hello") {
complete("Hello, world!")
}

Complete with a status code:

val route: Route = path("no-content") {
complete(Status.NoContent)
}

Complete with a status code and a body:

val route: Route = path("created") {
complete(Status.Created, "Resource created")
}

Complete with a status code, headers, and a body:

val route: Route = path("custom") {
complete(Status.Ok, Headers(Header.Raw(ci"X-Custom", "value")), "With headers")
}

Complete with a marshallable value (e.g., a case class with a Circe encoder):

case class User(name: String, age: Int)

val route: Route = path("user") {
complete(User("Alice", 30))
}

Complete with an IO[T]:

val route: Route = path("async") {
complete(IO.pure("Async result"))
}

Complete with a Response[IO]:

val route: Route = path("raw") {
complete(Response[IO](Status.Ok).withEntity("Raw response"))
}

reject

Rejects the request. The route does not handle the request and allows alternatives to be tried.

Reject with no rejections (empty rejection):

val route: Route = reject

Reject with specific rejections:

val route: Route = reject(ValidationRejection("Invalid input"))

Multiple rejections can be provided:

val route: Route = reject(
ValidationRejection("Field A is invalid"),
ValidationRejection("Field B is invalid")
)

redirect

Redirects the request to a different URI with the given status code.

val route: Route = path("old") {
redirect(uri"/new", Status.MovedPermanently)
}
val route: Route = path("temp") {
redirect(uri"/other", Status.TemporaryRedirect)
}

failWith

Fails the request with an exception. The exception propagates up the route tree until it is handled by a handleExceptions directive or the default exception handler.

val route: Route = path("error") {
failWith(new RuntimeException("Something went wrong"))
}

handle

Converts a function or partial function from Request[IO] to IO[Response[IO]] into a route.

Total function:

val route: Route = handle { request =>
IO.pure(Response[IO](Status.Ok).withEntity(s"Handled: ${request.uri}"))
}

Partial function (rejects with empty rejections if not defined):

val route: Route = handle {
case request if request.uri.path.renderString.startsWith("/api") =>
IO.pure(Response[IO](Status.Ok).withEntity("API response"))
}

Partial function with custom rejections:

val route: Route = handle(
{ case request if request.uri.path.renderString.startsWith("/api") =>
IO.pure(Response[IO](Status.Ok).withEntity("API response"))
},
Seq(ValidationRejection("Not an API request"))
)

handleSync

Like handle, but the handler function is synchronous.

Total function:

val route: Route = handleSync { request =>
Response[IO](Status.Ok).withEntity(s"Sync: ${request.uri}")
}

Partial function:

val route: Route = handleSync {
case request if request.uri.path.renderString.startsWith("/static") =>
Response[IO](Status.Ok).withEntity("Static content")
}

Partial function with custom rejections:

val route: Route = handleSync(
{ case request if request.uri.path.renderString.startsWith("/static") =>
Response[IO](Status.Ok).withEntity("Static content")
},
Seq(ValidationRejection("Not a static request"))
)