Build REST API in Scala with Play Framework

Reading Time: 4 minutes

Overview

In earlier blogs I discussed about play framework now lets we move to further topics on play.

For building simple, CRUD-style REST APIs in Scala, the Play Framework is a good solution. It has an uncomplicated API that doesn’t require us to write too much code.

In this blog, we’re going to build a REST API in Scala with Play. We’ll use JSON as the data format and look at multiple HTTP methods and status codes.

What we build ?

As our example, we’ll build a student list application. Rather than use a database, we’ll store the student list items in memory.

The application will provide several endpoints, and we’ll build it in small, incremental steps.

We’ll also look at how to run and test our application as we grow it.

Setup the Project

Open a terminal.

Navigate to directories (cd) to the folder that will contain the project folder.

Run the following command and respond to the prompts to create a new project template

$ sbt new playframework/play-scala-seed.g8

This creates a new project with one controller (in the app/controllers directory), two HTML files (in the app/views directory), and a basic configuration (in the conf directory).

As we don’t need them, let’s remove HomeController.scalaindex.scala.html, and main.scala.html files. Let’s also remove the existing content of the routes file.

The First REST Endpoint

Create a Controller

First, we create a new controller class in the app/controllers directory.

@Singleton
class HomeController @Inject()(val controllerComponents: ControllerComponents)
extends BaseController {
}

The new class extends BaseController and has a constructor that’s compatible with it.

We’ve used the @Inject annotation to instruct the Play Framework to pass the required class dependencies automatically. And, we marked the class as a @Singleton so that the framework will create only one instance. This means it will reuse it for every request.

Returning Items

Now, we are going to return the whole student list. Let’s define the data model, create an in-memory collection of tasks, and modify the HomeController to return JSON objects.

Define the Model

First, we create a new class in the app/models directory:

case class Student(id: Int, address: String, name: String)

In-Memory List

Now, we define the list of tasks in the HomeController  class. As this list will be modified, we need the mutable collections package:

class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {

  val studentList = new mutable.ListBuffer[Student]()
  studentList += Student(1,"Delhi","Mayank")
  studentList += Student(2,"Mathura","Yashika")

To help with our testing, we’ve added a couple of hard-coded values in this list to be available from startup.

JSON Formatter

Next, let’s create the JSON formatter that converts the Studentlist object into JSON. We start by importing the JSON library:

import play.api.libs.json._

After that, we create the JSON formatter inside the HomeController class:

implicit val studentListJson = Json.format[Student]
 

We make it an implicit field to avoid having to pass it to the Json.toJson function all the time.

Conditional Logic in the getAll Function

Now we have some data to return, let’s change our getAll function to return the NoContent status code only when the list is empty. Otherwise, it should return the list items converted to JSON:

def getAll(): Action[AnyContent] = Action {
    if (studentList.isEmpty) NoContent else Ok(Json.toJson(studentList))
  }

Testing

Let’s test the GET endpoint:

$ localhost:9000

[
    {
        "id": 1,
        "address": "Delhi",
        "name": "Mayank"
    },
    {
        "id": 2,
        "address": "Mathura",
        "name": "Yashika"
    }
]

Returning One Item

A REST API should also support retrieving individual items via a path parameter. We want to be able to do something like:

$ curl localhost:9000/hello/1

This should return the item, or NotFound if the ID is unknown. Let’s implement that.

Adding a Parameter to the Route

First, we define a new endpoint in the routes file:

GET     /hello/:studentId                controllers.HomeController.getById(studentId:Int)

The notation /hello/:studentId  means that the Play Framework should capture everything after the /hello/ prefix and assign it to the studentId variable. After that, Play calls the getById function and passes the studentId as its first parameter.

Because we have specified the parameter type, it automatically converts the text to a number or returns a BadRequest if the parameter is not a number.

Finding the Matching Element

In the HomeController, let’s add the getById method:

def getById(studentId:Int) : Action[AnyContent] = Action {
    val stdId = studentList.find(_.id == studentId)
    stdId match {
      case Some(value) => Ok(Json.toJson(value))
      case None => NotFound
    }
  }

Our method gets the itemId parameter and tries to find the student list item with the same id.

We’re using the find function, which returns an instance of the Option class. So, we also use pattern matching to distinguish between an empty Option and an Option with a value.

When the item is present in the student list, it’s converted into JSON returned in an OK response. Otherwise, NotFound is returned – causing an HTTP 404.

Testing

Now, we can try to get an item that is found:

$ localhost:9000/hello/1

