How to create versioned endpoints using Akka HTTP?

How to create versioned endpoints using Akka HTTP?

This topic is based on a conversation between an anonymous user of Akka and our “Ask AI about Akka” bot in the documentation.

Creating versioned endpoints in Akka HTTP involves defining different routes for each version of your API. Each route in Akka HTTP contains one or more directives such as path, get, post, complete, etc.
Here is a basic example of how you might define versioned endpoints:

Route v1Routes = pathPrefix("v1", () -> concat(
  path("users", () -> post(() -> complete("User created in v1"))),
  path("users", () -> get(() -> complete("User retrieved in v1")))
));

Route v2Routes = pathPrefix("v2", () -> concat(
  path("users", () -> post(() -> complete("User created in v2"))),
  path("users", () -> get(() -> complete("User retrieved in v2")))
));

Route allRoutes = concat(v1Routes, v2Routes);

In this example, we have two versions of the API: v1 and v2. Each version has its own set of routes, which are grouped under a pathPrefix corresponding to the version number. The allRoutes variable combines all the versioned routes into a single route that can be bound to a server.

Please note that this is a simplified example. In a real-world application, you would likely have more complex routes and might separate them into different classes or files for better organization.

For more information on routing in Akka HTTP, you can refer to the official documentation.

What does ~ operator do with directives?

The ~ operator in Akka HTTP is used to connect two or more routes. It allows the next specified route to get a chance at a request if the first route “rejected” it. This concept of “rejections” is used by Akka HTTP for maintaining a more functional overall architecture and to properly handle all kinds of error scenarios.

Here’s an example of how the ~ operator is used to combine directives:

def innerRoute(id: Int): Route =
  get {
    complete {
      "Received GET request for order " + id
    }
  } ~
    put {
      complete {
        "Received PUT request for order " + id
      }
    }

val route: Route = path("order" / IntNumber) { id => innerRoute(id) }

In this example, the ~ operator is used to combine a get directive and a put directive into a single route. If a GET request is received, the first part of the route will handle it. If a PUT request is received, the first part of the route will “reject” it, and the second part of the route will handle it.

However, it’s worth noting that while the ~ operator is commonly used in Akka HTTP apps, the concat directive is recommended as a safer alternative for composing routes.

For more information, you can refer to the official documentation.

What is the difference between ~ and concat?

The ~ operator and concat directive in Akka HTTP are both used for combining or chaining routes. However, there are some differences in their behavior and usage.

The ~ operator is used to chain two or more routes in a way that allows the next specified route to get a chance at a request if the first route “rejected” it. This is useful for maintaining a more functional overall architecture and handling various error scenarios. However, forgetting the ~ character in between directives can result in valid Scala code that compiles but does not work as expected. This is because what would be intended as a single expression would actually be multiple expressions, and only the final one would be used as the result of the parent directive.

On the other hand, the concat directive is recommended as a safer alternative to the ~ operator. It is used to concatenate two or more routes, but unlike the ~ operator, it does not have the risk of resulting in unexpected behavior if forgotten in between directives.

Here’s an example of how concat is used:

val route =
  concat(
    path("foo") {
      complete("/foo")
    },
    path("foo" / "bar") {
      complete("/foo/bar")
    }
  )

In this example, the concat directive is used to combine two routes. If a request is made to “/foo”, the first route will handle it. If a request is made to “/foo/bar”, the second route will handle it.
For more information, you can refer to the official documentation and this guide.

Note that we do recommend the concat directive over the ~ directive nowadays. ~ is mostly kept around for backwards compatibility.

1 Like