Requests and Responses
Read input and return output.
Requests and Responses is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Request as a Value
In http4s a Request[F] is an immutable value carrying the method, URI, headers, HTTP version, and a streaming body. You inspect it with pure accessors and never mutate it in place.
To change a request you produce a copy via withHeaders, withUri, or similar combinators.
val r: Request[IO] = Request[IO](
method = Method.GET,
uri = uri"/health"
)The Streaming Body
The body of a request or response is an fs2.Stream[F, Byte]. It is consumed lazily and effectfully, so large payloads never have to sit fully in memory.
You usually do not touch raw bytes; an EntityDecoder handles decoding for you.
// raw access (rarely needed)
val bytes: fs2.Stream[IO, Byte] = request.bodyDecoding the Body
An EntityDecoder[F, A] knows how to turn the body stream into an A. Call req.as[A] to decode, which returns F[A] and fails the effect on a malformed body.
A built-in decoder exists for String; richer ones come from JSON modules.
HttpRoutes.of[IO] {
case req @ POST -> Root / "echo" =>
req.as[String].flatMap(body => Ok(body))
}Binding the Request
To access the request object inside a case, bind it with @. Here req names the whole request while the pattern still matches method and path.
Without the binder you only have the destructured parts and cannot read the body or headers.
HttpRoutes.of[IO] {
case req @ PUT -> Root / "name" =>
req.as[String].flatMap(n => Ok(s"set $n"))
}Reading Headers
Headers live in request.headers. Use headers.get[H] with a typed header model, or headers.get(CIString("X-Custom")) for arbitrary names.
Typed headers like Authorization and Content-Type parse and validate their values for you.
import org.http4s.headers.Authorization
val auth: Option[Authorization] =
request.headers.get[Authorization]Building Responses
The DSL response builders such as Ok, Created, and NotFound are functions returning F[Response[F]]. Passing an argument sets the body via an EntityEncoder.
Each builder corresponds to a status code, so Created(...) yields 201.
Ok("all good") // 200
Created("made it") // 201
NotFound("missing") // 404Status Codes Directly
For a status without a convenience builder, or to customize, construct a Response[F] with an explicit Status and chain withEntity.
This gives full control over status, headers, and body in one expression.
import org.http4s.{Response, Status}
Response[IO](Status.Accepted)
.withEntity("queued")
.pure[IO]Encoding Bodies
An EntityEncoder[F, A] serializes a value into the response body and sets the Content-Type. String and byte encoders are built in; JSON encoders come from circe.
When you pass a value to Ok(value), http4s looks up the encoder implicitly.
// String encoder is implicit; sets text/plain
val resp: IO[Response[IO]] = Ok("plain text body")Setting Response Headers
Add headers by passing them as extra arguments to a builder, or chain .map(_.putHeaders(...)) on the response effect.
Typed header models ensure correctly formatted values, for example a Location header on a 201 Created.
import org.http4s.headers.Location
Created("ok").map(_.putHeaders(
Location(uri"/items/42")
))Handling Decode Failures
req.as[A] fails the F effect when the body cannot be decoded. Use req.attemptAs[A].value to get an F[Either[DecodeFailure, A]] and respond with 400 on the left.
This keeps malformed-input handling explicit and total.
req.attemptAs[String].value.flatMap {
case Right(b) => Ok(b)
case Left(_) => BadRequest("bad body")
}Cookies and Redirects
Set cookies with response.addCookie(ResponseCookie(name, value)). For redirects, use a 3xx builder and a Location header.
SeeOther(Location(uri)) performs a 303, the idiomatic post-redirect-get response.
import org.http4s.headers.Location
SeeOther(Location(uri"/login"))
.map(_.addCookie("sid", "abc123"))Quick Check
Think about safe body decoding.
Recap
Requests and responses are immutable values with streaming fs2 bodies. You decode with as/attemptAs via EntityDecoder and encode with EntityEncoder.
Builders set status codes, typed headers stay safe, and cookies and redirects are just response transformations.
Frequently asked questions
Is the “Requests and Responses” lesson free?
Yes — the full text of “Requests and Responses” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO.
What will I learn in “Requests and Responses”?
Read input and return output. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Scala for Backend Engineering & Functional Programming?
No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Requests and Responses” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?
Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Routes and HttpRoutes
- Requests and Responses
- JSON Endpoints
- Serving the Application