All you need to know about Play's routing DSL

Posted by Tamer M AbdulRadi on Mon, Mar 14, 2016

Play 2.4 introduced a new way to write your applications that can be appealing to developers with minimalistic preferences. Now it's straightforward to embed Play into your application, adding only the necessary dependence, and using a sexy routing DSL.

In this post, we will go through creating an embedded Play application step by step, then we will take a tour of Play's new routing DSL features, explaining different usecases, including some advanced ones. 

Programmatically start Play server

0. Start with a default SBT project

Play now supports default SBT project layout, which means more consistency with your non-Play applications.

If you don't have an existing project, create a new one including the minimal structure described above.

1. Add Dependencies to build.sbt

Starting from Play 2.4.0, a new "play-netty-server" package has been introduced, which contains just the minimal dependinces you need to start a netty server and specify your routes. 

libraryDependencies += "com.typesafe.play" %% "play-netty-server" % "2.4.6"

Other extra features of Play (like twirl templates) are packaged on it's own packages, you may add them seperately. 

2. Add resources/application.conf

Play still requires its configuration file to be named application.conf, but this time it lives under resources folder like other config files. 

Minimum configuration you must provide is play.crypto.secret, as shown below:

3. Run NettyServer

Now the interesting part, start Play server from your Main class

This will launch Nettyserver on the default host and port and give you back an instance of Nettyserver, that enables shutting down the server programmtically as well.

server.shutdown()

Meet the SIRD

SIRD stands for String Interprolating Routing DSL, we already saw a glimpse of it in the previous example

case GET(p"/posts/") =>

This is still pretty readable even for developers with no Scala background. First example will match on Http GET requests on URL "/posts"

case GET(p"/posts/$id") =>

This will match on Http GET requests on URL "/posts/something", extracting the "something" part into id.

Extracting query params looks sexy

case GET(p"/posts" q"page=$p" q”limit=$l") =>

This will extract p and l as Strings. However, if any of those params didn't exist in the URL, this route won't match

Optional params

case GET(p"/posts" q"page=$p" q_?”limit=$l") =>

q_? (or alternatively q_o) allows you to extract optional parameters. l here will be Option[String]

Extracting Sequences

Play supports extracting array params from URLs that look like "/posts?tag=tag1&tag=tag2&tag=tag3"

case GET(p"/posts/" q_*"tag=$tags") =>

This will alllow extracting every occurence of the query string "tag" into tags as a Seq[String] using q_* (or alternatively q_s)

Note that if the tag was not found in the params, the route will still match, and tags will be an empty list. 

Regex is still supported

Same regex syntax used in Play routes file is supported using SIRD as well

case GET(p"/posts/$id<[0-9]+>") =>

But No Regex for query strings

case GET(p"/posts/" ? q"id=$id<[0-9]{3}>") =>

Good news here, is you get a compile time error!

Anyway, there is a much better way to enforce validations on query strings in the next section.

Typed Extractors

case GET(p"/posts" q"page=${ int(p) }") =>

p will be available as Int, and of course won't match if "page" had letters and not digits.

Other primitives are supported as well like longfloatdouble, and bool.

case GET(p"/posts/" q_?”limit=${ int(l) }" & q_*"xs=${ int(xs) }") =>

Typed extractors shine with optional and sequence extractors! l wil be Option[Int], and xs will be Seq[Int].

case GET(p"/posts/${ int(id) }" =>

Ofcourse, path segments are also supported.

More validations?

If you need even more validations, you can combine the typed extractors with the regex syntax 

case GET(p"/posts/${ int(id) }<[0-9]{3}>" q_*"tag=$tags") =>

Also don't forget that this is normal Scala pattern matching, you can use pattern guards as well!

case GET(p"/posts/${ int(id) }<[0-9]{3}>" q_*"tag=$tags") if tags.length > 0 =>

Custom Extractors

Well, the examples above can get really hairy, especially if you have sophisticated validations requirements that are being repeated in multiple routes. You can get very wide lines that quickly become unreadable. Good news here; you can roll your own extractors that look like this:

case GET(p"/posts/${postId(id)}") =>

This is possible by writing couple of lines

postId is instance of PathBindableExtractor, which implicitly takes bindablePostId as a parameter, which will validate and extract the value into id as instance of PostId.

Note that if the parameter didn't match the PostId require, the route will not match, flowing down into the next route. No errors will be shown and interestingly the error message provided in bindablePostId won't be used! I don't know the use of the error message though, it may be that in the next releases it becomes more useful. 

Query String Groups Extractors

What if we have a group of query strings that appear together. For example let's say page and limit appear on multiple end points. We'd like to group them in a case class to keep our code DRY.

 There is a littile bit of boilerplate you have to write, but only once. Then we can use this extractor in multiple places.

You can as well combine the extractor as any normal extractor

case GET(p"/posts/" ? pagination(p) & q"show_comments=${ bool(showComments) }") =>

Composing Play routes

Imagine we have a big project with hundreds of endpoints, your routes will quickly explode into a huge file with hundreds or even thousands of lines to scroll back and forth around them. Won't it be nice if we can split our routes into multiple modules that we compose at the main class?

Let's imagine we have these two services

Routes are PartialFunctions, which can be composed together, using plain old Scala method orElse

Router.from(service1.routes orElse service2.routes)
This will result in one big routes PartialFunction, that checks first for the first routes, then fallback into the second routes. Now this simple feature shines with another method provided by Play called withPrefix that nests the routes.

service1.withPrefix("echo")

This will match URLs "echo/hello/something"
Router.from(service1.withPrefix("service").routes orElse service2.withPrefix("service2").routes)

Using the primitives above, I wrote a simple utility Router that makes it more pretty to combine you routes like:

The full source is available on gist, feel free to improve it to suit your case, and please share with us.

 

Topics: Scala, Play

Posts by Topic

see all

Subscribe to Email Updates