Sunday, April 3, 2011

Building a Simple Scala REST API with Bowler framework

I've been working on a hobby project with the intent of learning more about Scala. The first phase of my project is to create a RESTful API that can serve multiple client types including a browser-based client, and a programmatic client of some vaguely defined nature.

I started with Lift to write the browser-based client, but eventually switched to Vaadin because I like the notion of not dealing with javascript directly.  I also like the Vaadin widgets, documentation, and samples.   On the other hand, Lift has a lot of cool features like actors and REST support that Vaadin is missing.  I may circle back and give Lift another shot eventually.

Anyway, I saw Gregg Carrier's blog post about using Scala, MongoDB, Scalatatra and Casbah, and used it as a springboard.  I ended up with Bowler framework because it magically handles JSON responses, and that's primarily what I'm interested in for the API layer.  I'm already infatuated with MongoDB.   I think that I'm going to use Salat on top of Casbah since both BowlerFramework and Salat use case classes.  Bowler maps request data to a case class, Salat writes a case class to MongoDB.  What more could you want?  We'll see...

The code that follows does something very simple.  It handles a GET request to "/headers" and returns a JSON response with the callers request headers.  Since I'm really only concerned with returning JSON, I was looking for most minimalist approach I could get. 

Project Setup

I should get familiar with SBT, but haven't got there yet.  Bowler documents a maven dependency, but I found that I needed several more to get things working.  Here are my dependencies:

    <dependencies>
        <dependency>
            <groupId>org.scalatra</groupId>
            <artifactId>scalatra-scalate_2.8.1</artifactId>
            <version>${scalatra.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.scalatra</groupId>
            <artifactId>scalatra-fileupload_2.8.1</artifactId>
            <version>${scalatra.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.recursivity</groupId>
            <artifactId>recursivity-commons_2.8.1</artifactId>
            <version>0.4.1</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.25</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.8.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.bowlerframework</groupId>
            <artifactId>core_2.8.1</artifactId>
            <version>0.3-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-compiler</artifactId>
            <version>2.8.1</version>
        </dependency>
        <dependency>
            <groupId>net.liftweb</groupId>
            <artifactId>lift-json_2.8.1</artifactId>
            <version>2.3-RC4</version>
        </dependency>
    </dependencies>

My web.xml and Bootstrap class look very similar to the example ones that the Bowler team provided.  I have changed the name of my controller to ApiController, and have updated the web.xml to point to my Bootstrap class, but those are the only differences.

Resources

Since Bowler is going to use case classes to generate responses, I've created a package and will put them all in one file.  Here it is with my single "Headers" case class:

package com.sentientswarm.propdesk.resources

case class RequestHeaders(host:String, userAgent:String, accept:String,         acceptLanguage:String ,acceptEncoding:String,acceptCharset:String,keepAlive:String, connection:String, dnt:String)
It just has parameters for each of the headers from the request.


ApiController

This class defines the routes that are handled by the API.  I have only one:  /headers.  It is (appropriately?) sensitive to trailing slashes.  I started out by defining my route as /headers/, and scratched my head for a while when I was getting errors using a URL of http://localhost:8080/headers instead of http://localhost:8080/headers/.

Here is my ApiController:



package com.sentientswarm.propdesk

import org.bowlerframework.controller.Controller
import org.bowlerframework.view.Renderable
import resources.{RequestHeaders, SandBox}
class ApiController extends Controller with Renderable {

  // responds to GET requests at /headers
  get("/headers")((request, response) => {

    val host = request.getHeader("host")
    val userAgent = request.getHeader("user-agent")
    val accept = request.getHeader("accept")
    val acceptLanguage = request.getHeader("accept-language")
    val acceptEncoding = request.getHeader("accept-encoding")
    val acceptCharset = request.getHeader("accept-charset")
    val keepAlive = request.getHeader("keep-alive")
    val connections = request.getHeader("connections")
    val dnt = request.getHeader("dnt")
    val s = RequestHeaders(host, userAgent, accept, acceptLanguage, acceptEncoding, acceptCharset, keepAlive, connections, dnt)

    render(s)
  })
}

This just instantiates the RequestHeader with the values from the HTTP request, then calls the render method from Renderable.

Views and Layouts

To be honest, this stuff is like watching a cricket match to me.  I'm sure it makes sense, but clearly I do not understand the rules.  I'll get there, but for now, I'm really glad that all I have to do is emit JSON. 

There were two files that I needed to set up to get a successful result in the end.  The first one was resources/layouts/default.mustache.  I just used the same one that was provided in the Bowler example.  I don't know much about mustache, but I love the name.  It brings to mind a funny image that matches the decorative purpose of the tool.

The other file was the view.  Since I'm only emitting JSON, I stripped that down from the example, and think I have the minimal detail necessary to not generate an error.  I named the file /resources/views/GET/headers/index.mustache.  This is where Bowler looks for the view based on the operation (GET) and the path (/headers).  The contents are:

{{#requestHeaders}}
{{/requestHeaders}}

Obviously if you want to emit HTML, you'll need to put more in here.  The case class name RequestHeaders gets mapped to the requestHeaders token in the file.

Result

That's about all it took.  When I use REST Client in my browser to send a get request to http://localhost:8080/headers with the "Accept" header set to "application/json", I get the following result:

{"host":"localhost:8080","userAgent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3","accept":"application/json","acceptLanguage":null,"acceptEncoding":"gzip,deflate","acceptCharset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7","keepAlive":"115","connection":null,"dnt":"1"}

Next Steps

My next goal is to have a simple form submit some data to a URL using POST, map the data to a case class, and write the case class to MongoDB.  I will also implement a URL to GET data from the MongoDB collection and emit it as JSON.  Wish me luck!

3 comments:

  1. Actually, if you only want to use JSON, you can entirely strip out the layout and view stuff - the JSON rendering uses a completely different ViewRenderer implementation.

    I'd be very interested in hearing on the mailing list what you thought was confusing about the layouts/views, this is an area that I'm looking to improve, and also maybe some of the improvements need only to be around the documentation of it?

    Any and all constructive feedback is useful, "even" critical feedback, as I suffer from "home blindness" being the author of the framework and may not see what others see as tricky..

    ReplyDelete
  2. BTW, I'm tending to agree in that JSON API's are becoming more important than "thick" serverside page generation: I think we'll see a bigger separation between client-side (browser resident) code talking to JSON backends in the future, something I previously wrote about at some length: http://blog.recursivity.com/post/1372343840/are-serverside-web-frameworks-becoming-irrelevant

    ReplyDelete
  3. I'll look into using a different ViewRenderer implementation to remove the excess layout and view config. I've signed up for and will cross-post to the mailing list anything that seems important. The confusing part about views and layouts is more of a personal issue than a Bowler issue. I find HTML, JSP, CSS, and all that front-end stuff generally confusing. I probably need to raise the priority of getting proficient in these things if I'm going to be working with them, but it's definitely one of the reasons I chose to use Vaadin over Lift for my project.

    ReplyDelete

Note: Only a member of this blog may post a comment.