Backend Helpers | Automation and Software Development for Cloud Applicationses

Introduction to REST APIS with Scala and Spray

Spray is a collection of lightweight Scala libraries providing client and server-side REST/HTTP support on top of Akka toolkit. 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 the 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:


sbt
> re-stop

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 usespray-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 create now a class child of spray.routing.HttpService toimplement the Restful http logic. This class uses functionality from the 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