Chez: A Scala Library for JSON Schemas, OpenAPI Spec generation & building AI Apps

Chez: A Scala Library for JSON Schemas, OpenAPI Spec generation & building AI Apps

A brief disclaimer

I’m still relatively new to Scala, I picked up Scala around 2 years ago and have not been able to put it down. I built a career leaning front end heavy, with plenty of back-end middle tier services in the mix (rest APIs, GraphQL, collection services, etc).

Any rate I mention this because it makes me nervous to publish anything among Scala developers because well, I don't feel like a 'real' developer. So this article is part introduction/part story of how my experience shaped Chez.


Why Chez?

At my last company, our team was building a large application with both front-of-the-house systems (the user-facing side; UIs, REST APIs, and other middle-tier services) and deeper back-end systems. The front of the house was largely in the TypeScript ecosystem, and we were used to a smooth developer experience: tools like Fastify, Typebox for JSON schema generation, and OpenAPI spec generation that could feed directly into automatic client library generation. This tooling wasn’t just convenient; it was critical to the team's velocity and keeping our contracts consistent across services.

On the Scala side of the application, we were using Cask. Which I absolutely love, including pretty much every other library in the lihaoyi ecosystem. And while Cask provided a solid foundation, it lacked some key pieces we had needed:

  1. JSON Schema validation
  2. OpenAPI spec generation

Without these, we did not have the ability to define schemas once in our rest APIs and have the rest of the stack benefit automatically. That gap created friction for our team and made the Scala portion of the application feel less integrated into our overall developer experience.

And sadly, towards the end of the project, we were seriously considering replacing Cask altogether. The reality was that, if we had to choose again, we might have gone with the TypeScript ecosystem from the start for all rest APIs, not because Scala couldn’t handle the workloads, but because the tooling in TypeScript was already there, ready to support building exactly these kinds of systems.

Chez grew out of both that gap and my obsession with Scala: a way to bring into Scala the kind of tooling we were used to in TypeScript, and, just as importantly, to introduce approachable libraries and Scala coding styles to other TypeScript developers who might be curious about building in Scala.


Chez & CaskChez

The first piece of the puzzle was Chez itself; a library for building JSON Schema specification definitions, built on top of the wonderful uPickle library. 

The second piece is CaskChez: a library built on top of Cask that provides OpenAPI spec generation and automatic HTTP validation. This is the part that makes Scala feel much closer to the developer experience we had on the JavaScript side, where schemas and contracts naturally flowed through our services. 

Notice a theme? I really like lihaoyi’s libraries and style!
import chez.derivation.Schema
import caskchez.*
import upickle.default.*
import cask.main.Main
import caskchez.openapi.config.OpenAPIConfig

@Schema.title("CreateUser")
case class CreateUser(
  @Schema.minLength(1) name: String,
  @Schema.format("email") email: String,
  @Schema.minimum(0) age: Int
) derives Schema, ReadWriter

@Schema.title("User")
case class User(id: String, name: String, email: String, age: Int) derives Schema, ReadWriter

object Api extends Main {
  @CaskChez.post(
    "/users",
    RouteSchema(
      summary = Some("Create user"),
      body = Some(Schema[CreateUser]),
      responses = Map(201 -> ApiResponse("Created", Schema[User]))
    )
  )
  def create(req: ValidatedRequest) = {
    req.getBody[CreateUser] match {
      case Right(in) => write(User("1", in.name, in.email, in.age))
      case Left(err) => write(ujson.Obj("error" -> "validation_failed", "message" -> err.message))
    }
  }

