├── blogy ├── conf │ ├── application.conf │ ├── routes │ └── logback.xml ├── project │ ├── build.properties │ └── plugins.sbt ├── build.sbt ├── app │ ├── views │ │ ├── layout.scala.html │ │ └── index.scala.html │ └── controllers │ │ └── IndexController.scala └── .gitignore ├── images ├── template-for.png ├── template-index.png └── action-not-found.png ├── README.md ├── 02-templates.md └── 01-hello-world.md /blogy/conf/application.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blogy/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 -------------------------------------------------------------------------------- /blogy/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") -------------------------------------------------------------------------------- /images/template-for.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shekhargulati/play-the-missing-tutorial/HEAD/images/template-for.png -------------------------------------------------------------------------------- /images/template-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shekhargulati/play-the-missing-tutorial/HEAD/images/template-index.png -------------------------------------------------------------------------------- /images/action-not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shekhargulati/play-the-missing-tutorial/HEAD/images/action-not-found.png -------------------------------------------------------------------------------- /blogy/build.sbt: -------------------------------------------------------------------------------- 1 | name := "blogy" 2 | 3 | version := "1.0.0-SNAPSHOT" 4 | 5 | lazy val root = (project in file(".")).enablePlugins(PlayScala) 6 | 7 | scalaVersion := "2.12.3" 8 | 9 | libraryDependencies += guice -------------------------------------------------------------------------------- /blogy/conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | 6 | GET / controllers.IndexController.index() 7 | GET /index controllers.IndexController.index() -------------------------------------------------------------------------------- /blogy/app/views/layout.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 | 4 | 5 | @title 6 | 7 | 8 |
Blogy: Home
9 |
10 |
@content
11 | 12 | -------------------------------------------------------------------------------- /blogy/app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String, user: Map[String, String], posts: List[Map[String, String]]) 2 | @layout(title) { 3 | @if(user.get("username").isDefined){ 4 |

Hello, @user.get("username").get!

5 | }else{ 6 |

Hello, Guest!

7 | } 8 | @for(post <- posts){ 9 |

@post.getOrElse("author","Guest") says: @post.getOrElse("body","Hello!")

10 | } 11 | } 12 | -------------------------------------------------------------------------------- /blogy/app/controllers/IndexController.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api.mvc._ 4 | 5 | class IndexController extends Controller { 6 | 7 | def index = Action { 8 | val user = Map("username" -> "shekhargulati") 9 | val posts = List( 10 | Map("author" -> "Shekhar", 11 | "body" -> "Getting started with Play" 12 | ), 13 | Map("author" -> "Rahul", 14 | "body" -> "Getting started with Docker" 15 | ) 16 | ) 17 | Ok(views.html.index("Welcome to Blogy", user, posts)) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /blogy/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %coloredLevel %logger{15} - %message%n%xException{10} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /blogy/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### SBT template 3 | # Simple Build Tool 4 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 5 | 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | .history 11 | .cache 12 | ### PlayFramework template 13 | # Ignore Play! working directory # 14 | bin/ 15 | /db 16 | .eclipse 17 | /lib/ 18 | /logs/ 19 | /modules 20 | /project/project 21 | /project/target 22 | /target 23 | tmp/ 24 | test-result 25 | server.pid 26 | *.iml 27 | *.eml 28 | /dist/ 29 | .cache 30 | ### Scala template 31 | *.class 32 | *.log 33 | 34 | # sbt specific 35 | .cache 36 | .history 37 | .lib/ 38 | dist/* 39 | target/ 40 | lib_managed/ 41 | src_managed/ 42 | project/boot/ 43 | project/plugins/project/ 44 | 45 | # Scala-IDE specific 46 | .scala_dependencies 47 | .worksheet 48 | ### JetBrains template 49 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 50 | 51 | *.iml 52 | 53 | ## Directory-based project format: 54 | .idea/ 55 | # if you remove the above rule, at least ignore the following: 56 | 57 | # User-specific stuff: 58 | # .idea/workspace.xml 59 | # .idea/tasks.xml 60 | # .idea/dictionaries 61 | 62 | # Sensitive or high-churn files: 63 | # .idea/dataSources.ids 64 | # .idea/dataSources.xml 65 | # .idea/sqlDataSources.xml 66 | # .idea/dynamic.xml 67 | # .idea/uiDesigner.xml 68 | 69 | # Gradle: 70 | # .idea/gradle.xml 71 | # .idea/libraries 72 | 73 | # Mongo Explorer plugin: 74 | # .idea/mongoSettings.xml 75 | 76 | ## File-based project format: 77 | *.ipr 78 | *.iws 79 | 80 | ## Plugin-specific files: 81 | 82 | # IntelliJ 83 | /out/ 84 | 85 | # mpeltonen/sbt-idea plugin 86 | .idea_modules/ 87 | 88 | # JIRA plugin 89 | atlassian-ide-plugin.xml 90 | 91 | # Crashlytics plugin (for Android Studio and IntelliJ) 92 | com_crashlytics_export_strings.xml 93 | crashlytics.properties 94 | crashlytics-build.properties 95 | 96 | tmp.ws 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Play: The Missing Tutorial ™ 2 | ------ 3 | 4 | Last weekend(19-20 March 2016) I started learning [Play Framework](https://www.playframework.com/) as part of my [52 Technologies in 2016](https://github.com/shekhargulati/52-technologies-in-2016) blog series. Rather than writing about Play framework in one tutorial I decided to create a tutorial series(in a separate Github repository) so that I can focus on it individually. This repository will host both the content and sample application source code. This work is inspired by [The Flask Mega Tutorial](http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) written by [Miguel Grinberg](https://twitter.com/miguelgrinberg). The Flask Mega Tutorial is very well written step by step introduction on how to build Python web applications using Flask framework. 5 | 6 | Play Framework is not an easy framework to get started. You can very quickly get overwhelmed by its features and complexity. The goal of this tutorial is to help you build an application step by step so that you remain focussed and get most out of the Play framework. 7 | 8 | **This tutorial will cover Play Scala framework version 2.5.0** 9 | 10 | ## Application 11 | 12 | In this tutorial, we will build a blogging platform called `blogy` that you can use to write and publish blogs. To build this web application, we will cover topics mentioned below. 13 | 14 | ## Table of Contents 15 | 16 | These are some of the topics I will cover as we make progress with this project: 17 | 18 | * [Part 1: Hello World!](./01-hello-world.md) 19 | * [Part 2: Templates](./02-templates.md) 20 | 21 | ## Contributing to the Play: The Missing Tutorial ™ 22 | 23 | Please contribute if you see an error or something that could be better! Raise an [issue](https://github.com/shekhargulati/play-the-missing-tutorial/issues) or send me a pull request to improve. Contributions of all kinds, including corrections, additions, improvements, and translations, are welcome! 24 | 25 | ----------- 26 | You can follow me on twitter at [https://twitter.com/shekhargulati](https://twitter.com/shekhargulati) or email me at . Also, you can read my blogs at [http://shekhargulati.com/](http://shekhargulati.com/) 27 | 28 | [![Analytics](https://ga-beacon.appspot.com/UA-59411913-4/shekhargulati/play-the-missing-tutorial)](https://github.com/igrigorik/ga-beacon) 29 | -------------------------------------------------------------------------------- /02-templates.md: -------------------------------------------------------------------------------- 1 | # Play Scala Templates 2 | 3 | In the [previous chapter](./01-hello-world.md), we created a basic Play Scala application that renders `Hello, World!` when a HTTP GET request is made to index `/`. You can run the application by executing `sbt run` command from inside the application root directory. 4 | 5 | We will start this chapter from where we left in last chapter, so you may want to make sure you have the above application correctly installed and working. 6 | 7 | ## Github repository 8 | 9 | The code for demo application is available on github: [blogy](./blogy). You should get the `part-02` release. If you are not comfortable with Git, then you can directly download the [part-02.zip](https://github.com/shekhargulati/play-the-missing-tutorial/archive/part-02.zip). 10 | 11 | ## Why we need templates? 12 | 13 | Let's start with the use case that we have to render HTML page when user visits our application. The page should have welcome message with name of the logged in user. In this chapter, we will ignore the fact that we don't have user authentication and authorization in place. We will use a dummy user to convey the point. 14 | 15 | One way to render HTML would be to return the HTML in the response body as shown below. 16 | 17 | ```scala 18 | package controllers 19 | 20 | import javax.inject._ 21 | import play.api.mvc._ 22 | 23 | class IndexController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { 24 | 25 | def index() = Action { 26 | val user = Map("username" -> "shekhargulati") 27 | Ok { 28 | s""" 29 | | 30 | | 31 | | Home Page 32 | | 33 | | 34 | |

Hello, ${user.getOrElse("username", "guest")}

35 | | 36 | | 37 | """.stripMargin 38 | }.as("text/html") 39 | } 40 | 41 | } 42 | ``` 43 | 44 | In the code shown above, we modified the `index` method to return HTML in the response body. `Ok` is factory method to produce `Result`. `Result` is value object which defines the response header and a body ready to send to the client. We set the `Content-Type` to `text/html` using the `as` method so that browser render it as HTML. 45 | 46 | Go to [http://localhost:9000/](http://localhost:9000/) to see how it looks in your browser. 47 | 48 | The approach we have taken above is a quick and easy hack to render HTML but it will not scale for complex pages. It will very quickly become unmanageable as we are mixing multiple concerns together. 49 | 50 | ## Templates can help 51 | 52 | Template allows you to separate presentation concern from the rest of the application. This makes it easy for UI designers to work independently on the user interface concern without being bothered by the application source code. 53 | 54 | Play comes bundled with [Twirl](https://github.com/playframework/twirl), a powerful Scala based template engine. 55 | 56 | Let's write our first template. Templates are typically created inside the `app/views` directory. Create a new folder called `views`, a new file in the newly created `views` folder called `index.scala.html`, and populate it with following content. 57 | 58 | ```html 59 | @(title: String, user: Map[String, String]) 60 | 61 | 62 | 63 | @title 64 | 65 | 66 |

Hello, @user.getOrElse("username", "guest")!

67 | 68 | 69 | ``` 70 | 71 | As you case see above, we wrote a mostly standard HTML page with some placeholders marked with `@` for dynamic content. Every time Twirl template engine encounters `@` character, it indicates the beginning of a dynamic statement. If you have used other templates engines like mustache or jinja then you would have used placeholders like `{{}}`. The statement after `@` is valid Scala code as you can see from `@user.getOrElse("username","guest")`. We are calling `getOrElse` function of `Map`. 72 | 73 | > **Because @ is a special character, you’ll sometimes need to escape it. Do this by using @@.** 74 | 75 | One thing that we have not discussed is the first line of template `@(title: String, user: Map[String, String])`. Let's decipher this statement. Each Twirl template is compiled to a function. The first line defines the parameters of this template function. The index template function needs two parameters of type `String` and `Map[String, String]`. Now, the controller who will use this template will have to pass these as method parameters or template will not compile and you will see compilation error when you will view the page. 76 | 77 | Now, let's see how we can use the template defined above in our controller. 78 | 79 | ```scala 80 | package controllers 81 | 82 | import javax.inject._ 83 | import play.api.mvc._ 84 | 85 | class IndexController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { 86 | 87 | def index = Action { 88 | val user = Map("username" -> "shekhargulati") 89 | Ok(views.html.index("Welcome to Blogy", user)) 90 | } 91 | 92 | } 93 | ``` 94 | 95 | In the code shown above, we are returning the index template instead of hard coded HTML. You can also notice that we are passing the function parameters as well. `index` is a class with an `apply` method so you can call as you are calling a function. 96 | 97 | Let's understand what happens when you return a template using `views.html.index("Welcome to Blogy", user)`. When the view is rendered for the first time, `index.scala.html` template is compiled to a class with same name as template i.e. index in our case. This class extends from `BaseScalaTemplate`, which is the base class for all Twirl templates. This class has all the utility methods that the template might need to convert the `index.scala.html` template to actual html. `index` class also extend one trait `play.twirl.api.Template*`, `*` depends on the number of parameters template needs. In our case, `play.twirl.api.Template2` as we have two parameters defined in the `index.scala.html` template. The index class looks as shown below. 98 | 99 | ```scala 100 | import play.twirl.api._ 101 | 102 | class index extends BaseScalaTemplate[HtmlFormat.Appendable,Format[HtmlFormat.Appendable]](HtmlFormat) with Template2[String,Map[String, String]],HtmlFormat.Appendable] { 103 | 104 | def apply(title: String, user: Map[String, String]):HtmlFormat.Appendable = { 105 | ... 106 | } 107 | } 108 | ``` 109 | 110 | You can view the application by opening [http://localhost:9000/](http://localhost:9000/) in your favorite browser. It will look something like as shown below. 111 | 112 | ![](images/template-index.png) 113 | 114 | ## Using Control statements in templates 115 | 116 | Twirl templates have support for control statements. Let's add an if statement that will render username if it exists in the Map or `Guest` otherwise. 117 | 118 | ```html 119 | @(title: String, user: Map[String, String]) 120 | 121 | 122 | @title 123 | 124 | 125 | @if(user.get("username").isDefined){ 126 |

Hello, @user.get("username").get!

127 | } else { 128 |

Hello, Guest!

129 | } 130 | 131 | 132 | ``` 133 | 134 | Feel free to test it by removing username from the Map to see control statement in action. 135 | 136 | ## Iterating in templates 137 | 138 | The logged in user in our `blogy` application will probably want to see recent posts from followed users in the home page, so let's see how we can do that. 139 | 140 | Let's update the controller to return a list of posts as well. Now, the `views.html.index` function is taking three parameters. We have to change our template to reflect that. 141 | 142 | ```scala 143 | package controllers 144 | 145 | import play.api.mvc._ 146 | 147 | class IndexController extends Controller { 148 | 149 | def index() = Action { 150 | val user = Map("username" -> "shekhargulati") 151 | val posts = List( 152 | Map( 153 | "author" -> "Shekhar", 154 | "body" -> "Getting started with Play" 155 | ), 156 | Map( 157 | "author" -> "Rahul", 158 | "body" -> "Getting started with Docker" 159 | ) 160 | ) 161 | Ok(views.html.index("Welcome to Blogy", user, posts)) 162 | } 163 | 164 | } 165 | ``` 166 | 167 | We are using a List to store a post. Each post is Map with key value pairs. Later in this series, we will use a real database to store and fetch the data. For now, we will go with in-memory data structure representing data from the database. 168 | 169 | Let's update our template to add `posts` as its parameter. To iterate over list, we will use the `for` loop as shown below. 170 | 171 | ```html 172 | @(title: String, user: Map[String, String], posts: List[Map[String, String]]) 173 | 174 | 175 | @title 176 | 177 | 178 | @if(user.get("username").isDefined) { 179 |

Hello, @user.get("username").get!

180 | } else { 181 |

Hello, Guest!

182 | } 183 | @for(post <- posts) { 184 |

@post.getOrElse("author","Guest") says: @post.getOrElse("body","Hello!")

185 | } 186 | 187 | 188 | ``` 189 | 190 | The page will be rendered as shown below. 191 | 192 | ![](images/template-for.png) 193 | 194 | ## Defining reusable layout templates 195 | 196 | One good practice when working with templates is to make sure they don't duplicate content. To achieve that, you should define layout that templates can reuse. Our `blogy` web application will need to have a navigation bar at the top of the page with a few links. Here you will get the link to edit your profile, to login, logout, etc. 197 | 198 | We can create a new template `layout.scala.html` that will define a base template that includes the navigation bar and page structure. 199 | 200 | ```html 201 | @(title: String)(content: Html) 202 | 203 | 204 | 205 | @title 206 | 207 | 208 |
Blogy: Home
209 |
210 |
@content
211 | 212 | 213 | ``` 214 | 215 | As you can see above, layout template takes two parameters: a title and an HTML content block. 216 | 217 | We will update `index.scala.html` to use the base layout. 218 | 219 | ```html 220 | @(title: String, user: Map[String, String], posts: List[Map[String, String]]) 221 | @layout(title) { 222 | @if(user.get("username").isDefined){ 223 |

Hello, @user.get("username").get!

224 | }else{ 225 |

Hello, Guest!

226 | } 227 | @for(post <- posts){ 228 |

@post.getOrElse("author","Guest") says: @post.getOrElse("body","Hello!")

229 | } 230 | } 231 | ``` 232 | 233 | --- 234 | 235 | That's it for the second part of Play framework tutorial. If you have any feedback then you can add a comment to this Github issue [https://github.com/shekhargulati/play-the-missing-tutorial/issues/1](https://github.com/shekhargulati/play-the-missing-tutorial/issues/1). 236 | 237 | [![Analytics](https://ga-beacon.appspot.com/UA-59411913-4/shekhargulati/play-the-missing-tutorial/02-templates)](https://github.com/igrigorik/ga-beacon) 238 | -------------------------------------------------------------------------------- /01-hello-world.md: -------------------------------------------------------------------------------- 1 | # Let's say Hello World! with Play Framework 2 | 3 | 4 | [Play framework](https://www.playframework.com/) is a MVC style web application framework for JVM built using Scala, Akka, and Netty. It provides API for both Java and Scala programming languages. You can use it to build either your traditional web applications with server side rendering or modern [single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application) that uses REST with JavaScript MVC framework like AngularJS. One design decision that makes Play different from other Java/Scala MVC web frameworks is that it is not built on top of the Servlet standard. It is full stack Java framework that runs your application stand-alone. **Play is a framework not a library**. 5 | 6 | ## Application 7 | 8 | In this tutorial series, we will build a blogging platform called `blogy` that you can use to write and publish blogs. 9 | 10 | ## Prerequisite 11 | 12 | To work along with this tutorial, you will need following installed on your machine. 13 | 14 | 1. [Download and install latest JDK 8 update](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) on your operating system. I am using JDK version `1.8.0_60`. 15 | 16 | 2. [Download and install latest Scala version](http://www.scala-lang.org/download/) on your machine. I am using Scala `2.12.3`. 17 | 18 | 3. [Download and install the latest SBT version](http://www.scala-sbt.org/download.html) on your machine. I am using SBT version `0.13.16`. 19 | 20 | 4. An IDE of your choice. You can use either of [IntelliJ](https://www.jetbrains.com/idea/download/) or [Eclipse](http://scala-ide.org/). I prefer IntelliJ. 21 | 22 | 5. You should be comfortable reading and writing Scala code. Throughout the tutorial series, we will also use SBT so in case you are not familiar with SBT then you can read [my SBT tutorial](https://github.com/shekhargulati/52-technologies-in-2016/tree/master/02-sbt). 23 | 24 | ## Github repository 25 | 26 | The code for demo application is available on github: [blogy](./blogy). You should get the `part-01` release. If you are not comfortable with Git, then you can directly download the [part-01.zip](https://github.com/shekhargulati/play-the-missing-tutorial/archive/part-01.zip). 27 | 28 | ## Getting started with Play framework 29 | 30 | Okay, let's get started with application development. 31 | 32 | In this series, we will use the latest Play framework version `2.6.3`. Play documentation recommends that one should [download a Starter Project](https://playframework.com/download#starters) to quickly get started with Play framework. These starter projects contains everything you need to go from zero to a running Play project. There are a lot of official and templates that one can choose from. You can also start a new project using `sbt new` like: 33 | 34 | ```bash 35 | $ sbt new playframework/play-scala-seed.g8 36 | ``` 37 | 38 | For Scala projects or: 39 | 40 | ```bash 41 | $ sbt new playframework/play-java-seed.g8 42 | ``` 43 | 44 | For Java based projects. 45 | 46 | Both templates above generate minimum projects so that you can get (almost) only the basic project structure. But you can also create the project manually if you think it is less intimidating and easier to understand. 47 | 48 | ### Manually creating the project 49 | 50 | Open a new command-line terminal and navigate to a convenient location on your file system where you want to host the application source code. Let's call the application folder `blogy`. 51 | 52 | ```bash 53 | $ mkdir -p blogy/{app/controllers,conf,project} 54 | $ cd blogy 55 | ``` 56 | 57 | Create the following directory layout: 58 | 59 | ``` 60 | blogy/ 61 | ├── app 62 | │   └── controllers 63 | ├── conf 64 | └── project 65 | ``` 66 | 67 | We will start with creating our build file `build.sbt`. Play uses SBT to build and run the application. Create a new file `build.sbt` inside the root i.e. `blogy` directory and populate it with following contents. 68 | 69 | ```scala 70 | name := "blogy" 71 | 72 | version := "1.0.0-SNAPSHOT" 73 | 74 | lazy val root = (project in file(".")).enablePlugins(PlayScala) 75 | 76 | scalaVersion := "2.12.3" 77 | 78 | libraryDependencies += guice 79 | ``` 80 | 81 | The build file shown above does the following: 82 | 83 | 1. It specified name of the project as `blogy`. 84 | 2. Then, we specified version of the project as `1.0.0-SNAPSHOT`. 85 | 3. Next, it enables `PlayScala` plugin for `blogy` project. Later, we will add Play SBT plugin to the `blogy` application. 86 | 4. Finally, we set Scala version to `2.12.3`. 87 | 88 | Now, we will add Play Scala plugin to our project so that it is treated as a Play application. In SBT, you declare plugins you need in your project by adding them to `plugins.sbt` file inside the `project` directory. Create a new file `plugins.sbt` inside the project directory and populate it with following contents. 89 | 90 | ```scala 91 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") 92 | ``` 93 | 94 | This is one required SBT plugin that we will need in our project. There are many more plugins like `sbt-coffeescript`, `sbt-less`, `sbt-scalariform`, etc that we can add for adding different capabilities to our project. We will add few more plugins later in this series. 95 | 96 | It is a good practice in SBT projects to lock down the version of SBT. By default, the installed version of SBT will be used by the application. This might cause compatibility issues if the future version of SBT becomes incompatible with Play. To force a SBT version, create a new file `build.properties` inside the `project` directory and add the following line to it. 97 | 98 | ``` 99 | sbt.version=0.13.16 100 | ``` 101 | 102 | Now, to test our current setup launch the `sbt` command from inside the `blogy` directory. You will see output as shown below. 103 | 104 | ```bash 105 | $ sbt 106 | ``` 107 | 108 | ``` 109 | [info] Loading global plugins from /Users/shekhargulati/.sbt/0.13/plugins 110 | [info] Loading project definition from /Users/shekhargulati/dev/git/play-the-missing-tutorial/blogy/project 111 | [info] Updating {file:/Users/shekhargulati/dev/git/play-the-missing-tutorial/blogy/project/}blogy-build... 112 | [info] Resolving org.fusesource.jansi#jansi;1.4 ... 113 | [info] Done updating. 114 | [info] Set current project to blogy (in build file:/Users/shekhargulati/dev/git/play-the-missing-tutorial/blogy/) 115 | [blogy] $ 116 | ``` 117 | 118 | Write `play` and press tab you will see all play specific tasks. 119 | 120 | ``` 121 | [blogy] $ play 122 | playAggregateReverseRoutes playAllAssets playAssetsClassloader playAssetsWithCompilation playCommonClassloader playCompileEverything playDefaultAddress 123 | playDefaultPort playDependencyClasspath playDevSettings playDocsJar playDocsModule playDocsName playExternalizeResources 124 | playExternalizedResources playGenerateReverseRouter playGenerateSecret playInteractionMode playJarSansExternalized playMonitoredFiles playNamespaceReverseRouter 125 | playOmnidoc playPackageAssets playPlugin playPrefixAndAssets playReload playReloaderClasspath playRoutes 126 | playRoutesGenerator playRoutesImports playRoutesTasks playRunHooks playStop playUpdateSecret 127 | 128 | ``` 129 | 130 | To run a Play project, you can use the `run` task. It will start the server at port `9000`. 131 | 132 | ``` 133 | [blogy] $ run 134 | 135 | --- (Running the application, auto-reloading is enabled) --- 136 | 137 | [info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000 138 | 139 | (Server started, use Enter to stop and go back to the console...) 140 | ``` 141 | 142 | > **You can specify a different port number by just passing it along with run task like `run 8080`. This will start Play application at port 8080.** 143 | 144 | You can access the application at [http://localhost:9000/](http://localhost:9000/). On accessing the page, you will be greeted with following error message. 145 | 146 | ``` 147 | java.io.IOException: resource not found on classpath: application.conf, application.json 148 | ``` 149 | 150 | Every play framework application needs a configuration file `application.conf` inside the configuration directory. Create a new file `application.conf`. You can have an empty configuration for now and then the defaults will be used. `application.conf` uses HOCON(Human Optimized Config Object Notation) notation. According to [HOCON documentation](https://github.com/typesafehub/config/blob/master/HOCON.md), 151 | 152 | > **The primary goal is: keep the semantics (tree structure; set of types; encoding/escaping) from JSON, but make it more convenient as a human-editable config file format.** 153 | 154 | Access the index page [http://localhost:9000/](http://localhost:9000/) again, this time you will be greeted by different error page. 155 | 156 | ![](images/action-not-found.png) 157 | 158 | The reason for this error message is that we have not mapped any action to the GET request to index `/` path. 159 | 160 | ## "Hello, World" with Play 161 | 162 | You now have a working Play application environment. Let's write our first Play controller. In Play, you write controllers which define `Action`s that process the request and return response. The decision to select an `Action` is made by the `router` which uses the `routes` configuration to select the correct `Action`. The `router` looks at the request details like method, path and query parameters to decide which `Action` should handle the request. 163 | 164 | Create your first controller inside the `app/controllers` directory. We will name our controller `IndexController` as it is handling the index request. 165 | 166 | ```scala 167 | package controllers 168 | 169 | import javax.inject._ 170 | import play.api.mvc._ 171 | 172 | class IndexController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { 173 | 174 | def index() = Action { 175 | Ok("Hello, World!") 176 | } 177 | 178 | } 179 | ``` 180 | 181 | The code shown above does the following: 182 | 183 | 1. It import all the classes and traits inside the `javax.inject` and `play.api.mvc` packages. 184 | 2. Next, we created a Scala class that extends `BaseController` trait. `BaseController` provides access to all the utility methods to generate `Action` and `Result` types and requires that you implements `controllerComponents` method. 185 | 3. So we inject an instance of `ControllerComponents` as a `val` to have a full implementation of `BaseController`.s 186 | 3. Finally, we created a method `index` that returns an `Action`. Action is a function `Request[A] => Result` that takes a request and returns a result. We returned HTTP status Ok i.e. 200 with `Hello, World!` text in the response body. Action syntax `Action {}` is possible because of a `apply` method defined in the `Action` object that takes a block `def apply(block: => Result): Action[AnyContent]`. 187 | 188 | Now, we will map the index URL `/` to index `Action` by defining configuration in the `routes` configuration. Create a new file `routes` inside the `conf` directory. 189 | 190 | ``` 191 | # Routes 192 | # This file defines all application routes (Higher priority routes first) 193 | # ~~~~ 194 | 195 | GET / controllers.IndexController.index() 196 | ``` 197 | 198 | As is obvious, we are mapping HTTP GET request to `/` to `controllers.IndexController.index()` action method. 199 | 200 | You can now test that `/` url is working by either opening [http://localhost:9000/](http://localhost:9000/) in the browser or using a command-line tool like cURL as shown below. 201 | 202 | ```bash 203 | $ curl -i http://localhost:9000/ 204 | ``` 205 | 206 | ``` 207 | HTTP/1.1 200 OK 208 | Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin 209 | X-Frame-Options: DENY 210 | X-XSS-Protection: 1; mode=block 211 | X-Content-Type-Options: nosniff 212 | Content-Security-Policy: default-src 'self' 213 | X-Permitted-Cross-Domain-Policies: master-only 214 | Date: Fri, 08 Sep 2017 22:33:29 GMT 215 | Content-Type: text/plain; charset=UTF-8 216 | Content-Length: 12 217 | 218 | Hello, World 219 | ``` 220 | 221 | You can map multiple URLs to the same action by defining the route configuration for new URL. Let's suppose we want to map both `/` and `/index` to the same controller then we can update our `routes` file as shown below. 222 | 223 | ``` 224 | GET / controllers.IndexController.index() 225 | GET /index controllers.IndexController.index() 226 | ``` 227 | 228 | Every Play application has access to the Play documentation at [http://localhost:9000/@documentation/Home](http://localhost:9000/@documentation/Home). 229 | 230 | > **When you run the Play application using the `run` task, Play application is launched in the `dev` mode. In dev mode, sources are constantly watches for changes, and whenever source changes project is reloaded with new changes. There are two other modes of Play application - `test` and `production`. We will look at them later in this series.** 231 | 232 | You can stop the running server by pressing `Ctrl+D` Or `ENTER`. This will take you back to the SBT shell. If you will press `Ctrl+C` then SBT process will also exit and you will be back to your command-line terminal. 233 | 234 | --- 235 | 236 | That's it for the first part of Play framework tutorial. If you have any feedback then you can add a comment to this Github issue [https://github.com/shekhargulati/play-the-missing-tutorial/issues/1](https://github.com/shekhargulati/play-the-missing-tutorial/issues/1). 237 | 238 | You can read next part [here](./02-templates.md). 239 | 240 | [![Analytics](https://ga-beacon.appspot.com/UA-59411913-4/shekhargulati/play-the-missing-tutorial/01-hello-world)](https://github.com/igrigorik/ga-beacon) 241 | --------------------------------------------------------------------------------