Time and time again, I have to create a REST service for an application somewhere. I admit that its a bit hard to remember the signatures. So I end up having to look up many of the pieces in the docs every single time. I am putting this out for anyone (including me) to use as a starting point with signatures and flows already in place, so that Akka HTTP REST services can be easily copied and pasted together from this article.

In general, I use Akka HTTP to build REST services, because of its concise APIs, run speed, and how well it integrates with Akka Streams. If your apps are written in a functional coding style, you probably use Akka Streams in many of them so you can think of building a REST service with Akka HTTP as simply extending those Akka streams over HTTP. Its the natural thing to do in a Scala app.

You could of course use Play, but why should you? Play would bring in a lot of overhead that you don’t need. What I am showing here is based on the Akka HTTP documentation and demonstrates a very lightweight solution to building a REST API, which also happens to be type-safe.

Another nice thing about using Akka HTTP, is that it is well integrated with Spray JSON, so that you never have to worry about serialization. Everything happens automatically. All you have to do is declaring an implicit for each REST post type, and the conversion from case classes to JSON and back will be done for you, which will keep your boilerplate to the absolute minimum.

None of this stuff is new, I just couldn’t find a concise enough example, that had all the pieces I needed in one place, so I created one. Here’s the full example code:

package com.synkre.examples

import akka.actor.ActorSystem
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives.{path, _}
import akka.http.scaladsl.server.directives.MethodDirectives.get
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.{Http, server}
import akka.stream.ActorMaterializer
import akka.util.Timeout
import scala.concurrent.Future
import spray.json.DefaultJsonProtocol.jsonFormat1
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._

import scala.concurrent.duration._

object Protocol{
  case class RequestWeatherReport(day: String)
  case class WeatherReport(isSunny: String)

  implicit val fmt1 = jsonFormat1(RequestWeatherReport)
  implicit val fmt2 = jsonFormat1(WeatherReport)
}

class WeatherBackend{
  import Protocol._
  def requestWeatherReport(requestWeatherReport: RequestWeatherReport): Future[WeatherReport] =
    Future.successful{
      if (requestWeatherReport.day == "Sunday")
        WeatherReport("sunny")
      else
        WeatherReport("raining")
    }
}

class WeatherService(backend: WeatherBackend)(implicit system: ActorSystem, materializer: ActorMaterializer) {
  import Protocol._
  implicit val ec = system.dispatcher
  implicit val timeout = Timeout(10 seconds)

    val api: server.Route =
      concat(
        get {
          path("info") {
            complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>The server is up!</h1>"))
          }
        },
        post {
          path("requestWeatherReport") {
            entity(as[RequestWeatherReport]) { requestWeatherReport: RequestWeatherReport =>
              onComplete(backend.requestWeatherReport(requestWeatherReport))(complete(_))
            }
          }
        }
      )

  val binding = Http().bindAndHandle(
    handler = api,
    interface = "localhost",
    port = 20000)
}

//This is a the sample server
object WeatherServiceApp extends App{
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  val backend = new WeatherBackend
  val service = new WeatherService(backend)
}

//This is the client
object WeatherServiceClientApp extends App{
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher
  import Protocol._

  val reportFuture: Future[WeatherReport]  =
    for(requestEntity <- Marshal(RequestWeatherReport("Sunday")).to[RequestEntity];
      req = HttpRequest(
         method = HttpMethods.POST,
         uri = "http://localhost:20000/requestWeatherReport",
         entity = requestEntity);
      response <- Http().singleRequest(req);
      _ = system.log.info("Post Response:" + response.entity.toString);
      responseMessage <- Unmarshal(response.entity).to[WeatherReport]
  ) yield responseMessage

  reportFuture.map(report =>
    println(s"We got a weather report $report")
  )
}

Follow these steps

1. Create an HTTP service with path entries for each request

Follow the code sample to create an HTTP service. It has a DSL that allows for the creation of paths with minimum boilerplate code. For the purposes of this example we set up just one such request, requestWeatherReport. But for each request type you should add a path entry.

2. Add a case class for each message and response type

Each path should be associated with a pair of request and reply case classes. One of the nicest things about using this technology is that we don’t have to bother with hand serializing messages. Just create the case class with the fields you want the JSON for your REST posts to contain. So in our example I created a RequestWeatherReport case class with a field for the day we want the weather report for and a WeatherReport case class with a field that tells us the weather forecast.

3. Add a format for each message. Variable name not important.

In order for the automatic serialization to work don’t forget that the following dependency is required: “com.typesafe.akka” %% “akka-http-spray-json” % “10.1.7”. In Addition you must import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._. If you don’t do this you will immediately see typing errors for the case classes.

4. Create a back end class to separate out all the HTTP-related code from business logic

Add a method for each message to access slow business logic, DB, etc, and return a Future of the response message. The HTTP service simply calls each of these methods with the case class that was created as a result of the automatic un-marshaling and when these methods complete, it takes the future it receives and automatically marshals it back into JSON, then returns it as an HTTP response. It basically does all the tedious work for us so that we won’t have to hand code it.

5. HTTP Client example

Use this flow to access any HTTP service and it will automatically serialize the case classes to and from, just like the server did, using the same APIs of Akka HTTP. I included a full flow example. All you need to do is create such a flow for each request by copying and pasting it and changing the case class name to match the request path your coding.

Conslusion

For me this is the most idiomatic way to code REST services in Scala, because it uses the same tech stack as what many other Scala libraries are based on. This makes it easy to interface our REST service with the rest of the app.