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.