In our previous blog post we went through the official demo to create a basic Scalatra service http://honstain.com/scalatra-giter8/. Now we want to expand that to implement a RESTful API.

WARNING - I am not a Scalatra expert, this blog is meant to document my journey of discovery as I work towards a more fully featured service.

Adding Support for JSON

The best reference I found here is to follow the guide for http://scalatra.org/guides/2.3/formats/json.html. By adding two new dependencies to the build.sbt file for scalatra-json and json4s-jackson we will have everything we need to get started.

I started out with an extremely basic implementation of a system to track inventory (there is no persistence layer set up and everything is kept in memory in a naive fashion).

import org.scalatra._
// JSON-related libraries
import org.json4s.{DefaultFormats, Formats}
// JSON handling support from Scalatra
import org.scalatra.json._


class ToyInventory extends ScalatraServlet with JacksonJsonSupport {

  protected implicit val jsonFormats: Formats = DefaultFormats

  before() {
    contentType = formats("json")
  }

  get("/") {
    InventoryData.all
  }

  post("/") {
    val newInventory = parsedBody.extract[Inventory]
    InventoryData.all = newInventory :: InventoryData.all
    newInventory
  }

}

case class Inventory(sku: String, qty: Int, description: String)

object InventoryData {

  var all = List(
    Inventory("ZL101", 1, "Black shoes"),
    Inventory("ZL102", 0, "Red dress"),
    Inventory("ZL103", 4, "Block of wood"),
  )
}

I won't try to duplicate the information in the official guide, but we get support for JSON by mixing in the JacksonJsonSupport trait, configuring a before() filter, and using an implicit to specific desired format (a quick review of the README on https://github.com/json4s/json4s could be helpful here).

The result here is that now we have a toy example that we can GET and POST to. I have included the Postman examples below (Postman is my preferred tool for interacting with RESTful services).

Adding Tests to Our New Endpoints

At this stage I have created two endpoints for this toy inventory system:

  • GET /
  • POST /

If you used the giter8 template to generate the service, you will already have a basic test, you can use IntelliJ to run the tests for you (it gave me more than one choice, I wanted to use ScalaTest).

The test passes but it only verifies that the GET / endpoint returns an HTTP response code of 200. Lets start by adding another test for the POST. I referenced this to convert my Inventory object to JSON https://commitlogs.com/2017/01/14/serialize-deserialize-json-with-json4s-in-scala/

import org.json4s.DefaultFormats
import org.scalatra.test.scalatest._
import org.json4s.jackson.Serialization.write

class ToyInventoryTests extends ScalatraFunSuite {

  implicit val formats = DefaultFormats

  addServlet(classOf[ToyInventory], "/*")

  test("GET / on ToyInventory should return status 200") {
    get("/") {
      status should equal (200)
    }
  }

  test("POST / on ToyInventory should return status 200") {
    post("/", write(Inventory("ZL104", 3, "Pine Block of wood"))) {
      status should equal (200)
    }
  }
}

What if we want to verify the response as well? We can add additional tests and validate the body of the response. You might chose to consolidate your tests more, I kept the status code check separate from the body check in an effort to help the reader.

package org.bitbucket.honstain.app

import org.json4s.DefaultFormats
import org.scalatra.test.scalatest._
import org.json4s.jackson.Serialization.write

class ToyInventoryTests extends ScalatraFunSuite {

  implicit val formats = DefaultFormats

  addServlet(classOf[ToyInventory], "/*")

  test("GET / on ToyInventory should return status 200") {
    get("/") {
      status should equal (200)
    }
  }

  test("GET / on ToyInventory should return inventory list") {
    get("/") {
      val expectedResult = List(
        Inventory("ZL101", 1, "Black shoes"),
        Inventory("ZL102", 0, "Red dress"),
        Inventory("ZL103", 4, "Block of wood"),
      )
      body should equal (write(expectedResult))
    }
  }

  test("POST / on ToyInventory should return status 200") {
    val newInventory = Inventory("ZL104", 3, "Pine Block of wood")
    post("/", write(Inventory("ZL104", 3, "Pine Block of wood"))) {
      status should equal (200)
    }
  }

  test("POST / on ToyInventory should return newly created inventory") {
    val newInventory = Inventory("ZL104", 3, "Pine Block of wood")
    post("/", write(Inventory("ZL104", 3, "Pine Block of wood"))) {
      // Using a string representation of the expected Inventory for demonstration purposes.
      body should equal ("""{"sku":"ZL104","qty":3,"description":"Pine Block of wood"}""")
    }
  }
}

We have used the ScalatraFunSuite which supports a simple structure for tests, I would recommend taking a look at the ScalaTest documentation for FunSuite http://www.scalatest.org/getting_started_with_fun_suite

You can source for these changes in the project repo here: https://bitbucket.org/honstain/scalatra-example-blog/commits/ffda1e40fc8db9ae79beaea123d542459b6d3682

References I found Helpful During This Guide: