Running as a Play server¶
To expose endpoint as a play-server first add the following dependencies:
"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "0.19.0-M2"
and (if you don’t already depend on Play)
"com.typesafe.play" %% "play-akka-http-server" % "2.8.7"
or
"com.typesafe.play" %% "play-netty-server" % "2.8.7"
depending on whether you want to use netty or akka based http-server under the hood.
Then import the object:
import sttp.tapir.server.play.PlayServerInterpreter
This object contains the toRoute
and toRoutesRecoverError
methods. This first requires the
logic of the endpoint to be given as a function of type:
I => Future[Either[E, O]]
The second recovers errors from failed effects, and hence requires that E
is
a subclass of Throwable
(an exception); it expects a function of type I => Future[O]
. For example:
import sttp.tapir._
import sttp.tapir.server.play.PlayServerInterpreter
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import akka.stream.Materializer
import play.api.routing.Router.Routes
implicit val materializer: Materializer = ???
def countCharacters(s: String): Future[Either[Unit, Int]] =
Future(Right[Unit, Int](s.length))
val countCharactersEndpoint: Endpoint[String, Unit, Int, Any] =
endpoint.in(stringBody).out(plainBody[Int])
val countCharactersRoutes: Routes =
PlayServerInterpreter().toRoutes(countCharactersEndpoint)(countCharacters _)
Note that the second argument to toRoutes
is a function with one argument, a tuple of type I
. This means that
functions which take multiple arguments need to be converted to a function using a single argument using .tupled
:
import sttp.tapir._
import sttp.tapir.server.play._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import akka.stream.Materializer
import play.api.routing.Router.Routes
implicit val materializer: Materializer = ???
def logic(s: String, i: Int): Future[Either[Unit, String]] = ???
val anEndpoint: Endpoint[(String, Int), Unit, String, Any] = ???
val aRoute: Routes = PlayServerInterpreter().toRoutes(anEndpoint)((logic _).tupled)
Bind the routes¶
Creating the HTTP server manually¶
An HTTP server can then be started as in the following example:
import play.core.server._
import play.api.routing.Router.Routes
val aRoute: Routes = ???
object Main {
// JVM entry point that starts the HTTP server
def main(args: Array[String]): Unit = {
val playConfig = ServerConfig(port =
sys.props.get("http.port").map(_.toInt).orElse(Some(9000))
)
NettyServer.fromRouterWithComponents(playConfig) { components =>
aRoute
}
}
}
As part of an existing Play application¶
Or, if you already have an existing Play application, you can create a Router
class and bind it to the application.
First, add a line like following in the routes
files:
-> /api api.ApiRouter
Then create a class like this:
class ApiRouter @Inject() () extends SimpleRouter {
override def routes: Routes = {
anotherRoutes.orElse(tapirGeneratedRoutes)
}
}
Find more details about how to bind a Router
to your application in the Play framework documentation.
Configuration¶
The interpreter can be configured by providing a PlayServerOptions
value, see
server options for details.
Defining an endpoint together with the server logic¶
It’s also possible to define an endpoint together with the server logic in a single, more concise step. See server logic for details.