Skip to content
AgileLogics
6 min read

Building Web Services

Prerequisites: Functional effects: Cats Effect and ZIO, JSON

Scala's web frameworks range from minimal to full-stack:

Framework Style
Tapir describe endpoints as values; generates OpenAPI docs; runs on many servers
http4s functional HTTP on Cats Effect
ZIO HTTP high-performance HTTP on ZIO
Play full-stack MVC framework
Cask Flask-like simplicity from the Li Haoyi ecosystem

A Tapir endpoint

// illustrative
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.11.25
//> using dep com.softwaremill.sttp.tapir::tapir-json-upickle:1.11.25
import sttp.tapir.*
import sttp.tapir.json.upickle.*
import sttp.tapir.generic.auto.*
import sttp.tapir.server.netty.sync.NettySyncServer
import upickle.default.*

case class Book(id: Int, title: String) derives ReadWriter

val books = Map(1 -> Book(1, "Programming in Scala"))

val getBook = endpoint.get
  .in("books" / path[Int]("id"))
  .out(jsonBody[Book])
  .errorOut(statusCode(sttp.model.StatusCode.NotFound).and(stringBody))
  .handle(id => books.get(id).toRight(s"No book $id"))

@main def server(): Unit =
  NettySyncServer().port(8080).addEndpoint(getBook).startAndWait()
$ curl localhost:8080/books/1
{"id":1,"title":"Programming in Scala"}
$ curl -i localhost:8080/books/9
HTTP/1.1 404 Not Found
No book 9

The endpoint's inputs, outputs, and errors are types: a handler that returns the wrong type doesn't compile, and the same description generates OpenAPI documentation and a typed HTTP client.

http4s

// illustrative
import cats.effect.*
import org.http4s.*, org.http4s.dsl.io.*, org.http4s.ember.server.*
import com.comcast.ip4s.*

object Main extends IOApp.Simple:
  val routes = HttpRoutes.of[IO] {
    case GET -> Root / "hello" / name => Ok(s"Hello, $name")
  }.orNotFound

  val run = EmberServerBuilder.default[IO]
    .withPort(port"8080").withHttpApp(routes).build.useForever

Routes are pattern matches on requests — a very natural fit for Scala.

Production concerns

Add structured logging, configuration (pureconfig or ciris), database access (Doobie, Skunk, or Magnum), metrics and tracing (OpenTelemetry), and graceful shutdown. Effect systems handle the last one automatically through their resource management.