Skip to content
nuzelac edited this page Feb 10, 2012 · 13 revisions

Your first Play application

Let's write a simple todo list application with Play 2.0 and deploy it to the cloud.

Prerequisites

First of all, make sure that you have a working Play installation. You only need Java (version 6 minimum), and to unzip the Play binary package to start; everything is included.

As we will use the command line a lot, it’s better to use a Unix-like OS. If you run a Windows system, it will also work fine; you’ll just have to type a few commands in the command prompt.

You will of course need a text editor. If you are accustomed to use a full featured Scala IDE like Eclipse or IntelliJ you can of course use it. However with Play you can have fun working with a simple text editor like Textmate, Emacs or VI. This is because the framework manages the compilation and the deployment process itself.

Project creation

Now that Play is correctly installed, it’s time to create the new application. Creating a Play application is pretty easy and fully managed by the Play command line utility. That allows for standard project layouts between all Play applications.

Open a new command line and type:

$ play new todolist

It will prompt you for a few question. Choose to create a simple Scala application project template.

The play new command creates a new directory todolist/ and populates it with a series of files and directories, the most important being:

app/ contains the application’s core, split between models, controllers and views directories. This is the directory where .scala source files live.

conf/ contains all the application’s configuration files, especially the main application.conf file, the routes definition files and the messages files used for internationalization.

project contains the build scripts. The build system is based on sbt. But a new play application comes with a default build script that will just works fine for our application.

public/ contains all the publicly available resources, which includes JavaScript, stylesheets and images directories.

test/ contains all the application tests. Tests are written either as Specs2 specifications.

Because Play uses UTF-8 as single encoding, it’s very important that all text files hosted in these directories are encoded using this charset. Make sure to configure your text editor accordingly.

Using the Play console

Once you have an application created, you can run the Play console. Go to the new todolist/ directory and run:

$ play

It launches the Play console. There are several things you can do from the Play console, but let's start by running the application. From the console prompt, type run:

[todolist] $ run

Now the application is running in development mode. Open a browser at http://localhost:9000/:

Note: Read more about The Play Console.

Overview

Let’s see how the new application can display this page.

The main entry point of your application is the conf/routes file. This file defines all of the application’s accessible URLs. If you open the generated routes file you will see this first route:

GET	/       controllers.Application.index

That simply tells Play that when the web server receives a GET request for the / path, it must retrieve the Action to execute from the controllers.Application.index method.

Let’s see how the controllers.Application.index method looks like. Open the todolist/app/controllers/Application.scala source file:

package controllers

import play.api._
import play.api.mvc._

object Application extends Controller {
  
  def index = Action {
    Ok(views.html.index("Your new application is ready."))
  }
  
}

You see that controllers.Application.index returns an Action that will handle the request. An Action must return a Result that represent the HTTP response to send back to the Web browser.

Note: Read more about Actions.

Here the action returns a 200 OK response filled with HTML content. The HTML content is provided by a template. Play templates are compiled to standard Scala functions, here as views.html.index(message: String).

This template is defined in the app/views/index.scala.html source file:

@(message: String)

@main("Welcome to Play 2.0") {
    
    @play20.welcome(message)
    
}

The first line defines the function signature. Here it takes a single String parameter. Then the template content mix HTML (or any text based language) with Scala statements. The Scala statements starts with the special @ character.

Development workflow

Now let's make some modifications to the new application. In the Application.scala change the content of the response:

def index = Action {
  Ok("Hello world")
}

With this change the index action will now respond with a simple text/plain Hello world response. To test this change, just refresh the home page in your browser:

There is no need to compile the code yourself or restart the server to see the modification. It is automatically reloaded when a change is detected. But what happen when you make a mistake in your code?

Let's try:

