Belajar Scala - Backend Services: http4s / Tapir
Episode 13 of 23

Belajar Scala - Backend Services: http4s / Tapir

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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: HTTP Server Pure Functional

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:

Dependencies http4s
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"
)

Server Minimal

Server http4s minimal
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
      .useForever

Bedah bagian pentingnya:

  • HttpRoutes[IO] — koleksi route di mana setiap request adalah IO[Response].
  • case GET -> Root / "hello" / namepattern 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:

Tes server
curl -i http://localhost:8080/hello/Sari

Serialisasi JSON dengan circe

circe adalah library JSON paling populer di ekosistem Scala. Dengan case class + derivasi otomatis, encode/decode jadi bebas boilerplate:

Model JSON dengan circe
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:

Derivation sematics di Scala 3
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.

Endpoint dengan JSON

Gabungkan dengan http4s:

Endpoint JSON lengkap
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: Endpoint Sebagai Data

Tapir mengambil pendekatan berbeda: endpoint didefinisikan sekali sebagai nilai, lalu diinterpretasikan menjadi server, client, dan dokumentasi. Tidak ada duplikasi antara kode dan spec.

Definisi endpoint Tapir
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:

  • Server logic — fungsi yang dipanggil saat request masuk.
  • OpenAPI spec — dokumentasi yang bisa di-download.
  • Client type-safe — kode pemanggil API yang dihasilkan otomatis.
Server logic Tapir
import sttp.tapir.server.ServerEndpoint
import cats.effect.IO
 
val serverEndpoint: ServerEndpoint[Any, IO] =
  getUserEndpoint.serverLogicSuccess { id =>
    IO.pure(User(id, "Sari", active = true))
  }
OpenAPI otomatis
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.

Play Framework: Pendekatan Tradisional

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.

FrameworkGayaKekuatan
http4sPure functional, fiberKinerja, komposisi, ekosistem Typelevel
TapirEndpoint-as-dataType-safe penuh, OpenAPI + client otomatis
PlayMVC tradisionalFitur 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.

Common Pitfalls

  • Effect yang bocor di route — route harus mengembalikan IO[Response]; jangan memanggil unsafeRunSync di dalam handler.
  • Lupa orNotFound — route yang tidak cocok harus menghasilkan 404; gunakan orNotFound atau route alternatif.
  • Decoder circe tidak ditemukan — pastikan given encoder/decoder atau circe.generic.auto.* ter-import.
  • Port sudah terpakaiEmberServerBuilder gagal bind; cek dengan lsof -i :8080 dan ganti port.

Penutup

Inti yang harus dibawa pulang:

  • http4s membangun server HTTP sebagai IO-effect: HttpRoutes, pattern matching pada request, EmberServerBuilder.
  • circe meng-encode/decode JSON ke/dari case class dengan typeclass given.
  • Tapir mendefinisikan endpoint sekali → server + client + OpenAPI otomatis.
  • Play adalah alternatif MVC tradisional untuk web app.
  • Pilih stack sesuai kebutuhan; kombinasi http4s + Tapir + circe adalah standar modern.

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!

Belajar Scala - Backend Services: http4s / Tapir | Belajar Scala