    {
        "id": 1,
        "address": "Delhi",
        "name": "Mayank"
    }

Or an item that isn’t:

$ localhost:9000/hello/999

204 No Content.

PUT and DELETE Methods

The Play Framework handles all HTTP methods, so when we want to use PUT or DELETE, we need to configure them in the routes file. For example, we can define endpoints that mark an item as completed and remove completed items:

PUT     /hello/:id                       controllers.HomeController.markAsDone(id:Int)
DELETE  /hello/done/:id                  controllers.HomeController.deleteAllDone(id:Int)

Adding a New Task

Finally, our implementation should add a new task when we send a POST request containing a new item:

Note that we must specify only the description. Our application will generate the id and the initial status.

POST Endpoint in the routes File

First, let’s specify the new endpoint in the routes file:

POST     /hello/addItem                  controllers.HomeController.addNewItem

Data Transfer Object

Now, we need to add a new class to the app/models directory. We create a new data transfer object (DTO), which contains the description field:

case class NewStudentItem(address:String , name:String)

Reading the JSON Object

In the HomeController, we need a JSON formatter for that new class:

implicit val newStudentItem = Json.format[NewStudentItem]

Let’s also define a method to create NewStudentListItem objects from the JSON input:

In our method, content.asJson parses the given JSON object and returns an OptionWe would get a valid object only if the deserialization were successful.

If the caller has sent us content that cannot be deserialized as a NewStudentItem or Content-Type was not application/json, we end up with a None instead.

Adding a New Item

Now, let’s add the following code to the end of addNewItem. This will either store the new object and return HTTP Created or respond with BadRequest:

def addNewItem(): Action[JsValue] = Action(parse.json) { implicit request =>
      request.body.validate[NewStudentItem].asOpt
        .fold{
         BadRequest("No item added")
        }
    {
      response =>
      val nextId = studentList.map(_.id).max +1
        val newItemAdded = Student(nextId,response.address,response.name)
        studentList += newItemAdded
        Ok(Json.toJson(studentList))
    }
    }

Testing

Let’s test adding a new item to the list: POST request

$ localhost:9000/hello/addItem

{
    "address": "Mumbai",
    "name": "Kamal"
}

In response, we should see the Created status code and the new item in the response body. Additionally, to verify that the item was added to the list, we can retrieve all items again:

$ localhost:9000/hello/addItem

[
    {
        "id": 1,
        "address": "Delhi",
        "name": "Mayank"
    },
    {
        "id": 2,
        "address": "Mathura",
        "name": "Yashika"
    },
    {
        "id": 3,
        "address": "Mumbai",
        "name": "Kamal"
    }
]

This time, the JSON array should contain three objects, including the newly added one.

We should note that it’s crucial to specify the Content-Type header. Otherwise, Play Framework reads the data as application/x-www-form-urlencoded and fails to convert it into a JSON object.

Conclusion

In this article, we implemented a REST API in the Play Framework using Scala.

First, we initialized the project and defined our first route and controller class. Then we defined DTO objects and converted them in and out of JSON format.

We have also looked at how to use curl to verify that our code works correctly.

knoldus

Written by 

Meenakshi Goyal is a Software Consultant and started her career in an environment and organization where her skills are challenged each day, resulting in ample learning and growth opportunities. Proficient in Scala, Akka, Akka HTTP , JAVA. Passionate about implementing and launching new projects. Ability to translate business requirements into technical solutions. Her hobbies are traveling and dancing.