Play is a framework used to write web applications. As shown in the following diagram, a web application is based on a client-server architecture and uses the HTTP protocol for communication:

Web-oriented architecture
Users have the role of clients and make HTTP requests to servers to interact with the application. The servers process their requests and send them a response. Along the way, the web application might need to make use of various databases or perhaps other web services. This entire process is depicted in the following diagram:

The Play framework's overall architecture
This book will show you how Play can help you to write such web applications. The preceding diagram shows a first big picture of the framework's overall architecture. We will refine this picture as we read through this book, but for now it is a good start. The diagram shows a web client and a Play application. This one is made of a business layer (on the right), which provides the services and resources specific to the application. These features are exposed to the HTTP world by actions, which themselves can be logically grouped within controllers. Gray boxes represent the parts of code written by the developer (you!), while the white box (the router) represents a component already provided by Play.
HTTP requests performed by the client (1) are processed by the router that calls the corresponding action (2) according to URL patterns you configured. Actions fill the gap between the HTTP world and the domain of your application. Each action maps a service or resource of the business layer (3) to an HTTP endpoint.
All the code examples in this book will be based on a hypothetical shopping application allowing users to manage and sell their items. The service layer of this application is defined by the following Shop
trait:
case class Item(id: Long, name: String, price: Double) trait Shop { def list(): Seq[Item] def create(name: String, price: Double): Option[Item] def get(id: Long): Option[Item] def update(id: Long, name: String, price: Double): Option[Item] def delete(id: Long): Boolean }
In Java, the service layer is defined by the following Shop
interface:
public class Item { public final Long id; public final String name; public final Double price; public Item(Long id, String name, Double price) { this.id = id; this.name = name; this.price = price; } } interface Shop { List<Item> list(); Item create(String name, Double price); Item get(Long id); Item update(Long id, String name, Double price); Boolean delete(Long id); }
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
The Item
type simply says that an item has a name, a price, and an ID. The Shop
type defines the typical Create, Read, Update, and Delete (CRUD) operations we want to do. Connecting with the figure that shows the architecture of Play applications, these types represent the business layer of your web service and their definition should live in a models
package in the app/
source directory. The remainder of this chapter explains how to write a controller exposing this business layer via HTTP endpoints using JSON to represent the data.
As an example, here is a possible minimalist Scala implementation of the Shop
trait:
package models import scala.collection.concurrent.TrieMap import java.util.concurrent.atomic.AtomicLong object Shop extends Shop { private val items = TrieMap.empty[Long, Item] private val seq = new AtomicLong def list(): Seq[Item] = items.values.to[Seq] def create(name: String, price: Double): Option[Item] = { val id = seq.incrementAndGet() val item = Item(id, name, price) items.put(id, item) Some(item) } def get(id: Long): Option[Item] = items.get(id) def update(id: Long, name: String, price: Double): Option[Item] = { val item = Item(id, name, price) items.replace(id, item) Some(item) } def delete(id: Long): Boolean = items.remove(id).isDefined }
This implementation stores the data in memory, so it loses everything each time the application restarts! Nevertheless, it is a sufficient business layer basis, letting us focus on the web layer. The implementation uses a concurrent collection to solve concurrency issues. Indeed, as I will explain later, the code called by the controllers must be thread safe.
For Java developers, here is a minimalist implementation of the Shop
interface:
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicLong; new Shop() { SortedMap<Long, Item> items = new ConcurrentSkipListMap<>(); AtomicLong seq = new AtomicLong(); @Override public Collection<Item> list() { return new ArrayList<>(items.values()); } @Override public Item create(String name, Double price) { Long id = seq.incrementAndGet(); Item item = new Item(id, name, price); items.put(id, item); return item; } @Override public Item get(Long id) { return items.get(id); } @Override public synchronized Item update(Long id, String name, Double price) { Item item = items.get(id); if (item != null) { Item updated = new Item(id, name, price); items.put(id, updated); return updated; } else return null; } @Override public Boolean delete(Long id) { return items.remove(id) != null; } };
As previously mentioned, the code called by controllers must be thread safe, hence the use of Java concurrent collections.
While it is totally possible to start a Play project from nothing, you might find it more convenient to start from an empty application skeleton and set up the build system to depend on Play so that you can directly focus on the code of your application.
Typesafe Activator (https://typesafe.com/activator) can be used to generate such an empty application skeleton. This tool lists several application templates designed to be used as the project's starting point. Actually, Activator does a bit more than this: it can also compile and run your project and even provide a minimalist development environment with a code editor right in your web browser!
Let's start with a basic Scala or Java application. Download and install Activator by referring to its documentation. Though some templates already support advanced features out of the box, we will begin with a completely empty application and will progressively enhance it with new features (for example, persistence or client-side technologies).
In a *nix terminal, create a new application skeleton by running the following activator command:
$ activator new
You will be asked which application template to use; choose just-play-scala
(or just-play-java
to create a Java application). Give the application the name shop
.
A new directory, shop/
, has been created that contains the application's code. Go to this directory and execute the activator run
command:
$ cd shop $ activator run
Activator starts a development HTTP server listening (by default) on port 9000
. In your browser, go to http://localhost:9000
to test it; the HTTP server compiles your application, starts it, and processes your HTTP request. If everything works fine, your browser should show a page titled Just Play Scala (or Just Play Java).
You can stop the running application with Ctrl + D.
You can also start Activator without passing it a command name:
$ activator
In such a case, you enter in the project sbt shell, where you can manage the life cycle of your project as in any sbt project. For instance, you can try these commands: clean
, compile
, run
, and console
. The last one starts a REPL where you can evaluate expressions using your application's code.
Note
sbt is a build tool for Scala projects. Check out http://www.scala-sbt.org for more details.
To explain why you get this result in your browser when you ran the application, let's have a look at the files created by the activator new
command. Under the project root directory (the shop/
directory), the build.sbt
file describes your project to the build system.
It currently contains a few lines, which are as follows:
name := """shop""" version := "1.0-SNAPSHOT" lazy val root = project.in(file(".")).enablePlugins(PlayScala)
The first two lines set the project name and version, and the last line imports the default settings of Play projects (in the case of a Java project, this line contains PlayJava
instead of PlayScala
). These default settings are defined by the Play sbt plugin imported from the project/plugins.sbt
file. As the development of a Play application involves several file generation tasks (templates, routes, assets and so on), the sbt plugin helps you to manage them and brings you a highly productive development environment by automatically recompiling your sources when they have changed and you hit the reload button of your web browser.
Though based on sbt, Play projects do not follow the standard sbt projects layout: source files are under the app/
directory, test files under the test/
directory, and resource files (for example, configuration files) are under the conf/
directory. For instance, the definitions of the Shop
and Item
types should go into the app/
directory, under a models
package.
After running your Play application, try changing the source code of the application (under the app/
directory) and hit the reload button in your browser. The development HTTP server automatically recompiles and restarts your application. If your modification does not compile, you will see an error page in your browser that shows the line causing the error.
Let's have a deeper look at the files of this minimal Play application.
The app/
directory, as mentioned before, contains the application's source code. For now, it contains a controllers
package with just one controller named Application
. It also contains a views/
directory that contains HTML templates. We will see how to use them in Chapter 3, Turning a Web Service into a Web Application.
The conf/
directory contains two files. The conf/application.conf
file contains the application configuration information as key-value pairs. It uses the Human Optimized Configuration Object Notation syntax (HOCON; it is a JSON superset, check out https://github.com/typesafehub/config/blob/master/HOCON.md for more information). You can define as many configuration points as you want in this file, but several keys are already defined by the framework to set properties, such as the language supported by the application, the URL to use to connect to a database, or to tweak the thread pools used by the application.
The conf/routes
file defines the mapping between the HTTP endpoints of the application (URLs) and their corresponding actions. The syntax of this file is explained in the next section.
The routing component is the first piece of the framework that we will look at:

URL routing
The preceding diagram depicts its process. It takes an HTTP request and calls the corresponding entry point of the application. The mapping between requests and entry points is defined by routes in the conf/routes
file. The routes
file provided by the application template is as follows:
# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ # Home page GET / controllers.Application.index # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.versioned(path="/public", file)
Apart from comments (starting with #
), each line of the routes file defines a route associating an HTTP verb and a URL pattern to a controller action call.
For instance, the first route associates the /
URL to the controllers.Application.index
action. This one processes requests by always returning an HTTP response with a 200 status code (OK
) and an HTML body that contains the result of the rendering of the views.html.index
template.
The content of the routes file is compiled by the sbt plugin into a Scala object named Router
and contains the dispatching logic (that is, which action to call according to the incoming request verb and URL). If you are curious, the generated code is written in the target/scala-2.10/src_managed/main/routes_routing.scala
file. The router tries each route, one after the other, in their order of declaration. If the verb and URL match the pattern, the corresponding action is called.
Your goal is to expose your Shop
business layer as a web service, so let's add the following lines to the routes
file:
GET /items controllers.Items.list POST /items controllers.Items.create GET /items/:id controllers.Items.details(id: Long) PUT /items/:id controllers.Items.update(id: Long) DELETE /items/:id controllers.Items.delete(id: Long)
The first route will return a list of items for sale in the shop, the second one will create a new item, the third one will show detailed information about an item, the fourth one will update the information of an item, and finally, the last one will delete an item. Note that we follow the REST conventions (http://en.wikipedia.org/wiki/REST) for the URL shapes and HTTP verbs.
In the controllers
package of your code, add the following Items
controller that matches the added routes:
package controllers import play.api.mvc.{Controller, Action} object Items extends Controller { val shop = models.Shop // Refer to your Shop implementation val list = Action { NotImplemented } val create = Action { NotImplemented } def details(id: Long) = Action { NotImplemented } def update(id: Long) = Action { NotImplemented } def delete(id: Long) = Action { NotImplemented } }
The equivalent Java code is as follows:
package controllers; import play.mvc.Controller; import play.mvc.Result; public class Items extends Controller { static final Shop shop = Shop.Shop; // Refer to your Shop implementation public static Result list() { return status(NOT_IMPLEMENTED); } public static Result create() { return status(NOT_IMPLEMENTED); } public static Result details(Long id) { return status(NOT_IMPLEMENTED); } public static Result update(Long id) { return status(NOT_IMPLEMENTED); } public static Result delete(Long id) { return status(NOT_IMPLEMENTED); } }
Each route is mapped by a controller member of type Action
(or in Java, a public static method that returns a Result
). For now, actions are not implemented (they all return NotImplemented
) but you will progressively connect them to your Shop
service so that, for instance, the Items.list
action exposes the shop list
method.
In our example, in the first route, the URL pattern associated with the controllers.Items.details
action is /items/:id
, which means that any URL starting with /items/
and then containing anything but another /
will match. Furthermore, the content that is after the leading /
is bound to the id
identifier and is called a path parameter. The /items/42
path matches this pattern, but the /items/
, /items/42/0
, or even /items/42/
paths don't.
When a route contains a dynamic part such as a path parameter, the routing logic extracts the corresponding data from the URL and passes it to the action call.
You can also force a path parameter to match a given regular expression by using the following syntax:
GET /items/$id<\d+> controllers.Items.details(id: Long)
Here, we check whether the id
path parameter matches the regular expression \d+
(at least one digit). In this case, the /items/foo
URL will not match the route pattern and Play will return a 404 (Not Found) error for such a URL.
A route can contain several path parameters and each one is bound to only one path segment. Alternatively, you can define a path parameter spanning several path segments using the following syntax:
GET /assets/*file controllers.Assets.at(path = "/public", file)
In this case, the file
identifier captures everything after the /assets/
path segment. For instance, for an incoming request with the /assets/images/favicon.png
URL, file
is bound to images/favicon.png
. Obviously, a route can contain at most one path parameter that spans several path segments.
By default, request parameters are coerced to String
values, but the type annotation id: Long
(for example, in the details
route) asks Play to coerce the id
parameter to type Long
. The routing process extracts the content corresponding to a parameter from the URL and tries to coerce it to its target type in the corresponding action (Long
in our example) before calling it.
Note that /items/foo
also matches the route URL pattern, but then the type coercion process fails. So, in such a case, the framework returns an HTTP response with a 400 (Bad Request) error status code.
This type coercion logic is extensible. See the API documentation of the QueryStringBindable
and PathBindable
classes for more information on how to support your own data types.
The parameter values of the called actions can be bound from the request URL, or alternatively, can be fixed in the routes file by using the following syntax:
GET / controllers.Pages.show(page = "index")
GET /:page controllers.Pages.show(page)
Here, in the first route, the page
action parameter is set to "index"
and it is bound to the URL path in the second route.
In addition to path parameters, you can also define query string parameters: parameters extracted from the URL query string.
To define a query string parameter, simply use it in the action call part of the route without defining it in the URL pattern:
GET /items controllers.Items.details(id: Long)
The preceding route matches URLs with the /items
path and have a query string parameter id
. For instance, /items
and /items?foo=bar
don't match but /items?id=42
matches. Note that the URL must contain at least all the parameters corresponding to the route definition (here, there is only one parameter, which is id
), but they can also have additional parameters; /items?foo=bar&id=42
also matches the previous route.
Finally, you can define default values for query string parameters. If the parameter is not present in the query string, then it takes its default value. For instance, you can leverage this feature to support pagination in your list
action. It can take an optional page
parameter defaulting to the value 1
. The syntax for default values is illustrated in the following code:
GET /items controllers.Items.list(page: Int ?= 1)
The preceding route matches the /items?page=42
URL and binds the page
query string parameter to the value 42
, but also matches the /items
URL and, in this case, binds the page
query string parameter to its default value 1
.
Change the corresponding action definition in your code so that it takes a page
parameter, as follows:
def list(page: Int) = Action { NotImplemented }
The equivalent Java code is as follows:
public static Result list(Integer page) {
return status(NOT_IMPLEMENTED);
}
If you try to perform requests to your newly added routes from your browser, you will see a blank page. It might be interesting to try them from another HTTP client to see the full HTTP exchange between your client and your server. You can use, for example, cURL (http://curl.haxx.se/):
$ curl -v http://localhost:9000/items > GET /items HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:9000 > Accept: */* > < HTTP/1.1 501 Not Implemented < Content-Length: 0 <
The preceding command makes an HTTP GET
request on the /items
path and gets an HTTP response with the status code 501 (Not Implemented). Try requesting other paths such as /items/42, /itemss/foo
, or /foo
and compare the response status codes you get.
Routes are the way to expose your business logic endpoints as HTTP endpoints; your Shop
service can now be used from the HTTP world!
Actions process HTTP requests and return HTTP responses. So far, you have seen how HTTP requests were routed by the framework to call your application's code. Now, let's see how you can reply with HTTP responses.
An HTTP response has a status code and some optional headers and can be followed by a body. So, to build a response, you have to at least supply a status code.
For instance, your current action definitions return HTTP responses with status code 501 (Not Implemented). Try changing it to Ok
(or ok()
in Java) and reload the page in your browser (or perform a request with cURL), you should get a response with status code 200 (OK)
. Play provides helpers to build responses with common status codes. Examples of other predefined statuses are NotFound
(notFound()
in Java), BadRequest
(badRequest()
in Java) or InternalServerError
(internalServerError()
in Java).
More generally, you can build an HTTP response with any status code by using the Status
function (or status
in Java). For instance, Status(200)
(or status(200)
in Java) builds an empty response with status 200 and the Content-Length
header set to 0
.
In addition to a status code, HTTP responses can contain data in a body. For instance, the Application.index
action that was provided with the application skeleton returns an HTML document. Alternatively, we can make it return a text document:
val index = Action {
Ok("Just Play Scala")
}
The equivalent Java code is as follows:
public static Result index() {
return ok("Just Play Java");
}
In Scala, you can supply a body to your response by calling the apply
method of a status; Ok("Just Play Scala")
builds an HTTP response with status code 200 and a body that contains "Just Play Scala"
. Similarly, in Java, you can just pass the response body as a parameter to the status function.
Clients consuming a web service might want (or need) to know the content type of response body data. HTTP responses have a header for this, which is Content-Type
. As the value of this header is tied to the type of values you send, Play automatically infers the former from the latter, freeing you from writing redundant code.
In practice, in Scala, writing Ok("foo")
builds an HTTP response with a Content-Type
header set to text/plain
because "foo"
has type String
. Play infers the right content type by using the type of the value you pass as response body. The type signature of the apply
method of the Result
type is the following:
def apply[A](a: A)(implicit w: play.api.mvc.Writeable[A]): Result
This means that you can supply a response of type A
only if you provide an implicit value of type play.api.mvc.Writeable[A]
. The Writeable
typeclass actually tells Play which content type to use (and how to serialize it to the HTTP response body). For convenience, Play provides implicit Writeable
values for common content types such as JSON, HTML, XML, and plain text. However, if you want to send data of a type that is not currently supported by Play, you have to define the corresponding Writeable
instance.
Note
Typeclasses are a feature of the Haskell programming language to achieve ad hoc polymorphism (http://www.haskell.org/tutorial/classes.html). It can be encoded in Scala using parameterized types and implicit parameters.
In Java, the result method helpers, such as ok()
and notFound()
, are overloaded to support common data types (for example, String
, byte[]
); ok("foo")
builds an HTTP response with a Content-Type
header set to text/plain
. Data types that are not directly supported must implement the play.mvc.Content
interface to be correctly handled by Play. This interface specifies which content type to use and how to serialize the data to the HTTP response body.
Now that you know how to build an HTTP response containing a body, your last step to bootstrap your web service consists of returning your business data as a JSON document in the body of your HTTP responses.
Play comes with a rich library for JSON manipulation. Let's start by returning a JSON value in the details
action:
import play.api.libs.json.Json def details(id: Long) = Action { shop.get(id) match { case Some(item) => Ok(Json.obj( "id" -> item.id, "name" -> item.name, "price" -> item.price )) case None => NotFound } }
This code tries to retrieve the item in the shop and if found, returns it as a JSON object. The Json.obj
function builds a JSON object from a list of name-value pairs. If there is no item with the ID passed as a parameter, the action returns a NotFound
response. JSON objects have type JsValue
, and Play has a built-in Writeable[JsValue]
instance that sets the content type of a JSON response body to application/json
.
The equivalent Java code is as follows:
import play.libs.json.Json; public static Result details(Long id) { Item item = shop.get(id); if (item != null) { return ok(Json.toJson(item)); } else { return notFound(); } }
In Java, Play uses the Jackson library (http://jackson.codehaus.org/) to automatically serialize the Item
value so that you don't need to explicitly tell how to transform an Item
into a JSON object. The Jackson object mapper that performs this task can handle some simple data structures like the Item
class, but for more complex data structures (for example, involving cycles or bidirectional associations), you might have to supply your own serialization process by annotating your types with Jackson annotations.
The Scala API does not follow this approach because the Scala language gives convenient mechanisms that allow you to tell how to serialize data without relying on reflection and with minimal boilerplate.
If you call this action from your HTTP client, you will get a response like the following (assuming your shop has an item with the ID 42):
$ curl http://localhost:9000/items/42 {"id":42,"price":4.2,"name":"Play Framework Essentials"}
Similar to the implementation of the details
action, here is how you can implement the list
action and return the list of the items in the shop as a JSON array. The Java version of the code is as follows:
public static Result list() {
return ok(Json.toJson(shop.list()));
}
Again, the Json.toJson
call delegates the JSON serialization of the list of items to Jackson.
The Scala version of the code is as follows:
val list = Action {
Ok(Json.arr(shop.list.map(item => Json.obj(
"id" -> item.id,
"name" -> item.name,
"price" -> item.price
)): _*))
}
We use the Json.arr
method to create a JSON array and pass it a collection of JSON objects as a parameter.
You might have noticed that the code defining these JSON objects from the items duplicates the code already written in the details
action. Instead, you should isolate the logic corresponding to the serialization of an item into a JSON object as a function and reuse it. Actually, you can do even better; the Play JSON library defines a play.api.libs.json.Writes[A]
typeclass that captures the serialization logic for the type A
, so you can just write an implicit value of type Writes[Item]
and Play will use it when needed. This typeclass has just one method, which is writes(item: Item): JsValue
that defines how to transform an Item
into a JSON value. The JsValue
type is an algebraic data type representing JSON values. For now, you have seen how to define JSON objects (represented by the JsObject
type in Play) and arrays (JsArray
), using Json.obj
and Json.arr
, respectively, but there are other types of JsValue
such as numbers (JsNumber
), strings (JsString
), booleans (JsBoolean
), and null (JsNull
).
Your first reusable JSON serializer for items can be defined and used as follows:
import play.api.libs.json.Writes implicit val writesItem = Writes[Item] { case Item(id, name, price) => Json.obj( "id" -> id, "name" -> name, "price" -> price ) } val list = Action { Ok(Json.toJson(shop.list)) } def details(id: Long) = Action { shop.get(id) match { case Some(item) => Ok(Json.toJson(item)) case None => NotFound } }
The implicit value, writesItem
, defines the serialization logic for Items
. Then, in the list
and details
actions, we use Json.toJson
to transform our items into JSON objects. This toJson
method has the following signature:
def toJson[A](a: A)(implicit Writes[A]): JsValue
This means that it can serialize any value of type A
if there is an implicit value of type Writes[A]
in the implicit scope. Fortunately, Play defines such JSON serializers for common types and can combine them by chaining implicits; this is why Play is able to serialize an Item
as well as a List[Item]
.
Though the code of your writesItem
is quite concise, it follows a repetitive pattern. Each field of the Item
class is serialized to a JSON field of the same name. Hopefully, Play provides a macro that generates JSON serializers following this pattern, so the previous serializer can be synthesized by just writing the following:
implicit val writesItem = Json.writes[Item]
You might be wondering why automatic generation of JSON serializers is not the default behavior. There are two reasons for this. First, the automatic generation mechanism cannot handle all data types (for example, cyclic data types). Secondly, sometimes you don't want to use the same names for your JSON object fields and your Scala object fields.
Now your web service is able to send JSON data representing the application data. However, clients cannot yet create new data by sending JSON requests; the create
action is still not implemented. This action should read the JSON data of requests to extract the information required to create a new item, effectively create the item, and return a response telling the client whether its operation succeeded.
The first step consists in defining which information is required to create a new item:
case class CreateItem(name: String, price: Double)
The equivalent Java code is as follows:
public class CreateItem { public String name; public Double price; }
The CreateItem
data type just glues together the information needed to create a new item: a name and price. The Java version uses public fields so that it can automatically be handled by the Jackson object mapper.
The CreateItem
data type is easy to work with in your server-side code, but it means nothing for HTTP clients that only send JSON blobs. So you also have to define a JSON structure corresponding to the CreateItem
data type. A simple solution consists of representing a CreateItem
instance with a JSON object by mapping each member of the CreateItem
type with a member of the JSON object. That is, a JSON object with a member "name"
that contains a string value and a member "price"
that contains a number value.
The next step consists of defining how to convert a JSON object consistent with this structure into a CreateItem
value.
In Scala, similar to the Writes[A]
typeclass that defines how to serialize an A
value into a JSON object, there is a Reads[A]
typeclass that defines how to get an A
value from a JSON object. This typeclass has one abstract method:
def reads(json: JsValue): JsResult[A]
The JsResult[A]
type represents either a successful conversion, JsSuccess(a)
, or a unsuccessful conversion, JsError(errors)
, which contains a list of errors such as missing fields in the JSON source object. So the Reads[A]
typeclass tells how to try to convert a JSON value to an A
value.
Play provides Reads[A]
values for common types such as String
, Int
, or Double
. You can then combine them to define Reads[A]
values for more complex types. For instance, you can define a Reads[CreateItem]
value that tells how to try to convert a JSON value to a CreateItem
value, as follows:
import play.api.libs.json.{__, Reads} import play.api.libs.functional.syntax._ implicit val readsCreateItem: Reads[CreateItem] = ( ((__ \ "name").read[String]) and ((__ \ "price").read[Double]) )(CreateItem.apply _)
This code combines the Reads[String]
and Reads[Double]
values using the and
combinator. The (__ \ "name")
expression is a JSON path referring to a member "name"
so that the (__ \ "name").read[String]
expression reads the "name"
member as String
and the (__ \ "price").read[Double]
expression reads the "price"
member as Double
. Finally, these values are passed to the apply
method of the CreateItem
data type to make a CreateItem
instance. Before showing how to use this JSON reader to effectively transform the content of a JSON HTTP request, let's give more details on the process of transforming JSON blobs to values. As our readsCreateItem
type is built by combining two subreaders using and
, it tries to apply all of them. If all succeed, the obtained values are passed to the CreateItem.apply
function to build a CreateItem
instance and the reader returns a JsSuccess[CreateItem]
value. If one of the subreaders fails, the reader returns a JsError
value.
Tip
The and
combinator is not a method of Reads[A]
. It is available thanks to an implicit conversion imported by play.api.libs.functional.syntax._
. This import brings several other combinators, such as or
, which succeeds if one of the two subreaders succeeds. These combinators are not specific to the JSON API, and this is why they are defined in a separate package.
In our case, both sub-readers look up a member in a JSON object, according to a path defined by the \
operator. Note that we can define longer paths by chaining the \
operator. Consider, for instance, the following expression that defines a path locating a member "latitude"
nested in a "position"
member of a JSON object:
__ \ "position" \ "latitude"
Just like the Writes
definition, the readsCreateItem
definition is quite mechanical. We try to get each field of the CreateItem
case class from a field of the same name in the JSON object. Just like the Write
s definition, there is a macro automating the work for Scala case classes so that the preceding Reads
definition is completely equivalent to the following:
implicit val readsCreateItem = Json.reads[CreateItem]
In Java, the Jackson mapper is used to convert JSON data to POJOs using reflection, so you don't need to provide similar definitions.
Finally, the last step consists of making the create
action interpret request content as JSON data and making a CreateItem
value from this data:
val create = Action(parse.json) { implicit request => request.body.validate[CreateItem] match { case JsSuccess(createItem, _) => shop.create(createItem.name, createItem.price) match { case Some(item) => Ok(Json.toJson(item)) case None => InternalServerError } case JsError(errors) => BadRequest } }
The equivalent Java code is as follows:
import play.mvc.BodyParser; @BodyParser.Of(BodyParser.Json.class) public static Result create() { JsonNode json = request().body().asJson(); CreateItem createItem; try { createItem = Json.fromJson(json, CreateItem.class); } catch(RuntimeException e) { return badRequest(); } Item item = shop.create(createItem.name, createItem.price); if (item != null) { return ok(Json.toJson(item)); } else { return internalServerError(); } }
There are three important points to note in the preceding code. First, we tell the create
action to interpret the request body as JSON data by supplying the parse.json
value to the Action
builder (or in Java, by annotating the method with @BodyParser.Of(BodyParser.Json.class)
). In Play, the component responsible for interpreting the body of an HTTP request is named
body parser. By default, actions use a tolerant body parser that will be able to parse the request content as JSON, XML, or URL-encoded form or multipart form data, but you can force the use of a specific body parser by supplying it as the first parameter of your action builder (or by using the @BodyParser.Of
annotation in Java). The advantage is that within the body of your request, you are guaranteed that the request body (available as the body
field on the request
value) has the right type. If the request body cannot be parsed by the body parser, Play returns an error response with the status 400 (Bad Request).
Secondly, in the Scala version, the second parameter passed to the Action
builder is not a block of the Result
type, as with previous actions, but a function of type Request[A] => Result
. Actually, actions are essentially functions from HTTP requests (represented by the Request[A]
type) to HTTP responses (Result
). The previous way to use the Action
builder (by just passing it a block of type Result
) was just a convenient shorthand for writing an action ignoring its request parameter. The type parameter, A
, in Request[A]
represents the type of the request body. In our case, because we use the parse.json
body parser, we actually have a request of type Request[JsValue
]; the request.body
expression has type JsValue
. The default body parser produces requests of the type Request[AnyContent]
, whose body can contain JSON or XML content as described previously. In Java, Play sets up a context before calling your action code (just after the routing process) so that within a controller, you can always refer to the current HTTP request by using the request()
method.
Thirdly, we make the CreateItem
value from this request body by calling request.body.validate[CreateItem]
(or Json.fromJson(json, CreateItem.class)
in Java). The Scala version returns a JsResult
value; this type can either be JsSuccess
if the CreateItem
object can be created from the JSON data (using the Reads[CreateItem]
value available in the implicit scope), or JsError
if the process failed. In Java, the result is simply null
in the case of an error.
Note
In Scala, there is a body parser that not only parses the request body a JSON blob but also validates it according to a reader definition and returns a 400 (Bad Request) response in the case of a failure so that the previous Scala code is equivalent to the following shorter version:
val create = Action(parse.json[CreateItem]) { implicit request =>
shop.create(request.body.name, request.body.price) match {
case Some(item) => Ok(Json.toJson(item))
case None => InternalServerError
}
}
At this point, your clients can consult the items of the shop and create new items. What happens if one tries to create an item with an empty name or a negative price? Your application should not accept such requests. More precisely, it should reject them with the 400 (Bad Request) error.
To achieve this, you have to perform validation on data submitted by clients. You should implement this validation process in the business layer, but implementing it in the controller layer gives you the advantages of detecting errors earlier and error messages can directly refer to the structure of the submitted JSON data so that they can be more precise for clients.
In Java, the Jackson API provides nothing to check this kind of validation. The recommended way is to validate data after it has been transformed into a POJO. This process is described in Chapter 3, Turning a Web Service into a Web Application. In Scala, adding a validation step in our CreateItem
reader requires a few modifications. Indeed, the Reads[A]
data type already gives us the opportunity to report errors when the type of coercion process fails. We can also leverage this opportunity to report business validation errors. Incidentally, the Play JSON API provides combinators for common errors (such as minimum and maximum values and length verification) so that we can forbid negative prices and empty item names, as follows:
implicit val readsCreateItem = ( (__ \ "name").read(Reads.minLength[String](1)) and (__ \ "price").read(Reads.min[Double](0)) )(CreateItem.apply _)
The preceding code rejects JSON objects that have an empty name or negative price. You can try it in the REPL:
scala> Json.obj("name" -> "", "price" -> -42).validate[CreateItem] res1: play.api.libs.json.JsResult[controllers.CreateItem] = JsError(List( (/price,List(ValidationError(error.min,WrappedArray(0.0)))), (/name,List(ValidationError(error.minLength,WrappedArray(1))))))
The returned object describes two errors: the first error is related to the price
field; it has the "error.min"
key and additional data, 0.0
. The second error is related to the name
field; it has the "error.minLength"
key and an additional data, 1
.
The Reads.minLength
and Reads.min
validators are predefined validators but you can define your own validators using the filter
method of the Reads
object.
Consider the following data type representing an item with an optional description:
case class Item(name: String, price: Double, description: Option[String])
In Scala, optional values are represented with the Option[A]
type. In JSON, though you can perfectly represent them in a similar way, optional fields are often modeled using null
to represent the absence of a value:
{ "name": "Foo", "price": 42, "description": null }
Alternatively, the absence of a value can also be represented by simply omitting the field itself:
{ "name": "Foo", "price": 42 }
If you choose to represent the absence of value using a field containing null
, the corresponding Reads
definition is the following:
(__ \ "name").read[String] and
(__ \ "price).read[Double] and
(__ \ "description").read(Reads.optionWithNull[String])
The optionWithNull
reads combinator turns a Reads[A]
into a Reads[Option[A]]
by successfully mapping null
to None
. Note that if the description
field is not present in the read JSON object, the validation fails. If you want to support field omission to represent the absence of value, then you have to use readNullable
instead of read
:
(__ \ "name").read[String] and
(__ \ "price).read[Double] and
(__ \ "description").readNullable[String]
This is because read
requires the field to be present before invoking the corresponding validation. readNullable
relaxes this constraint.
Now, consider the following recursive data type representing categories of items. Categories can have subcategories:
case class Category(name: String, subcategories: Seq[Category])
A naive Reads[Category]
definition can be the following:
implicit val readsCategory: Reads[Category] = (
(__ \ "name).read[String] and
(__ \ "subcategories").read(Reads.seq[Category])
)(Category.apply _)
The seq
combinator turns Reads[A]
into Reads[Seq[A]]
. The preceding code compiles fine; however, at run-time it will fail when reading a JSON object that contains subcategories:
scala> Json.obj( "name" -> "foo", "subcategories" -> Json.arr( Json.obj( "name" -> "bar", "subcategories" -> Json.arr() ) ) ).validate[Category] java.lang.NullPointerException at play.api.libs.json.Json$.fromJson(Json.scala:115) …
What happened? Well, the seq[Category]
combinatory uses the Reads[Category]
instance before it has been fully defined, hence the null
value and NullPointerException
!
Turning implicit val readsCategory
into implicit lazy val readsCategory
to avoid the NullPointerException
will not solve the heart of the problem; Reads[Category]
will still be defined in terms of itself, leading to an infinite loop! Fortunately, this issue can be solved by using lazyRead
instead of read
:
implicit val readsCategory: Reads[Category] = (
(__ \ "name).read[String] and
(__ \ "subcategories").lazyRead(Reads.seq[Category])
)(Category.apply _)
The lazyRead
combinator is exactly the same as read
, but uses a byname parameter that is not evaluated until needed, thus preventing the infinite recursion in the case of recursive Reads
.
This chapter gave you an idea of the Play framework, but it contained enough material to show you how to turn a basic application into a web service. By following the principles explained in this chapter, you should be able to implement the remaining Items.update
and Items.delete
actions.
You saw how to generate an empty Play application skeleton using activator. Then, you saw how to define the mapping between HTTP endpoints and your application entry points and the different ways you can bind your controller action parameters from the request URL. You saw how to build HTTP responses and the mechanism used by Play to infer the right response content type. Finally, you saw how to serve JSON responses and how to read and validate JSON requests.
In the next chapter, you will replace the in-memory storage system with a persistent storage system, and you will see how your Play application can be integrated with existing persistence technologies like JDBC.