def index = Action {
  Ok("Hello world)
}

Now reload the home page in your browser:

As you see errors are beautiful displayed directly in your browser.

Preparing the application

For our todo list application, we need a few actions and the corresponding urls. Let's start by defining the routes.

Edit the conf/routes file:

# Home page
GET     /                       controllers.Application.index
                                
# Tasks          
GET     /tasks                  controllers.Application.tasks
POST    /tasks                  controllers.Application.newTask
POST    /tasks/:id/delete       controllers.Application.deleteTask(id: Long)

We create a route to list all tasks, and a couple of others to handle task creation and deletion. The route to handle task deletion defines a variable argument id in the URL path. This value is then passed to the deleteTask method that will create the Action.

Now if your reload in your browser, you will see that Play cannot compile your routes files:

This is because they reference non-existing actions methods. So let's add them to the Application.scala file:

object Application extends Controller {
  
  def index = Action {
    Ok("Hello world")
  }
  
  def tasks = TODO
  
  def newTask = TODO
  
  def deleteTask(id: Long) = TODO
  
}

As you see we use TODO to define our actions implementation. Because we don't want to write the actions implementation yet, we can use the built-in TODO action that will return a 503 Not Implemented response.

You can try to access the http://localhost:9000/tasks to see that:

Now the last thing we need to fix before starting the action implementation is the index action. We want it to redirect automatically to the tasks list page:

def index = Action {
  Redirect(routes.Application.tasks)
}

As you see we use Redirect instead of Ok to specify a 303 See Other response type. We also use the reverse router to get the URL needed to fetch the tasks actions.

Note: Read more about the Router and reverse router.

Prepare the Task model

Before continuing the implementation we need to define what a Task looks like in our application. Create a case class for it in the app/models/Task.scala file:

package models

case class Task(id: Long, label: String)

object Task {
  
  def all(): List[Task] = Nil
  
  def create(label: String) {}
  
  def delete(id: Long) {}
  
}

We have also created a companion object to manage Task operations. For now we wrote dummy implementation for each operation, but later in this tutorial we will write implementations able to store the tasks into a relational database.

The application template

Our simple application will use a single Web page containing both the tasks list and the task creation form. Let's modify the index.scala.html template for that:

@(tasks: List[Task], taskForm: Form[String])

@import helper._

@main("Todo list") {
    
    <h1>@tasks.size task(s)</h1>
    
    <ul>
        @tasks.map { task =>
            <li>
                @task.label
                
                @form(routes.Application.deleteTask(task.id)) {
                    <input type="submit" value="Delete">
                }
            </li>
        }
    </ul>
    
    <h2>Add a new task</h2>
    
    @form(routes.Application.newTask) {
        
        @inputText(taskForm("label")) 
        
        <input type="submit" value="Create">
        
    }
    
}

We changed the template signature to take 2 parameters:

  • A list of tasks to display
  • A task form

We also imported helper._ that give us the form creation helpers, typically the form function that create HTML <form> with filled action and method attributes, and the inputText function that create the HTML imput given a form field.

Note: Read more about the Templating system and Forms helper.

The task form

A Form object encapsulate an HTML form definition, including validation constraints. Let's create a very simple form for our need. We need a form with a single label field. It will also check that the label provided by the user is not empty:

import play.api.data._
import play.api.data.Forms._

val taskForm = Form(
  "label" -> nonEmptyText
)

The type of taskForm is then Form[String] since it is a form generating a simple String. You also need to import some play.api.data classes.

Note: Read more about the Form definitions.

Rendering the first page

Now we have all elements needed to display the application page. Let's write the tasks action:

def tasks = Action {
  Ok(views.html.index(Task.all(), taskForm))
}

It renders a 200 OK result filled with the HTML rendered by the index.scala.html template called with the tasks list and the task form.

You can now try to access http://localhost:9000/tasks in your browser:

Handling the form submission

For now if we submit the task creation form, we still get the TODO page. Let's write the implementation of the newTask action:

def newTask = Action { implicit request =>
  taskForm.bindFromRequest.fold(
    errors => BadRequest(views.html.index(Task.all(), errors)),
    label => {
      Task.create(label)
      Redirect(routes.Application.tasks)
    }
  )
}

To fill the form we need to have the request in the scope. It is used by bindFromRequest to create a new form filled with the request data. If there is any errors in the form, we redisplay it (here we use 400 Bad Request instead of 200 OK). If there are no errors, we create the task and then redirect to the tasks list.

Note: Read more about the Form submissions.

Persist the tasks in a database

It's now time to persist the tasks in a database to make the application useful. Let's start by enabling a database in our application. In the conf/application.conf file, add:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

For now we will use a simple in memory database using H2. No need to restart the server, refreshing the browser is enough to set up the database.

We will use Anorm in this tutorial to query the database. First we need to define the database schema. Let's use Play evolutions for that, so create a first evolution script in conf/evolutions/default/1.sql:

# Tasks schema
 
# --- !Ups

CREATE SEQUENCE task_id_seq;
CREATE TABLE task (
    id integer NOT NULL DEFAULT nextval('task_id_seq'),
    label varchar(255)
);
 
# --- !Downs
 
DROP TABLE task;
DROP SEQUENCE task_id_seq;

Now if you refresh your browser, Play will warn you that your database needs evolution:

Just click the Apply script button to apply the script automatically. You database schema is now ready!

Note: Read more about Evolutions.

It's now time to implement the SQL queries in the Task companion object. Let's start by the all() operation. Using Anorm we can define a parser that will transform a JDBC ResultSet row to a Task value:

import anorm._
import anorm.SqlParser._

val task = {
  get[Long]("id") ~ 
  get[String]("label") map {
    case id~label => Task(id, label)
  }
}

Here task is a parser which given a JDBC ResultSet row with at least an id and a label column, is able to create a Task value.

We can now use this parser to write the all() method implementation:

import play.api.db._
import play.api.Play.current

def all(): List[Task] = DB.withConnection { implicit c =>
  SQL("select * from task").as(task *)
}

We use the Play DB.withConnection helper to create and release automatically a JDBC connection.

Then we use the Anorm SQL method to create the query. The as method allows to parse the ResultSet using the task * parser: it will parse as many task rows as possible and then return a List[Task] (since our task parser returns a Task).

It's time to complete the implementation:

def create(label: String) {
  DB.withConnection { implicit c =>
    SQL("insert into task (label) values ({label})").on(
      'label -> label
    ).executeUpdate()
  }
}

def delete(id: Long) {
  DB.withConnection { implicit c =>
    SQL("delete from task where id = {id}").on(
      'id -> id
    ).executeUpdate()
  }
}

Now you can play again with the application, creating new tasks should work.

Note: Read more about Anorm.

Deleting tasks

Now that we can create tasks, we need to be able to delete them. Very simple, we just need to finish the implementation of the deleteTask action:

def deleteTask(id: Long) = Action {
  Task.delete(id)
  Redirect(routes.Application.tasks)
}

Deploying to Heroku

All features are completed. It's time to deploy our application in production. Let's deploy it to heroku. First you need to create a Procfile for Heroku. Create the Procfile in the root application directory:

web: target/start -Dhttp.port=${PORT} -DapplyEvolutions.default=true -Ddb.default.url=${DATABASE_URL} -Ddb.default.driver=org.postgresql.Driver

Note: Read more about Deploying to Heroku.

Using system properties we override the application configuration when running on Heroku. But since heroku provides an PostgreSQL database we need to add the required driver to our application dependencies.

Specify it into the project/Build.scala file:

val appDependencies = Seq(
  "postgresql" % "postgresql" % "8.4-702.jdbc4"
)

Note: Read more about Dependencies management.

Heroku uses git to deploy your application. Let's initialize the git repository:

$ git init
$ git add .
$ git commit -m "init"

Now we can create the application on Heroku:

$ heroku create --stack cedar

Creating warm-frost-1289... done, stack is cedar
http://warm-1289.herokuapp.com/ | [email protected]:warm-1289.git
Git remote heroku added

And then deploying it using simple git push heroku master:

$ git push heroku master

Counting objects: 34, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (20/20), done.
Writing objects: 100% (34/34), 35.45 KiB, done.
Total 34 (delta 0), reused 0 (delta 0)

-----> Heroku receiving push
-----> Scala app detected
-----> Building app with sbt v0.11.0
-----> Running: sbt clean compile stage
       ...
-----> Discovering process types
       Procfile declares types -> web
-----> Compiled slug size is 46.3MB
-----> Launching... done, v5
       http://8044.herokuapp.com deployed to Heroku

To [email protected]:floating-lightning-8044.git
* [new branch]      master -> master

Heroku will build your application and deploy it to a node somewhere on the cloud. You can check the state of the application’s processes:

$ heroku ps

Process       State               Command
------------  ------------------  ----------------------
web.1         up for 10s          target/start

It's started, you can now open it in your browser.

Your first application is now up and running in production!

Clone this wiki locally