X

This site uses cookies and by using the site you are consenting to this. We utilize cookies to optimize our brand’s web presence and website experience. To learn more about cookies, click here to read our privacy statement.

Building a Simple REST API with Scala & Play! (Part 2)

In this 3 part series, we’ll cover creating a basic Play! REST API on top of Reactive Mongo.  Full source code for this tutorial is available at https://github.com/jrodenbostel/getting-started-play-scala.

Welcome back!

If you’re coming in fresh, and need some instructions on getting the appropriate tools installed and creating a shell of an environment, please refer to part 1.  In part 2, we’ll cover adding our first API functions as asynchronous actions in a Play! controller, as well as define our first data access functions.
Because we started with a seed project, we got some bonus cruft in our application in the form of a views package.  While Scala templates are a fine way to create views for your application, for this tutorial, we’ll be building a RESTful API that responds with JSON strings. Start by deleting the ‘views’ folder at ‘app/views’ such that:
Screen Shot 2016-01-25 at 10.03.14 PM
Note that this will break the default controller that came as part of the project seed.  To remedy this, update the default controller action response to simply render text, by replacing:

[code language=”scala”]
Ok(views.html.index("Your new application is ready."))
[/code]

with:

[code language=”scala”]
Ok("Your new application is ready.")
[/code]

Creating the controller

Next, we’ll create our controller.  Create a new file in the ‘app/controllers’ folder named ‘Widgets.scala’.  This will house the RESTful actions associated with Widgets.  We’ll also add default methods to this controller for the RESTful actions that we’ll implement later.

[code language=”scala”]
package controllers

import play.api.mvc._

class Widgets extends Controller {

def index = TODO

def create = TODO

def read(id: String) = TODO

def update(id: String) = TODO

def delete(id: String) = TODO
}
[/code]

Play! comes with a default “TODO” page for controller actions that have not yet been implemented.  It’s a nice way to keep your app functioning and reloading while you’re building out functionality incrementally.  Before we can see that default “TODO” page, we must first add routes to the application configuration that reference our new controller and it’s not-yet-implemented actions.
Update /conf/routes with paths to the Widgets controller such that the following is true:

[code]
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET / controllers.Application.index
GET /cleanup controllers.Application.cleanup

#Widgets
GET /api/widgets controllers.Widgets.index
GET /api/widget/:id controllers.Widgets.read(id: String)
POST /api/widget controllers.Widgets.create
DELETE /api/widget/:id controllers.Widgets.delete(id: String)
PATCH /api/widget/:id controllers.Widgets.update(id: String)
[/code]

Now, we can visit one of these new paths to view the default “TODO” screen.
Screen Shot 2016-01-25 at 10.18.35 PM

Data access

