Skip to main content

Basic Directives

Basic directives are foundational low-level building blocks for constructing custom directives. They provide mechanisms for extracting values from requests, transforming requests and responses, and manipulating route results and rejections.

Extraction​

extract​

Extracts a single value from the RequestContext using the given function.

val extractHost: Directive1[String] = extract(_.request.uri.host.map(_.value).getOrElse("unknown"))

val route: Route = extractHost { host =>
complete(s"Host: $host")
}

extractRequest​

Extracts the current Request[IO] instance.

val route: Route = extractRequest { request =>
complete(s"Method: ${request.method}, URI: ${request.uri}")
}

extractRequestContext​

Extracts the full RequestContext.

val route: Route = extractRequestContext { ctx =>
complete(s"Unmatched path: ${ctx.unmatchedPath}")
}

extractUri​

Extracts the complete request URI.

val route: Route = extractUri { uri =>
complete(s"Full URI: $uri")
}

extractRequestEntity​

Extracts the request entity body as an EntityBody[IO] (alias for Stream[IO, Byte]).

val route: Route = extractRequestEntity { body =>
complete(Status.Ok -> body)
}

extractDataBytes​

Extracts the request body as a Stream[IO, Byte]. Functionally equivalent to extractRequestEntity.

val route: Route = extractDataBytes { bytes =>
val collected = bytes.through(fs2.text.utf8.decode).compile.string
onSuccess(collected) { text =>
complete(s"Received: $text")
}
}

extractStrictEntity​

Buffers the whole request entity in memory, provides it as a fs2.Chunk[Byte] and leaves the request body replayable for the inner route. Fails the request with 408 Request Timeout if the entity does not arrive within the timeout, and with 413 Payload Too Large if it exceeds maxBytes (8 MiB when omitted).

import scala.concurrent.duration._

val route: Route = extractStrictEntity(1.second) { bytes =>
complete(s"Received ${bytes.size} bytes")
}

extractUnmatchedPath​

Extracts the yet-unmatched portion of the request path.

val route: Route = pathPrefix("api") {
extractUnmatchedPath { remaining =>
complete(s"Remaining path: $remaining")
}
}

extractMatchedPath​

Extracts the already-matched portion of the request path.

val route: Route = pathPrefix("api" / "v1") {
extractMatchedPath { matched =>
complete(s"Matched: $matched")
}
}

Providing Values​

provide​

Injects a single value into a directive, making it available to the inner route.

val route: Route = provide("injected-value") { value =>
complete(value)
}

tprovide​

Injects a tuple of values into a directive.

val route: Route = tprovide(("hello", 42)) { (greeting, number) =>
complete(s"$greeting $number")
}

textract​

Extracts a tuple of values from the RequestContext.

val extractMethodAndPath = textract(ctx => (ctx.request.method, ctx.request.uri.path))

pass​

A no-op directive that always passes the request to its inner route unchanged. Useful as an identity element in directive composition.

val maybeAuth: Directive0 = if (authEnabled) authenticateBasic("realm", auth) else pass

val route: Route = maybeAuth {
complete("OK")
}

Request Transformation​

mapRequest​

Transforms the incoming Request[IO] before passing it to the inner route.

import org.http4s.Header
import org.typelevel.ci.CIString

val route: Route = mapRequest(_.putHeaders(Header.Raw(CIString("X-Modified"), "true"))) {
extractRequest { req =>
complete(s"Header present: ${req.headers.get(CIString("X-Modified")).isDefined}")
}
}

mapRequestContext​

Transforms the RequestContext before passing it to the inner route.

val route: Route = mapRequestContext(ctx => ctx.copy(unmatchedPath = ctx.unmatchedPath)) {
complete("OK")
}

toStrictEntity​

Buffers the whole request entity in memory and replaces the request body with the buffered bytes, so directives inside it can read the body more than once (for example several entity(as[T]) calls or formFields followed by entity). Over a real server a request body can be read only once; this directive is the escape hatch. Fails the request with 408 Request Timeout if the entity does not arrive within the timeout, and with 413 Payload Too Large if it exceeds maxBytes (8 MiB when omitted).

import scala.concurrent.duration._

val route: Route = toStrictEntity(1.second) {
entity(as[String]) { first =>
entity(as[String]) { second =>
complete(s"$first == $second")
}
}
}

Response Transformation​

mapResponse​

Transforms the Response[IO] produced by the inner route.

import org.http4s.Header
import org.typelevel.ci.CIString

val route: Route = mapResponse(_.putHeaders(Header.Raw(CIString("X-Served-By"), "stir"))) {
complete("Hello")
}

mapResponseEntity​

Transforms the response entity body (EntityBody[IO]) produced by the inner route.

val route: Route = mapResponseEntity(_.through(fs2.text.utf8.decode).through(fs2.text.utf8.encode)) {
complete("Re-encoded content")
}

mapResponseHeaders​

Transforms the response Headers produced by the inner route.

import org.typelevel.ci._

val route: Route = mapResponseHeaders(_.removeHeader(ci"X-Internal")) {
complete("Cleaned headers")
}

Route Transformation​

mapInnerRoute​

Transforms the inner route itself, allowing you to wrap or modify route behavior.

val route: Route = mapInnerRoute { inner =>
ctx => inner(ctx) // pass through unchanged
} {
complete("OK")
}

Route Result Transformation​

These directives operate on RouteResult values produced by inner routes. They are primarily used when building custom directives.

  • mapRouteResult(f: RouteResult => RouteResult) -- transforms the route result synchronously.
  • mapRouteResultIO(f: IO[RouteResult] => IO[RouteResult]) -- transforms the entire IO[RouteResult] effect, useful for error handling.
  • mapRouteResultWith(f: RouteResult => IO[RouteResult]) -- transforms the route result with an effectful function.
  • mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]) -- transforms matching route results; non-matching results pass through unchanged.
  • mapRouteResultWithPF(f: PartialFunction[RouteResult, IO[RouteResult]]) -- effectful variant of mapRouteResultPF.

Path Manipulation​

mapUnmatchedPath​

Transforms the unmatched path of the RequestContext before passing it to the inner route.

val route: Route = mapUnmatchedPath(path => Uri.Path.unsafeFromString(path.toString.toLowerCase)) {
path("api") {
complete("Case-insensitive match")
}
}

Rejection Manipulation​

mapRejections​

Transforms the list of rejections produced by the inner route.

val route: Route = mapRejections(_.filter(!_.isInstanceOf[MethodRejection])) {
get {
complete("GET only")
}
}

recoverRejections​

Converts rejections into a RouteResult, allowing rejected requests to be recovered.

val route: Route = recoverRejections { rejections =>
RouteResult.Complete(Response[IO](Status.NotFound))
} {
reject
}

recoverRejectionsWith​

Effectful variant of recoverRejections that returns IO[RouteResult].

cancelRejection​

Cancels a specific rejection from the list of rejections produced by the inner route.

val route: Route = cancelRejection(MethodRejection(Method.POST)) {
get {
complete("OK")
}
}

cancelRejections​

Cancels rejections matching the given classes or filter function.

val route: Route = cancelRejections(classOf[MethodRejection]) {
get {
complete("OK")
}
}