Play Routes – Part 1, Basics
Routes? What’s to explain?
Routes in Play are not a difficult concept to grasp, but as always, the devil is in the details. Let me show you how to get the most out of routes and how to tackle some of the typical problems in designing the routing scheme of a Play web application.
Before we dive into the details, let’s recap the basics.
A route is a mapping from a URL to a controller action. Routes are defined in a Play project’s routes file in the conf folder. You can only use one routes file and routes in submodules are currently not supported (but will be in the upcoming 2.1 release).
A route is defined by specifying the HTTP method (Play supports GET, PUT, POST, DELETE), the path, and a fully-qualified controller method (usually called an action). These three elements are separated by as much whitespace as you like.
# Show the index page GET / controllers.Application.index() # Login POST /login controllers.Application.login()
Routes are evaluated from top to bottom, i.e. first come, first served. This is important in your route design, because if you’re not careful, routes can override each other.
On thing that Play doesn’t handle well is routes that end with a slash. In the Unix/Mac file system, /Applications and /Applications/ would be considered equal. On the web, these could be different. Since this only creates confusion, you should choose one layout and stick to it. You can then redirect All the other URLs to your preferred format, either in the routes file or better in the Global object (see last section).
GET /users controllers.Users.showAllUsers() GET /users/ controllers.Default.redirectTo("/users")
In a web application, some parts of the URL are dynamic in nature. The path of the URL indicates a resource, while the query String provides options for the resource (most of the time HTML form values). In Play, you can map parameters to both the path as well as the query string.
You define path parameters using a colon, e.g. :username or :id, and map them by name (either as-is or with a regular expression) to parameters in the controller action. The controller action is written in Scala-like syntax, i.e. parameters are defined with
# Show a user GET /users/:username controllers.Users.showUser(username: String) # Show user picture in a certain format, e.g. /users/joe/image/114x114 GET /users/:username/image/$width<[0-9]+>x$height<[0-9]+> controllers.Users.userImage(username: String, width: Int, height: Int) # Delete a user DELETE /users/:id controllers.Users.deleteUser(id: Long)
When you want to them appear as query parameters, simply don’t put them in the path.
# /users/browse?sortBy=lastName&sortDirection=asc GET /users/browse controllers.Users.browse(sortBy: String, sortDirection: String)
Query parameters can have default values, so when they are not provided, that value will be passed to the action.
# Default values GET /users/browse controllers.Users.browse(sortBy: String ?= "lastName", sortDirection: String ?= "asc", page: Int ?= 1) # Now you can call /users/browse or /users/browse?sortBy=firstName&page=10
Parameters can also have fixed values. These are default values that cannot be changed at all and help at defining cleaner URLs.
# Fixed values GET /users/:username/profile-image controllers.Users.userImage(username: String, width: Int = 64, height: Int = 64)
By default, each parameter is of type String. This means you can omit the type declaration when you deal with Strings, although I prefer to keep it explicit.
Naturally, you don’t want to convert each parameter from String to some other time in each action, so other value types are automatically converted by Play. Play 2.0.x supports the Scala numerical types Int and Long, 2.1 adds Double, Float, List (Scala and Java), and java.util.UUID.
As mentioned above, you have to be careful about the evaluation order of routes.
GET /users/:username controllers.Users.showUser(username) GET /users/browse controllers.Users.browseUsers()
What would happen, when a user chooses the name
browse? Right, the
/users/browse route would not match anymore.
Every controller and every template automatically imports the reverse routes. A route maps an URL to an action with its parameters, so a reverse route does the opposite by mapping an action call with its parameters to the URL.
Taking our example from above,
routes.Users.browse() would resolve to
/users/joe. This parameter binding part is especially interesting because it also ensures that fixed parameters are resolved correctly.
What’s especially great is that the routes are compiled, so whenever you change a controller action’s signature, your controllers and templates will no longer compile. So be sure to always use the reverse routes instead of hard-coded URLs.
You can use the reverse routes outside a controller (e.g. in the GlobalSettings) by using the routes subpackage, e.g.
controllers.routes.Users.show(1234) or by importing
controllers.routes first and then using
Reverse routes are of type
play.mvc.Call (Java) or
play.api.mvc.Call (Scala) and are handled automatically by Templates and Controllers. If you want to generate URLs yourself, for example when sending a double-opt-in e-mail, use the route’s methods url() or absoluteURL(). The latter takes a boolean parameter that can be used to force https URLs.
You create a JS router by creating an action that returns
Just as in the normal reverse router, you can call url(), absoluteURL() and websocketURL() on the route. Additionally, there is the ajax() function from jQuery, allowing you to quickly create a safe AJAX call like this:
Play’s router is only one way to map URLs to controller actions. The Global settings object has a method
onRouteRequest which is called each time an incoming request should be routed to an action. The default implementation obviously asks the Play router to resolve and call the appropriate action. If you need something more dynamic than the rather static routes file (e.g. generated from the database), this is the place to plug in your own router.
Congratulations, you have now learned the basics about routes. The next part will explain some advanced uses of routes. I will also set up a GitHub repo with all the shown code in action.