Scalajs in Production

Play framework is my choice of backend solution. But for the frontend, I can not avoid to write Javascript code. It is a disaster for me. Whenever I want to upgrade my website I will experience some break down time, only because the JavaScript is not type-safe. And as a one-man team, I do not have enough man power to test every corner cases in each release.

I’ve been looking for an alternative for a long time, until I find Scalajs.

Scalajs with Play Framework

Just like play framework, the Scalajs compiler also works as a SBT plugin. To start a Scalajs project, simply add the sbt plugin in project/plugins.sbt:

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.15")

Then enable the plugin in the configure:

enablePlugins(ScalaJSPlugin)

@vmunier created a great example showing how to integrate Play with Scala.js. The server project uses the play framework, and the client project uses the Scalajs, and the shared project defines shard libraries between the client and server. When we sbt run the play project the scalajs code is compiled and added to the Twirl templates automatically. Based on this example, we can then write Scalajs for the web browser and everything else stays the same.

Scala RPC

Regarding to the communication between the client and server, I prefer RPC protocal. Autowire developed by @lihaoyi is my choice of the RPC library. Autowire use uPickle to serialize the data to be transferred into JSON format. The default JSON library in Play Framework is Play Json (moved to seperate library since 2.6). However, Play Json can not compile in ScalaJS, because of the dependency to Jackson. So, I move all Json request to use uPickle.

Shared API interfaces

First, we define the API interface in the shared project

trait api{
  def version():String
}

API Server

In the server side, we use the following code to handle all RPC requests:

class ApiServer@Inject()(users: UserDB, items: ItemDB, orders: OrderDB, stores:StoreDB, wallets:WalletDB) extends Controller {
  def fn(fn:String) = Action.async { implicit request =>
    ApiServer.route[api](new ApiWorker(users, items, orders, stores, wallets)(users.whois(request)))(
      autowire.Core.Request(
        fn.split('.'),
        upickle.json.read(request.body.asText.get).asInstanceOf[Js.Obj].value.toMap
      )
    ).map(upickle.json.write(_)).map(Ok(_))
  }
}

object ApiServer extends autowire.Server[Js.Value, Reader, Writer]{
  def read[Result: Reader](p: Js.Value) = upickle.default.readJs[Result](p)
  def write[Result: Writer](r: Result) = upickle.default.writeJs(r)
}

API Client

In the client(Scalajs) side, do the RPC call like:

AcuteClient[api].version().call()

Then, you will get a Future[Return value]. See link for documents on how to use the future in Scalajs.