The Play framework is a web framework built on top of Akka. It has a proven track record in industry, and is thus a reliable choice for building scalable web applications.
Play is an opinionated web framework: it expects you to follow the MVC architecture, and it has a strong opinion about the tools you should be using. It comes bundled with its own JSON and XML parsers, with its own tools for accessing external APIs, and with recommendations for how to access databases.
Web applications are much more complex than the command line scripts we have been developing in this book, because there are many more components: the backend code, routing information, HTML templates, JavaScript files, images, and so on. The Play framework makes strong assumptions about the directory structure for your project. Building that structure from scratch is both mind-numbingly boring and easy to get wrong. Fortunately, we can use Typesafe activators to bootstrap the project (you can also download the code from the Git repository in https://github.com/pbugnion/s4ds but I encourage you to start the project from a basic activator structure and code along instead, using the finished version as an example).
Typesafe activator is a custom version of SBT that includes templates to get Scala programmers up and running quickly. To install activator, you can either download a JAR from https://www.typesafe.com/activator/download, or, on Mac OS, via homebrew:
$ brew install typesafe-activator
You can then launch the activator console from the terminal. If you downloaded activator:
$ ./path/to/activator/activator new
Or, if you installed via Homebrew:
$ activator new
This starts a new project in the current directory. It starts by asking what template you want to start with. Choose play-scala
. It then asks for a name for your application. I chose ghub-display
, but go ahead and be creative!
Let's explore the newly created project structure (I have only retained the most important files):
├── app │ ├── controllers │ │ └── Application.scala │ └── views │ ├── main.scala.html │ └── index.scala.html ├── build.sbt ├── conf │ ├── application.conf │ └── routes ├── project │ ├── build.properties │ └── plugins.sbt ├── public │ ├── images │ │ └── favicon.png │ ├── javascripts │ │ └── hello.js │ └── stylesheets │ └── main.css └── test ├── ApplicationSpec.scala └── IntegrationSpec.scala
Let's run the app:
$ ./activator [ghub-display] $ run
Head over to your browser and navigate to the URL 127.0.0.1:9000/
. The page may take a few seconds to load. Once it is loaded, you should see a default page that says Your application is ready.
Before we modify anything, let's walk through how this happens. When you ask your browser to take you to 127.0.0.1:9000/
, your browser sends an HTTP request to the server listening at that address (in this case, the Netty server bundled with Play). The request is a GET request for the route /
. The Play framework looks in conf/routes
to see if it has a route satisfying /
:
$ cat conf/routes # Home page GET / controllers.Application.index ...
We see that the conf/routes
file does contain the route /
for GET requests. The second part of that line, controllers.Application.index
, is the name of a Scala function to handle that route (more on that in a moment). Let's experiment. Change the route end-point to /hello
. Refresh your browser without changing the URL. This will trigger recompilation of the application. You should now see an error page:
The error page tells you that the app does not have an action for the route /
any more. If you navigate to 127.0.0.1:9000/hello
, you should see the landing page again.
Besides learning a little of how routing works, we have also learned two things about developing Play applications:
Let's change the route back to /
. There is a lot more to say on routing, but it can wait till we start building our application.
The conf/routes
file tells the Play framework to use the method controllers.Application.index
to handle requests to /
. Let's look at the Application.scala
file in app/controllers
, where the index
method is defined:
// app/controllers/Application.scala package controllers import play.api._ import play.api.mvc._ class Application extends Controller { def index = Action { Ok(views.html.index("Your new application is ready.")) } }
We see that controllers.Application.index
refers to the method index
in the class Application
. This method has return type Action
. An Action
is just a function that maps HTTP requests to responses. Before explaining this in more detail, let's change the action to:
def index = Action { Ok("hello, world") }
Refresh your browser and you should see the landing page replaced with "hello world"
. By having our action return Ok("hello, world")
, we are asking Play to return an HTTP response with status code 200 (indicating that the request was successful) and the body "hello world"
.
Let's go back to the original content of index
:
Action { Ok(views.html.index("Your new application is ready.")) }
We can see that this calls the method views.html.index
. This might appear strange, because there is no views
package anywhere. However, if you look at the app/views
directory, you will notice two files: index.scala.html
and main.scala.html
. These are templates, which, at compile time, get transformed into Scala functions. Let's have a look at main.scala.html
:
// app/views/main.scala.html @(title: String)(content: Html) <!DOCTYPE html> <html lang="en"> <head> <title>@title</title> <!-- not so important stuff --> </head> <body> @content </body> </html>
At compile time, this template is compiled to a function main(title:String)(content:Html)
in the package views.html
. Notice that the function package and name comes from the template file name, and the function arguments come from the first line of the template. The template contains embedded @title
and @content
values, which get filled in by the arguments to the function. Let's experiment with this in a Scala console:
$ activator console scala> import views.html._ import views.html._ scala> val title = "hello" title: String = hello scala> val content = new play.twirl.api.Html("<b>World</b>") content: play.twirl.api.Html = <b>World</b> scala> main(title)(content) res8: play.twirl.api.HtmlFormat.Appendable = <!DOCTYPE html> <html lang="en"> <head> <title>hello</title> <!-- not so important stuff --> </head> <body> <b>World</b> </body> </html>
We can call views.html.main
, just like we would call a normal Scala function. The arguments we pass in get embedded in the correct place, as defined by the template in views/main.scala.html
.
This concludes our introductory tour of Play. Let's briefly go over what we have learnt: when a request reaches the Play server, the server reads the URL and the HTTP verb and checks that these exist in its conf/routes
file. It will then pass the request to the Action
defined by the controller for that route. This Action
returns an HTTP response that gets fed back to the browser. In constructing the response, the Action
may make use of a template, which, as far as it is concerned is just a function (arguments list) => String
or (arguments list) => HTML
.