JSON Endpoints
Combine http4s with Circe.
JSON Endpoints is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 3 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.
JSON via circe
http4s integrates JSON through the http4s-circe module, which bridges circe codecs to EntityDecoder and EntityEncoder. circe is the de facto functional JSON library for Scala.
You define Encoder and Decoder instances for your types and let http4s handle the wire format.
// build.sbt
// "org.http4s" %% "http4s-circe" % http4sV
// "io.circe" %% "circe-generic" % circeVDeriving Codecs
With circe-generic you derive codecs automatically using deriveEncoder/deriveDecoder or the @JsonCodec annotation. They map case-class fields to JSON keys by name.
Derivation needs codecs for every field type, recursively.
import io.circe.generic.semiauto._
case class User(id: Int, name: String)
implicit val enc = deriveEncoder[User]
implicit val dec = deriveDecoder[User]EntityEncoder from circe
The import org.http4s.circe.CirceEntityEncoder._ provides an implicit EntityEncoder[F, A] for any A with a circe Encoder. Then Ok(user) serializes to JSON with application/json.
No manual conversion to Json is needed in handlers.
import org.http4s.circe.CirceEntityEncoder._
HttpRoutes.of[IO] {
case GET -> Root / "me" => Ok(User(1, "Ada"))
}EntityDecoder from circe
Mirror image: org.http4s.circe.CirceEntityDecoder._ supplies an EntityDecoder[F, A] for any A with a circe Decoder. Then req.as[User] parses the JSON body.
Both imports together are bundled as CirceEntityCodec._.
import org.http4s.circe.CirceEntityDecoder._
case req @ POST -> Root / "users" =>
req.as[User].flatMap(u => Created(u))A Full JSON POST
Combining decode and encode gives a clean create endpoint: parse the incoming JSON to a domain type, run business logic, then encode the result back as JSON.
Decoding failures surface as 422 or 400 via the circe decoder.
import org.http4s.circe.CirceEntityCodec._
case req @ POST -> Root / "users" =>
for {
in <- req.as[User]
out <- store.create(in)
res <- Created(out)
} yield resThe json Interpolator
For ad hoc JSON, circe's json string interpolator from io.circe.literal builds a Json value directly, with interpolated Scala values.
Useful for small responses or test fixtures without a case class.
import io.circe.literal._
val body = json"""{ "status": "ok", "count": 3 }"""
Ok(body)Custom Field Names
When JSON keys differ from Scala field names, derive with a Configuration from circe-generic-extras, e.g. snake_case, or write the codec by hand using forProduct2.
This decouples your API contract from internal naming.
import io.circe.Encoder
implicit val e: Encoder[User] =
Encoder.forProduct2("user_id", "full_name")(u => (u.id, u.name))Encoding Lists
circe provides codecs for List, Vector, Option, and Map automatically once the element codec exists. So returning a collection just works.
An empty list encodes to [] and None to either an absent key or null.
import org.http4s.circe.CirceEntityEncoder._
case GET -> Root / "users" =>
store.all.flatMap(us => Ok(us)) // List[User] -> JSON arrayValidating Decoded JSON
Decoding gives you a syntactically valid value; semantic validation is yours. Run checks after as and short-circuit with BadRequest when invalid.
Keep domain invariants in smart constructors returning Either for clarity.
req.as[User].flatMap { u =>
if (u.name.nonEmpty) Created(u)
else BadRequest("name required")
}Error Responses as JSON
Return structured errors as JSON so clients can parse them. Define an error case class with a circe encoder and pass it to the relevant status builder.
Consistent error shapes make APIs far easier to consume.
case class ApiError(code: String, message: String)
implicit val e = deriveEncoder[ApiError]
BadRequest(ApiError("E_NAME", "name required"))Streaming JSON
For large collections you can stream JSON instead of buffering. jsonEncoderOf plus an fs2 Stream[F, A] emits a JSON array incrementally.
This keeps memory flat for big result sets, leveraging fs2 back-pressure.
import org.http4s.circe.streamJsonArrayEncoder
case GET -> Root / "feed" =>
Ok(store.streamAll) // fs2.Stream[IO, Event]Quick Check
Recall which import enables decoding a JSON body to a case class.
Recap
You wired JSON with http4s-circe: derive codecs, import CirceEntityCodec, and use Ok(value) / req.as[A] for full JSON I/O.
You handled lists, custom field names, structured JSON errors, and even streaming arrays for large payloads.
Frequently asked questions
Is the “JSON Endpoints” lesson free?
Yes — the full text of “JSON Endpoints” 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 “JSON Endpoints”?
Combine http4s with Circe. 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 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “JSON Endpoints” 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.