Membangun backend service pure functional: http4s untuk server dan client HTTP di atas cats-effect, Tapir untuk endpoint type-safe dengan OpenAPI otomatis, serta serialisasi JSON memakai circe dan integrasinya dengan effect system.

Kita telah mengumpulkan banyak senjata: type system (episode 5), ADT (episode 7), effect system (episode 11), dan concurrency (episode 12). Sekarang saatnya merakitnya menjadi satu: backend service HTTP yang pure functional, type-safe, dan siap produksi. Inilah tujuan utama mayoritas developer belajar Scala.
Mengapa episode ini penting? Karena pola yang dibangun di sini — endpoint type-safe, serialisasi otomatis, dokumentasi dari kode — adalah standar industri di ekosistem Scala 2026. Kalian akan melihat bagaimana konsep-konsep abstrak di episode sebelumnya berkumpul menjadi satu aplikasi yang nyata.
http4s adalah library HTTP untuk cats-effect — server dan client HTTP di mana request dan response diperlakukan sebagai stream dan effect. Tambahkan ke build.sbt:
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-ember-server" % "0.23.30",
"org.http4s" %% "http4s-dsl" % "0.23.30",
"org.http4s" %% "http4s-circe" % "0.23.30",
"io.circe" %% "circe-generic" % "0.14.10"
)import cats.effect.{IO, IOApp}
import org.http4s.*
import org.http4s.dsl.io.*
import org.http4s.ember.server.EmberServerBuilder
import com.comcast.ip4s.*
object Main extends IOApp.Simple:
val helloRoutes: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "hello" / name =>
Ok(s"Hello, $name!")
}
val run: IO[Unit] =
EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(helloRoutes.orNotFound)
.build
.useForeverBedah bagian pentingnya:
HttpRoutes[IO] — koleksi route di mana setiap request adalah IO[Response].case GET -> Root / "hello" / name — pattern matching pada request: method GET, path /hello/{name}, dengan name di-destructure otomatis.EmberServerBuilder — server Ember, implementasi HTTP server berbasis fiber dari http4s.Jalankan dengan sbt run, lalu uji:
curl -i http://localhost:8080/hello/Saricirce adalah library JSON paling populer di ekosistem Scala. Dengan case class + derivasi otomatis, encode/decode jadi bebas boilerplate:
import io.circe.*
import io.circe.generic.auto.*
import io.circe.syntax.*
case class User(id: Int, name: String, active: Boolean)
val u = User(1, "Sari", active = true)
println(u.asJson.noSpaces)
// {"id":1,"name":"Sari","active":true}Di Scala 3, kalian bisa memakai derivation sematics untuk kinerja lebih baik:
import io.circe.*
import io.circe.generic.semiauto.*
case class User(id: Int, name: String)
object User:
given Encoder[User] = deriveEncoder[User]
given Decoder[User] = deriveDecoder[User]Di sini given dari episode 5 berperan: compiler menemukan encoder/decoder secara implisit lewat mekanisme typeclass.
Gabungkan dengan http4s:
import io.circe.generic.auto.*
case class CreateUser(name: String)
val routes: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> Root / "users" =>
req.as[CreateUser].flatMap { body =>
val id = 1
Created(User(id, body.name, active = true))
}
case GET -> Root / "users" / IntVar(id) =>
Ok(User(id, "Sari", active = true))
}req.as[CreateUser] meng-decode body JSON ke case class secara otomatis; IntVar(id) men-parse segmen path sebagai Int. Semua type-safe di level compiler.
Tapir mengambil pendekatan berbeda: endpoint didefinisikan sekali sebagai nilai, lalu diinterpretasikan menjadi server, client, dan dokumentasi. Tidak ada duplikasi antara kode dan spec.
import sttp.tapir.*
import sttp.tapir.json.circe.*
import sttp.tapir.generic.auto.*
case class User(id: Int, name: String, active: Boolean)
val getUserEndpoint: PublicEndpoint[Int, String, User, Any] =
endpoint
.get
.in("users" / path[Int]("id"))
.out(jsonBody[User])
.errorOut(stringBody)
// lihat deskripsi endpoint sebagai data
println(getUserEndpoint.show)Satu definisi menghasilkan:
import sttp.tapir.server.ServerEndpoint
import cats.effect.IO
val serverEndpoint: ServerEndpoint[Any, IO] =
getUserEndpoint.serverLogicSuccess { id =>
IO.pure(User(id, "Sari", active = true))
}import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.tapir.openapi.circe.yaml.*
val docs: String =
OpenAPIDocsInterpreter()
.toOpenAPI(List(getUserEndpoint), "User API", "1.0")
.toYaml
println(docs)Inilah keunggulan Tapir: kode adalah satu-satunya sumber kebenaran — server, client, dan dokumentasi selalu sinkron.
Untuk kelengkapan, Play Framework adalah web framework Scala/Java bergaya MVC tradisional (pola controller → service → template). Ia populer untuk aplikasi web monolitik dengan UI server-side. Di ekosistem modern, http4s/Tapir lebih sering dipilih untuk API murni; Play tetap relevan untuk project yang sudah mengadopsinya.
| Framework | Gaya | Kekuatan |
|---|---|---|
| http4s | Pure functional, fiber | Kinerja, komposisi, ekosistem Typelevel |
| Tapir | Endpoint-as-data | Type-safe penuh, OpenAPI + client otomatis |
| Play | MVC tradisional | Fitur lengkap, dokumentasi besar, UI server-side |
Tip
Untuk API baru di 2026, kombinasi populer adalah http4s (server) + Tapir (definisi endpoint) + circe (JSON): fleksibilitas http4s, keamanan tipe Tapir, dan kenyamanan circe dalam satu stack.
IO[Response]; jangan memanggil unsafeRunSync di dalam handler.orNotFound — route yang tidak cocok harus menghasilkan 404; gunakan orNotFound atau route alternatif.given encoder/decoder atau circe.generic.auto.* ter-import.EmberServerBuilder gagal bind; cek dengan lsof -i :8080 dan ganti port.Inti yang harus dibawa pulang:
IO-effect: HttpRoutes, pattern matching pada request, EmberServerBuilder.case class dengan typeclass given.Di episode 14 selanjutnya, kita beralih ke ranah di mana Scala paling bersinar: data dan big data dengan Apache Spark dan Kafka — DataFrame, RDD, producer/consumer, dan streaming. Sampai jumpa di episode 14!