A good way to stay flexible is to write less code. --Pragmatic Programmer

By , on March 2, 2011

Development, Scala

Tags: , ,


As the web application I’m currently building will have a mobile app, a REST Interface is a nice clean way for the mobile app to connect, retrieve and update information from the website.
fortunately Liftweb makes it very easy to do this, all you need to do is extend your class with the RestHelper Trait and let liftweb know about it in your Boot.scala

Lets say we want to get a list of all books in the system when we access http://(server)/api/books/all

First we need to tell Lift we have a class that responds to requests in Boot.scala:

LiftRules.dispatch.append(BookRestApi)

then the class itself: BookRestApi.scala:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object BookRestApi extends RestHelper  {
  serve {
    // list of books
    case "api" :: "books" :: "all" :: _ XmlGet _=>
      {
         <Books>
          {
            Book.findAll.map(book => book.toXml)
          }
         </Books>
      }
    case "api" :: "books" :: "all" :: _ JsonGet _=>
      {
        JsonWrapper("books", Book.findAll.map(book => book.toJS))
      }
  }
}

The RestHelper trait parses the Url and makes the relevant parts available for Scala’s pattern matching capabilities.

for the first case statement:

  • the api :: books : all List will match if the url points to /api/books/all
  • the XmlGet method will match if the client has indicated he can accept xml by setting an accept header or by ending the url with a .xml suffix

Scala’s inline XML capabilities make it very easy, to envelop the result in a <Books> element.

the second statement does the same but then when the client requests a json response.

For the Json part I used a JsonWrapper helper method because the inline code looked totally unreadable, this still isn’t very readable but at least it has a descriptive method name now ;)

1
2
3
  def JsonWrapper(name : String, content : JValue) : JValue = {
    (name -> content)
  }

If you would call this method with the name “Books” and the content {“foo” : “bar” } it will output :

{"books" : 
  {"foo" : "bar"}
}

This shows both a positive and negative aspect to the Scala language, it makes for a very flexible and fast development, but loses a lot in readability, which makes maintenance more difficult.

Now if would want to get a single book when I specify the id of that book, I’d add the following:

1
2
3
4
5
6
7
case "api" :: "books" :: AsLong(id) :: _ XmlGet _=>
        {
          Book.findByKey(id) match {
            case Full(book) => {book.toXml}
            case (_) => NotFoundResponse("Book with id " + id + " not found\r\n")
          }
        }

This pattern will only match on /api/books/{id} and only if that Id can be converted to a Long, in which case it will try to look up a book with that id
if the book isn’t found (the Box returned from findByKey isn’t Full) it will give the text back

In the next article I’ll talk about PUT requests and authentication so we can add books through the rest interface



Comments are closed.