Contents

Tapir 1.0 released webp image

We're happy to announce the release of tapir 1.0!

After almost 4 years of development and multiple 0.x releases, the declarative, type-safe web endpoints library has reached the important milestone of a stable release.

libraryDependencies += 
  "com.softwaremill.sttp.tapir" %% "tapir-core" % "1.0.0"

import sttp.tapir._

The goal of tapir is to provide a programmer-friendly, reasonably type-safe API to expose, consume, and document HTTP endpoints. How do we achieve that? Read on! :)

What is tapir?

If you're not yet familiar with tapir, now is a great time to get started! With tapir, you can describe HTTP API endpoints as immutable Scala values. Each endpoint can contain multiple input and output parameters. An endpoint specification can be interpreted as a server, a client or documentation.

An example endpoint might be as follows:

val addBook: PublicEndpoint[(Book, AuthToken), String, Unit, Any] = 
  endpoint
    .post
    .in("books" / "add")
    .in(jsonBody[Book]
        .description("The book to add")
        .example(Book("Pride and Prejudice", Author("Jane Austen"))))
    .in(header[AuthToken](HeaderNames.Authorization)
        .description("The token is 'secret'"))
    .errorOut(stringBody)

When interpreting as a server, you need to couple the description of the endpoint (which contains only the metadata) with a function implementing the business logic. The signature of this function must match the shape of the endpoint, and this is verified at compile time. There's a number of server interpreters, integrating with server implementations such as akka-http, http4s, netty, playframework, vertx and others:

// returns either a string error or unit, which here
// means successful addition of the book
def bookAddLogic(
    book: Book, token: AuthToken): Future[Either[String, Unit]] = ???

val addBookServerEndpoint = 
  addBook.serverLogic((bookAddLogic _).tupled)

val akkaRoute = 
  AkkaHttpServerInterpreter().toRoute(addBookServerEndpoint)

Tapir supports all major Scala stacks: stdlib Future-based, cats-effect, and ZIO. So whatever your preference, we've got you covered!

On the other hand, when interpreting the same endpoint as a client, you'll get a function. That function needs to be provided with parameters, with which the endpoint should be called:

val result: Either[String, Unit] = SttpClientInterpreter()
  .toQuickClient(addBook, Some(uri"http://library.com"))
  .apply(Book("Iliad", Author("Homer")))

Finally, documentation interpreters allow converting the high-level description, captured as a Scala case class, into OpenAPI yaml, and exposing the yaml using the Swagger UI:

tapir openapi swagger

There are also other interpreters available, including AsyncAPI documentation (for web socket endpoints), and serverless deployments (currently interpreting the endpoint as AWS Lambda configuration). Explore the documentation to learn more!

You can also generate a tapir project with your preferred stack. Just head over to adopt-tapir.softwaremill.com, fill in the desired name, import it into your favourite IDE and get started!

Not a Scala user yet? You might want to visit our Scala starter page first!

Why tapir?

Given the plethora of possibilities to define an HTTP API in your application, why should you consider tapir?

First of all, there are all the good reasons why Scala should be on your candidate list. This modern, flexible, functional/object-oriented language is a solid foundation to model complex business domains and create maintainable code. Using Scala, the domain concepts can be brought to the front, instead of being drowned in tons of infrastructure code. The immutability of the data structures, and the fact that everything in Scala is an expression and has a value, make a much bigger difference in development than it might seem at first sight!

Tapir leverages Scala's possibilities in many ways:

  • type-safety: compile-time guarantees, develop-time completions, read-time information
  • declarative: separate the shape of the endpoint (the "what"), from the server logic (the "how")
  • OpenAPI / Swagger integration: generate documentation from endpoint descriptions
  • observability: leverage the metadata to report rich metrics and tracing information
  • abstraction: re-use common endpoint definitions, as well as individual inputs/outputs
  • library, not a framework: integrates with your stack

Explore the examples to see tapir in action.

What does "stable" mean?

With the 1.0 release, we're promoting the core module for Scala 2 to the "stable" status. This means that binary compatibility is guaranteed within a major version. That is, there won't be any binary-incompatible changes in that module until tapir 2.0. This is guarded by MiMa.

Tapir has many other modules (currently, over 40) that have different levels of stability. We've categorised them as stable, stabilising, and experimental.

While so far we're making only one module stable (however, it's the most important one!), the majority of other modules are in the stabilising phase. Subsequent minor releases of tapir will see them transition to stable. See the stability page in the docs for a comprehensive list.

The fact that core is stable means that third-party libraries can safely depend on that module, which defines the core tapir abstractions: the Endpoint, Schema, Codec, and ServerEndpoint. We're hoping to see some exciting developments in the form of independently-developed interpreters!

As far as Scala 3 is concerned, we're still considering some changes in the way Schema derivation macros are implemented (the macros in Scala 3 are still seeing some development themselves), so there may be some binary-incompatible changes in this area.

The community

The release of tapir 1.0 was only possible thanks to our fantastic community. Over the last 4 years, we've received continuous feedback in the form of questions on gitter, issues on GitHub and pull requests, which helped us shape tapir and cover as many corner cases as possible.

We've explored a number of different designs, some of them documented in the ADRs. Our early adopters have been patiently migrating their code bases from one 0.x version to another, helping us build confidence that we're going in the right direction, or stopping us when a new feature turned out to be too complex.

Thank you for your feedback!

tapir infographic

What's next

After celebrating the release of tapir 1.0, we're going back to hard work! Apart from the stabilisation efforts of non-core modules that I've mentioned before, there are also some exciting features that we'd like to work on. Examples of these are exposing the endpoints using gRPC, integrating with Amazon's CDK, or Google/GCP and Microsoft/Azure serverless stacks. There are also many minor features and additions, which don't require changes in the core data structures but need spending some time on. If you'd like to help, the full list is on GitHub!

Ultimately, it's up to you – the users – where tapir will head next. Join our community, let us know which features and interpreters are most useful, which are missing, what is easy to do with tapir, and what should be easier.

Going back to our overarching goal of simplifying the process of developing, maintaining, and documenting an HTTP API, can we do better? We already push a lot of the work to the compiler, aiming at shortening the feedback cycle and eliminating a whole class of bugs that would otherwise require writing a test (or worse, manual testing!). But that doesn't mean it's the end of the road!

Your next step: adopt a tapir, and generate your first tapir-based project today!

Blog Comments powered by Disqus.