Before we can build out our controller actions, we’ll add a data access layer to surface some data to our controller.  Let’s start by creating the trait, which will define the contract for our data access layer.  (Traits are similar to Interfaces in Java – more info that here http://docs.scala-lang.org/tutorials/tour/traits.html)
Create a new folder called ‘repos’ such that a directory at ‘app/repos’ exists.  In that directory, create a new Scala file named WidgetRepo.scala with the following content:
app/repos/WidgetRepo.scala:

[code language=”scala”]
package repos

import javax.inject.Inject

import play.api.libs.json.{JsObject, Json}
import play.modules.reactivemongo.ReactiveMongoApi
import play.modules.reactivemongo.json._
import play.modules.reactivemongo.json.collection.JSONCollection
import reactivemongo.api.ReadPreference
import reactivemongo.api.commands.WriteResult
import reactivemongo.bson.{BSONDocument, BSONObjectID}

import scala.concurrent.{ExecutionContext, Future}

trait WidgetRepo {
def find()(implicit ec: ExecutionContext): Future[List[JsObject]]

def select(selector: BSONDocument)(implicit ec: ExecutionContext): Future[Option[JsObject]]

def update(selector: BSONDocument, update: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult]

def remove(document: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult]

def save(document: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult]
}
[/code]

There should be no surprises there – normal CRUD operations for data access and manipulation.  Things you may notice that are special here – the return types.  They’re all asynchronous Futures.  The read operations return JsObjects https://www.playframework.com/documentation/2.0/api/scala/play/api/libs/json/JsObject.html and the write operations return WriteResults http://reactivemongo.org/releases/0.11/documentation/tutorial/write-documents.html.
Let’s continue by adding the implementations for each of these methods in the form of a Scala class in the same source file:
app/repos/WidgetRepo.scala:

[code language=”scala”]
class WidgetRepoImpl @Inject() (reactiveMongoApi: ReactiveMongoApi) extends WidgetRepo {

def collection = reactiveMongoApi.db.collection[JSONCollection]("widgets");

override def find()(implicit ec: ExecutionContext): Future[List[JsObject]] = {
val genericQueryBuilder = collection.find(Json.obj());
val cursor = genericQueryBuilder.cursor[JsObject](ReadPreference.Primary);
cursor.collect[List]()
}

override def select(selector: BSONDocument)(implicit ec: ExecutionContext): Future[Option[JsObject]] = {
collection.find(selector).one[JsObject]
}

override def update(selector: BSONDocument, update: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult] = {
collection.update(selector, update)
}

override def remove(document: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult] = {
collection.remove(document)
}

override def save(document: BSONDocument)(implicit ec: ExecutionContext): Future[WriteResult] = {
collection.update(BSONDocument("_id" -> document.get("_id").getOrElse(BSONObjectID.generate)), document, upsert = true)
}

}
[/code]

Again, there shouldn’t be much that’s surprising here other than the normal, somewhat complex IMO, Scala syntax.  You can see the implicit ExecutionContext, which, for asynchronous code, basically let’s Scala decide where in the thread pool to execute the related function.  You may also notice the ReadPreference in the find() function.  This tells Mongo that our repo would like to read it’s results from the primary Mongo node.

Back to the controller

At this point, we can return to our controller to round out the implementation details there.  Let’s start simple.
Before we can start adding implementation details, we need to configure some dependency injection details.  We’ll inject the ReactiveMongoApi into our Controller, then use that to create our repository.  Update the signature of the Widgets controller and imports so the following is true:

[code language=”scala”]
package controllers

import javax.inject.Inject

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.json.Json
import play.api.mvc._
import play.modules.reactivemongo.{MongoController, ReactiveMongoApi, ReactiveMongoComponents}
import reactivemongo.api.commands.WriteResult
import reactivemongo.bson.{BSONObjectID, BSONDocument}
import repos.WidgetRepoImpl

class Widgets @Inject()(val reactiveMongoApi: ReactiveMongoApi) extends Controller
with MongoController with ReactiveMongoComponents {

def widgetRepo = new WidgetRepoImpl(reactiveMongoApi)
[/code]

In ‘app/controllers/Widgets.scala’, we have an unimplemented ‘index’ function.  This is meant to display a list of all widgets in the database, selected without parameters.  The implementation for this function is very straightforward. Update the index function such that:

[code language=”scala”]
def index = Action.async { implicit request =>
widgetRepo.find().map(widgets => Ok(Json.toJson(widgets)))
}
[/code]

In this implementation, since we’re expecting a single result, when we execute ‘map’ on the result, we’ll take the resulting object and render it directly to our JSON response.
The ‘read’ method, which is very similar to ‘index’, will take a single String parameter (the id of the document being searched for) and return a single result as JSON string. Update the read function such that:

[code language=”scala”]
def read(id: String) = Action.async { implicit request =>
widgetRepo.select(BSONDocument(Id -> BSONObjectID(id))).map(widget => Ok(Json.toJson(widget)))
}
[/code]

 The ‘delete’ method is similarly straightforward in that it takes a String id for the document to be deleted, and returns a HTTP status code 202 (Accepted), and no body.

[code language=”scala”]
def delete(id: String) = Action.async {
widgetRepo.remove(BSONDocument(Id -> BSONObjectID(id)))
.map(result => Accepted)
}
[/code]

The ‘create’ and ‘update’ methods introduce a small amount of complexity in that they require the request body to be parsed using Scala’s built in pattern matching functionality. Since we’ll have to match the field names in two places, we’ll create a companion object to hold our field names. Create a ‘WidgetFields’ companion object in the Widgets controller source file:

[code language=”scala”]
object WidgetFields {
val Id = "_id"
val Name ="name"
val Description = "description"
val Author = "author"
}
[/code]

In the body of the Widget controller, add a scoped import for the companion object:

[code language=”scala”]
import controllers.WidgetFields._
[/code]

For the ‘create’ method, we’ll apply a JSON Body Parser to an implicit request, parsing out the relevant content needed to build a BSONDocument that can be persisted via the Repo:

[code language=”scala”]
def create = Action.async(BodyParsers.parse.json) { implicit request =>
val name = (request.body Name).as[String]
val description = (request.body Description).as[String]
val author = (request.body Author).as[String]
recipeRepo.save(BSONDocument(
Name -> name,
Description -> description,
Author -> author
)).map(result => Created)
}
[/code]

Execution of this method returns the HTTP status code 201.  For the ‘update’ method, we’ll perform largely the same operation – applying the JSON Body Parser to an implicit request, however, this time we’ll call a different repo method – this time building a BSONDocument to select the relevant document with, then passing in the current field values:

[code language=”scala”]
def update(id: String) = Action.async(BodyParsers.parse.json) { implicit request =>
val name = (request.body Name).as[String]
val description = (request.body Description).as[String]
val author = (request.body Author).as[String]
widgetRepo.update(BSONDocument(Id -> BSONObjectID(id)),
BSONDocument("$set" -> BSONDocument(Name -> name, Description -> description, Author -> author)))
.map(result => Accepted)
}
[/code]

Testing!

Part 3 of this series will cover testing using the Spec2 library. In the mean time…We have a fully functioning REST API – but testing manually requires configuration and the execution of HTTP operations. Many web frameworks are packed with functionality that allows a developer to ‘bootstrap’ an application – adding seed data to a local environment for testing as an example. Recent changes in the Play! framework’s GlobalSettings have changed the way developers do things like seed test databases (https://www.playframework.com/documentation/2.4.x/GlobalSettings). While the dust settles, and while we wait for part 3, I created some helper functions in the Application controller that will create and remove some test data:

[code language=”scala”]
package controllers

import javax.inject.{Inject, Singleton}

import play.api.Logger
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.json.Json
import play.api.mvc.{Action, Controller}
import play.modules.reactivemongo.json.collection.JSONCollection
import play.modules.reactivemongo.{MongoController, ReactiveMongoApi, ReactiveMongoComponents}
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.api.commands.bson.BSONCountCommand.{ Count, CountResult }
import reactivemongo.api.commands.bson.BSONCountCommandImplicits._
import reactivemongo.bson.BSONDocument

import scala.concurrent.Future

@Singleton
class Application @Inject()(val reactiveMongoApi: ReactiveMongoApi) extends Controller
with MongoController with ReactiveMongoComponents {

def jsonCollection = reactiveMongoApi.db.collection[JSONCollection]("widgets");
def bsonCollection = reactiveMongoApi.db.collection[BSONCollection]("widgets");

def index = Action {
Logger.info("Application startup…")

val posts = List(
Json.obj(
"name" -> "Widget One",
"description" -> "My first widget",
"author" -> "Justin"
),
Json.obj(
"name" -> "Widget Two: The Return",
"description" -> "My second widget",
"author" -> "Justin"
))

val query = BSONDocument("name" -> BSONDocument("$exists" -> true))
val command = Count(query)
val result: Future[CountResult] = bsonCollection.runCommand(command)

result.map { res =>
val numberOfDocs: Int = res.value
if(numberOfDocs > 1) {
jsonCollection.bulkInsert(posts.toStream, ordered = true).foreach(i => Logger.info("Record added."))
}
}

Ok("Your database is ready.")
}

def cleanup = Action {
jsonCollection.drop().onComplete {
case _ => Logger.info("Database collection dropped")
}
Ok("Your database is clean.")
}
}

[/code]

… and routes …

[code language=”scala”]
# Home page
GET / controllers.Application.index
GET /cleanup controllers.Application.cleanup
[/code]