JavaScript Routing in Play! Framework

I was working on a Play! Framework project and I was wondering why our whole API was available in one piece. It got me thinking whether there is a way to hide at least some parts of the API from the clients. Precisely because some of the clients didn’t have the privilege/permission to execute given operations, e.g. visiting restricted pages. I thought there should be a way to hide at least the operations that are on these restricted pages. I’ve found a way to do so and below it is.

JavaScript routing in Play! framework, like a fork in the forest

Image source: Reddit.com

 

The original structure

You might know we develop with Play! framework. Usually we use the routing system provided by the framework. In several cases we have used the Twirl template engine which is a built-in feature of the framework. We also use AngularJS on the client-side. The frontend logic needed the JavaScript routing. 

We had a controller for each business object, so most of the views were served from a few huge classes with all the logic implemented in those.

The provided routing had to be made accessible to the frontend. Creating an API call is as follows:

Making it accessible from the frontend.

And in our main.scala.html:

Usage from your code

In this case you just made every url accessible from your client-side/frontend. There are a few problems with this approach:

  • Your full “API” is public.
  • You have a huge file with all the URLs, even the ones that shouldn’t be accessible. (Read this article on how to protect your API with Deadbolt.)
  • As your app grows you have to maintain a huge list in the Application.java – after a few times 50 lines it’s a real pain in the ass, trust me

 

Shrinking the length of the jsRoutes method/function

First of all we changed how we separate functionalities. Each view had its own controller with a service specified for the specific functionality and common service for the common stuff. We moved the routing to each controller, but collected it into one huge jsRoutes.js as before. In this case the Application.java has been modified.

All the Controllers had the same getRoutes() method which returns the list of the public methods used from javascript code.

Your full API is still available in one piece, but the code maintenance has been decreased drastically because you can find all your code where it belongs locally.

[Update: As Csaba Ujvári pointed out to make this work in Play 2.4+ you have to use JavaScriptReserveRoute instead of JavascriptReserveRoute]

 

Creating jsRoutes for each view

The next step is to reduce the surface of your observable interface at a time. If you prefer this case, you need a little ceremony to create your implementation. First of all you need to create a new line in your routes file for each view.

Each of your Controller file should be modified a little:

Your view should download this routing for every view but it contains only the required functionality.

You can call them like this:

As you can see (petRoutes) you can specify the “namespace” with this approach.

 

Summary

It costs you only a short ceremony to:

  • show only a small part of your API at a time,
  • improve code readability by keeping your routing close to where it’s used, being kinda isolated from the other views and controllers,
  • decrease your time spent on maintaining your code.

I hope this will prove to be a useful resource to many of my fellow backend developers! If you have any questions or related solutions, let me know! (smile)

[Editor’s note: to learn more about the author, visit our Facebook page.]

Barnabás Oláh

Barnabás Oláh

Full-stack Developer at Wanari Ltd.
Never underestimate the capabilities of a lazy person.

  • Roberto

    Dear Barnabás, is it a typo that routes.javascript.PetCtrl.getPet() in the first code block is then called by $http.get(jsRoutes.controllers.PetCtrl.getPet(petId).url)?
    Cheers!

    • Barnabás Oláh

      Hi Roberto thanks for reaching out. Please call me Barni 🙂
      It’s not a typo, you don’t need to pass any arguments it’s like you are just adding the function reference to the collection.
      If you have any questions left don’t hesitate to ask 🙂

    • Barnabás Oláh

      Hi Roberto thanks for reaching out. Please call me Barni 🙂
      It’s not a typo, you don’t need to pass any arguments it’s like you are just adding the function reference to the collection.
      If you have any questions left don’t hesitate to ask 🙂

      • Roberto

        Oh ok excellent, thank you for the tip, I’ll switch to a proper js routing now, have been procrastinating it ! 🙂

  • Roberto

    Dear Barnabás, sorry to bother you again, but I think that I need your help with one line of code. I finally got to implement the js routing, but im stuck at this part:
    routeList.add(routes.javascript.MyController.myControllerMethod());

    It says that it cannot resolve myControllerMethod… If I use intellij auto suggestion, when i type routes.javascript.MyController. it only suggests cast, field, var, par, that is it…
    What am i doing wrong?
    Thank you so much!
    Roberto

  • Roberto

    I’d like to thank @barnabsolh:disqus once more for his immediate reply and help. I deleted by post just a few after I did it because luck wanted me to finally solve my issue right after I asked for help. Anyways, here was my issue:
    Intellij would not accept this line: routeList.add(routes.javascript.MyController.myControllerMethod());
    but the issue isn’t in the code or a mismatch of versions or anything, but rather that Intellij’s defaut source_folder did not include the directory classes_managed, you can read the whole thing here: http://stackoverflow.com/questions/16676311/unable-to-resolve-reverse-routing-methods-in-intellij

    Once again, not a coding issue, just an Intellij misconfiguration. And again thank you to Barnabás for being so supportive!