Restful services in Scala with Sprayio

Spray is a suite of lightweight Scala libraries providing client and server-side REST/HTTP support on top of Akka toolkit. It provides a set of integrated components for most of REST/HTTP needs. In this post we are going to create a simple restful API with spray.

Getting Started

Spray can run as an standalone service or inside a servlet container. There is a spray template project on github that can be used as starting point. This github repository has different branches, one branch for every possible configuration, we will clone this repo and we will use the branch on_spray-can_1.3_scala-2.11 . This branch contains the structure for a standalone spray-can, Scala 2.11 + Akka 2.3 + spray 1.3

     git clone git@github.com:spray/spray-template.git
     git checkout on_spray-can_1.3_scala-2.11

Running tests, Starting and stopping applicaton

Running tests:

    sbt test

To run server you need to execute sbt:

    sbt
    > re-start
    [info] Application demo not yet started
    [info] Starting application demo in the background ...
    demo Starting com.example.Boot.main()
    [success] Total time: 0 s, completed 15-Apr-2015 16:11:08
    > demo [INFO] [04/15/2015 16:11:09.323] [on-spray-can-akka.actor.default-dispatcher-3] [akka://on-spray-can/user/IO-HTTP/listener-0] Bound to localhost/127.0.0.1:8080

You can now request the applicaton using curl command:

        $ curl http://localhost:8080

Stopping application:

    sbt
    > re-stop

User Resource Example

We are going to implement a basic CRUD for a user resource. Just Crate and Get for now.

Json serializer

In order to handle json serialization, we will use spray-json to create the class com.notempo1320.ApiJsonProtocol that will implement a custom json protocol:


    package com.notempo1320

    import spray.httpx.SprayJsonSupport._
    import spray.httpx.SprayJsonSupport
    import spray.json._
    import DefaultJsonProtocol._

    case class User(var id: Option[Long], username: String, email: String)

    object ApiJsonProtocol extends DefaultJsonProtocol with SprayJsonSupport {
      //jsonFormatX depends of number of parameters that the object receives
      implicit val userFormat = jsonFormat3(User)

    }

User Service

We are going to create a class child of spray.routing.HttpService that will implement the Restful http logic. This class uses functionality from spray-routing module, this module provides a high-level routing DSL for defining RESTful web services.

    // this trait defines our service behavior independently from the service actor
    trait UserService extends HttpService {
      var userList = new ListBuffer[User]()

      val userRoute =
        path("user") {
          get {
            respondWithMediaType(`application/json`) {

              userList.append(User(Option(util.Random.nextInt(10000).toLong), "user1", "email1"))
              userList.append(User(Option(util.Random.nextInt(10000).toLong), "user2", "email2"))
              complete(userList.toList.toJson.compactPrint)
            }
          } ~
          post {
            entity(as[User]) { user =>
              val user2 = User(Option(util.Random.nextInt(10000).toLong), user.username, user.email)
              userList += user2
              respondWithMediaType(`application/json`) {
                complete(StatusCodes.Created, user2.toJson.compactPrint)
              }
            }
          }
        }
    }

The code above use routes to deliver the request to the appriate component.

User Actor

Now we will create an Actor that will wrap UserService logic:

    // we don't implement our route structure directly in the service actor because
    // we want to be able to test it independently, without having to spin up an actor
    class UserActor extends Actor with UserService {

      // the HttpService trait defines only one abstract member, which
      // connects the services environment to the enclosing actor or test
      def actorRefFactory = context

      // this actor only runs our route, but you could add
      // other things here, like request stream processing
      // or timeout handling
      def receive = runRoute(userRoute)
    }

Api class

Api class is a console command wich will be responsible to run spray-can server:

    package com.notempo1320

    import akka.actor.{ActorSystem, Props}
    import akka.io.IO
    import spray.can.Http
    import akka.pattern.ask
    import akka.util.Timeout
    import scala.concurrent.duration._

    object Api extends App {

      // we need an ActorSystem to host our application in
      implicit val system = ActorSystem("on-spray-can")

      // create and start our service actor
      val service = system.actorOf(Props[UserActor], "user-service")

      implicit val timeout = Timeout(5.seconds)
      // start a new HTTP server on port 8080 with our service actor as the handler
      IO(Http) ? Http.Bind(service, interface = "localhost", port = 7000)
    }

Updating Configuration file build.sbt


    organization  := "com.notempo1320"

    version       := "0.1"

    scalaVersion  := "2.11.6"

    scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")

    libraryDependencies ++= {
      val akkaV = "2.3.9"
      val sprayV = "1.3.3"
      Seq(
        "io.spray"            %%  "spray-can"     % sprayV,
        "io.spray"            %%  "spray-routing" % sprayV,
        "io.spray"            %%  "spray-json"    % "1.3.1",
        "io.spray"            %%  "spray-httpx"   % sprayV,
        "io.spray"            %%  "spray-testkit" % sprayV  % "test",
        "com.typesafe.akka"   %%  "akka-actor"    % akkaV,

        "com.typesafe.akka"   %%  "akka-testkit"  % akkaV   % "test",
        "org.specs2"          %%  "specs2-core"   % "2.3.11" % "test"
      )
    }



    Revolver.settings
    mainClass in (Compile, run) := Some("com.notempo1320.Api")
    mainClass in Revolver.reStart := Some("com.notempo1320.Api")

Creating an user

    curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username": "myname", "email": "mymail@server.com"}' http://localhost:7000/user

Getting a list of users

  curl http://localhost:7000/user

You can find source code of this example here

References