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.scala, index.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 Option. We 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.
