# Building HTTP Services — Scala

Source: https://www.geekswithgeeks.com/en/scala/s-http

> Choose a web stack and build a small JSON API.

## Scala web stacks

Scala offers several web stacks for different styles. **Play Framework** is a full-stack MVC framework, popular for traditional web apps. **http4s** (with Cats Effect) and **ZIO HTTP** are functional, typed HTTP libraries used for high-throughput APIs. **Tapir** describes endpoints as values, then interprets them into servers (http4s, Pekko HTTP, Netty, ZIO HTTP), clients and **OpenAPI** documentation from the same definition. **Pekko HTTP** (or Akka HTTP) suits actor-based systems, and **Cask** from the Li Haoyi ecosystem is a lightweight, Flask-like option for simple services. For JSON, common libraries are **circe**, **jsoniter-scala** (very fast) and **upickle**; for databases, **Doobie** and **Skunk** (functional), **Slick** and **ScalikeJDBC**, or **Quill** and **Magnum**. Whatever the stack, keep the domain logic in pure functions and ADTs, keep HTTP and database code at the edges, validate inputs into domain types, and map domain errors to HTTP status codes in one place.

## Layers of a Scala service

HTTP routes call pure domain logic, and repositories handle persistence at the edge.

![A three-layer stack: a top layer with route icons, a middle core with a gear, and a bottom layer with a database cylinder.](assets/figures/scala/section-8-map.svg) — Figure 8.1 — Routes, domain core and persistence.

## A small JSON API with Cask and upickle

Routes, validation into domain types and status codes.

```scala
//> using dep com.lihaoyi::cask:0.10.2
//> using dep com.lihaoyi::upickle:4.1.0
import upickle.default.*

case class NewOrder(customerId: String, totalPaise: Long) derives ReadWriter
case class Order(id: String, customerId: String, totalPaise: Long) derives ReadWriter

object OrderApi extends cask.MainRoutes:
  private var orders = Map.empty[String, Order]   // in-memory store for the example

  @cask.get("/orders/:id")
  def getOrder(id: String) =
    orders.get(id) match
      case Some(o) => cask.Response(write(o), headers = Seq("Content-Type" -> "application/json"))
      case None    => cask.Response(s"order $id not found", statusCode = 404)

  @cask.post("/orders")
  def create(request: cask.Request) =
    val input = read[NewOrder](request.text())
    if input.totalPaise <= 0 then cask.Response("total must be positive", statusCode = 400)
    else
      val order = Order(s"o-${orders.size + 1}", input.customerId, input.totalPaise)
      orders = orders.updated(order.id, order)
      cask.Response(write(order), statusCode = 201, headers = Seq("Content-Type" -> "application/json"))

  initialize()

// run with: scala run .   then POST /orders on port 8080
```

## One definition, many uses

With Tapir, the same endpoint value generates the server route, a typed client and OpenAPI docs, so documentation never drifts from the implementation.

**Quiz:** What is a key benefit of Tapir?

- [ ] It is a database driver
- [ ] It replaces the JVM
- [x] Endpoints are defined once as values and interpreted into servers, clients and OpenAPI docs
- [ ] It formats code

*Answer:* Endpoints are defined once as values and interpreted into servers, clients and OpenAPI docs. Tapir separates endpoint descriptions from their interpreters.
