Running using the AWS serverless stack
Tapir server endpoints can be packaged and deployed as an AWS Lambda function. You can utilize a single lambda function for multiple endpoints (“Fat Lambda”), or deploy the same jar multiple times, so that each handles its own endpoint or subset of endpoints. To invoke the function, HTTP requests can be proxied through AWS API Gateway.
To configure API Gateway routes, and the Lambda function, tools like AWS SAM, AWS CDK or Terraform can be used, to automate cloud deployments.
For an overview of how this works in more detail, see this blog post.
Runtimes & interpreters
AWS Lambda supports several runtimes — the language-specific environment in which your function code executes. Tapir supports three of them, each described in its own section below.
Note: the handler type parameter (AwsRequest or AwsRequestV1) determines the
expected API Gateway request format. Use AwsRequest for API Gateway V2 (HTTP
API) and AwsRequestV1 for API Gateway V1 (REST API). V1 requests are
automatically normalized to V2 internally.
Java runtime
With the Java runtime, you package your code as a fat JAR (via assembly) and
upload it to AWS, which manages the JVM. The Lambda entry point implements the
AWS
RequestStreamHandler
interface, which is provided by the aws-lambda-java-runtime-interface-client
dependency (pulled in transitively by all tapir AWS Lambda modules). You can use
this with direct-style, cats-effect, or ZIO:
Direct-style
No effect library needed. Extend SyncLambdaHandler, provide your endpoints via
getAllEndpoints, and you’re done — the class directly implements
RequestStreamHandler.
Uses AwsSyncServerInterpreter (AwsRequest => AwsResponse).
"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda-core" % "1.13.19"
Example: SyncLambdaApiExample
cats-effect
Extend LambdaHandler[F, R], provide your endpoints via getAllEndpoints, and
implement handleRequest by calling process(input, output).unsafeRunSync().
Uses AwsCatsEffectServerInterpreter (AwsRequest => F[AwsResponse]).
"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.13.19"
Examples: LambdaApiExample, V1 variant
ZIO
Create a handler instance via ZioLambdaHandler.default(endpoints), then call
handler.process[AwsRequest](input, output) from a RequestStreamHandler
implementation, running the ZIO effect with Runtime.default.unsafe.run(...).
Uses AwsZioServerInterpreter (AwsRequest => RIO[Env, AwsResponse]).
"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda-zio" % "1.13.19"
Example: ZioLambdaHandlerImpl
Custom runtime
As an alternative to the AWS-provided Java runtime, you can use a custom runtime where your application includes its own runtime loop that polls the Lambda Runtime API for invocations via HTTP. This can be useful when running in custom containers or with GraalVM native images. The tradeoff is an additional dependency on an HTTP client (sttp client4 with the fs2 backend).
cats-effect
Extend AwsLambdaIORuntime and provide your endpoints. The base class
implements main and runs the polling loop via AwsLambdaRuntime.
Uses AwsCatsEffectServerInterpreter (AwsRequest => F[AwsResponse]).
"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.13.19"
NodeJS runtime
You can also compile your Scala code to JavaScript via Scala.js and run it on Node.js. The main benefit are faster cold starts, though with AWS’s SnapStart, this might or might not be a significant advantage.
Handler functions are exported to JavaScript using @JSExportTopLevel and
return a js.Promise[AwsJsResponse]. Use AwsJsRouteHandler to bridge between
the JS request/response types and tapir’s Route[F]. You can use this with
either Future or cats-effect:
Future
Uses AwsFutureServerInterpreter (AwsRequest => Future[AwsResponse]).
"com.softwaremill.sttp.tapir" %%% "tapir-aws-lambda-core" % "1.13.19"
Example: LambdaApiJsExample
cats-effect
Uses AwsCatsEffectServerInterpreter (AwsRequest => IO[AwsResponse]).
Supports both plain Route[IO] (via catsIOHandler) and Resource[IO, Route[IO]] (via catsResourceHandler).
"com.softwaremill.sttp.tapir" %%% "tapir-aws-lambda" % "1.13.19"
Example: LambdaApiJsResourceExample
Deployment
To make it possible, to call your endpoints, you will need to deploy your application to Lambda, and configure Amazon API Gateway. Tapir leverages ways of doing it provided by AWS, you can choose from: AWS SAM template file, terraform configuration, and AWS CDK.
You can start by adding one of the following dependencies to your project, and then follow examples:
"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.13.19"
"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.13.19"
"com.softwaremill.sttp.tapir" %% "tapir-aws-cdk" % "1.13.19"
Examples
Go ahead and clone tapir project. To deploy you application to AWS you will need to have an AWS account and AWS command line tools installed.
SAM
SAM can be deployed using Java runtime or NodeJS runtime. For each of these cases first you will have to install AWS SAM command line tool, and create a S3 bucket, that will be used during deployment. Before going further, open sbt shell, as it will be needed for both runtimes.
For Java runtime, use sbt to run assembly task, and then runMain sttp.tapir.serverless.aws.examples.SamTemplateExample, this will generate
template.yaml sam file in main directory
For NodeJS runtime, first generate AWS Lambda yaml file by execution inside sbt
shell command awsExamples/runMain sttp.tapir.serverless.aws.examples.SamJsTemplateExample, and then build Node.js
module with awsExamplesJS/fastLinkJS, it will create all-in-one JS file under
tapir/serverless/aws/examples/target/js-2.13/tapir-aws-examples-fastopt/main.js
From now the steps for both runtimes are the same:
Before deploying, if you want to test your application locally, you will need Docker. Execute
sam local start-api --warm-containers EAGER, there will be a link displayed at the console outputTo deploy it to AWS, run
sam deploy --template-file template.yaml --stack-name sam-app --capabilities CAPABILITY_IAM --s3-bucket [name of your bucket]. The console output should print url of the application, just add/api/helloto the end of it, and you should seeHello!message. Be aware in case of Java runtime, the first call can take a little longer as the application takes some time to start, but consecutive calls will be much faster.When you want to rollback changes made on AWS, run
sam delete --stack-name sam-app
Terraform
Terraform deployment requires you to have a S3 bucket.
Install Terraform
Run
assemblytask inside sbt shellOpen a terminal in
tapir/serverless/aws/examples/target/jvm-2.13directory. That’s where the fat jar is saved. You need to upload it into your s3 bucket. Using command line tools:aws s3 cp tapir-aws-examples.jar s3://{your-bucket}/{your-key}.Run
runMain sttp.tapir.serverless.aws.examples.TerraformConfigExample {your-aws-region} {your-bucket} {your-key}inside sbt shellOpen terminal in tapir root directory, run
terraform initandterraform apply
That will create api_gateway.tf.json configuration and deploy Api Gateway and
lambda function to AWS. Terraform will output the url of the created API Gateway
which you can call followed by /api/hello path.
To destroy all the created resources run terraform destroy.
CDK
First you need to install:
Open sbt shell, then run
assemblytask, and executerunMain sttp.tapir.serverless.aws.examples.CdkAppExampleto generate CDK application template undercdkdirectoryGo to
cdkand runnpm install, it will create all files needed for the deploymentBefore deploying, if you want to test your application locally, you will need Docker and AWS SAM command line tool , then execute
cdk synth, andsam local start-api -t cdk.out/TapirCdkStack.template.json --warm-containers EAGERTo deploy it to AWS simply run
cdk deployWhen you want to rollback changes made on AWS, run
cdk destroy