  @CaskChez.swagger(
    "/openapi",
    OpenAPIConfig(
      title = "User API",
      summary = Some("User management with validation"),
      description = "Auto‑generated from RouteSchema",
      version = "1.0.0"
    )
  )
  def openapi(): String = ""  // auto‑generated JSON
  override def port = 8082
  initialize()

A full blown rest AP json schema validation and openapi spec generation

With Chez and CaskChez together, Scala services can start to enjoy the same schema-first, contract-driven developer experience that teams in TypeScript have come to expect.


Going Deeper into Scala

One of the hard earned joys of building Chez is that it forced me to go much deeper into the Scala language; something I had been itching to do since reading Martin Odersky’s Programming in Scala. To make Chez work, I got to experiment with metaprogramming techniques: implementing derives Schema, using annotations, and generating rich schema definitions directly from case classes. For example:

// A case class with json schema specification
@Schema.title("Product")
case class Product(
  @Schema.description("Product name")
  @Schema.minLength(1)
  @Schema.maxLength(100)
  name: String
) derives Schema

What a delightful API! This automatically creates a json schema specification

Before Chez, I had never published anything to Maven Central (via Sonatype). Shipping Chez was a first for me in that regard; a personal milestone that made the project feel real and part of the broader Scala ecosystem.


Why Scala, and Why Now?

When you want to build an AI app today, the choice basically comes down to two languages: TypeScript or Python. Those ecosystems are strong, but I was surprised that Scala wasn’t further along here: why isn’t Scala part of this conversation?

I’ve gone very deep into building AI applications, and my constant battle has been: just use TypeScript for convenience, or keep investing in Scala? The more I built my AI side projects in Scala, the more I became a believer. There are just so many things that make Scala way more suitable for building out agentic systems. Type safety, actual async/multi-threaded concurrency, & the ability to express agentic orchestration more naturally (think Akka or Castor actors!); it’s an absolute delight to write AI applications in Scala.

Writing expressive, artful code in Scala with type safety that feels practically invisible is unmatched!

Chez is my way of showing to other TypeScript developers that Scala can be a simple, approachable choice for building powerful AI applications; a topic I have been flirting with for a bit now...


Introducing ChezWiz

Chez and CaskChez are just the beginning. I’ve also been working on ChezWiz, a higher-level library that builds on them and extends into the world of agentic systems.

If Chez is about schemas and specs, and CaskChez is about HTTP validation and OpenAPI integration, then ChezWiz is about putting those building blocks to work: connecting agents, to then build out agentic workflows, and making the developer experience more seamless.

import chezwiz.agent.*
import chezwiz.agent.providers.OpenAIProvider
import chez.derivation.Schema

@Schema.title("Summary")
case class Summary(
  @Schema.minLength(1) 
  text: String
) derives Schema

val agent = Agent(
  name = "Summarizer",
  instructions = "Summarize briefly.",
  provider = new OpenAIProvider(sys.env("OPENAI_API_KEY")),
  model = "gpt-4o-mini"
)

val md = RequestMetadata(userId = Some("user-uuid-123"))
val res = agent.generateObject[Summary](
  "Summarize: Scala 3 match types.", 
  md
)

Putting together Chez and ChezWiz


A Pragmatic Approach

One thing I want to be clear about: I respect the hardcore functional part of Scala’s culture, but I want Chez to feel approachable for myself, & especially for developers coming from JavaScript, or other backgrounds.

My goals are to simply: make Scala delightful and practical for real work. That’s the community and style I want to help build; one that’s curious, pragmatic, and willing to explore how Scala can open new doors in building powerful every day applications, simply.


Le Chez, Awaits

Over the past few months, I’ve realized Chez is becoming more than a single library; it’s turning into an ecosystem for me, built on top of this simple approach which is heavily inspired by the lihaoyi ecosystem that helped cement my mental models in Scala.

That said, all of this is still new to me. I’m sure I’ve done things that more seasoned Scala developers might look at and think, wtf is this? But I’m here to learn, grow, and keep refining the approach and style that makes Scala feel approachable and fun.

My hope is that, in the not-so-distant future, reaching for Scala to build my next application will be as easy —> if not easier — than reaching for TypeScript.

If you’re curious, check out the repo, try them out, and let me know what you think. I’d love feedback from both Scala veterans and newcomers alike.

GitHub - silvabyte/Chez
Contribute to silvabyte/Chez development by creating an account on GitHub.

Read more