The Appeal of the Lift Web Framework

The extreme end of weird (as far as web frameworks go)

Lift is one of the better-known web frameworks for Scala. Version 2.5 has just been released, so it seems like a good time to show features of Lift that I particularly like.

Lift is different from other web frameworks (in fact, I labeled it at the extreme end of weird in the first presentation I gave about it), but people who get into Lift seem to love the approach it takes. It’s productive and enjoyable, which goes well with Scala.

I’ll keep this post short. Just two things:

Transforms

You might be familiar with an MVC approach to the Web, where you have code that forwards to a view, and in that view you maybe use a little bit of mark-up to loop or display values. That’s not how it goes in Lift.

Instead, you start with the view first, and use HTML5 attributes to mark the parts of the view that need transforming. Here’s an example:

<p data-lift="Poems">
  The work of <span class="author">someone</span> includes
  <span class="title">something</span>.
</p>

That’s valid HTML5. You can view it in your browser, or edit it in Adobe Fireworks, or whatever tool you want. The only part of it that looks a little strange is the data-lift attribute. What that’s doing is naming a Lift snippet, and a snippet is just a class. It might look like this:

package code.snippet

import net.liftweb.util.Helpers._

class Poems {
  def render =
    ".author *" #> "Philip Larkin" &
    ".title *" #> "This Be The Verse"
}

What’s going on here? We’re using a nifty DSL in Lift to transform the <p> tag so that it contains the content we want. We’re saying…

To render whatever template we’re given:

  • find those things with the “author” class (if any), use the * selector to focus on the content, and replace it with the text “Philip Larkin”; and
  • select the content of the title class, and replace it with the title of his most famous poem.

Things like ".author" are CSS Selectors, which you’re probably familiar with from .css files or jQuery. The right hand side of the #> method is the replacement function. (If you don’t like the #> symbol, use replaceWith instead.)

Under the hood—which you can open if you have something more complex to do—the transform takes a NodeSeq (a Scala representation of XML, a HTML <p> in our case) and returns a replacement NodeSeq. In other words, it’s a NodeSeq => NodeSeq function.

The final HTML sent to the browser would be:

<p>
  The work of <span class="author">Philip Larkin</span> includes 
  <span class="title">This Be The Verse</span>.
</p>

Two things to note from this. First, we have an encapsulated piece of functionality that doesn’t care what page it is put on.

Second, you can give the HTML to a designer, and they can work on it how they like, with whatever data they want to put on the page. When they give you the page back (or push it to your repository), there’s nothing to change. Did the designer change the content and put it in a table? Doesn’t matter, because we’re matching on the CSS class name.

Sure, you need to have some agreement on structure and CSS classes, and there are a few tricks to learn around repeated content, but this is a long way from having to rework HTML after each change.

That’s the first thing I like about Lift: it’s an addictive way of working with HTML.

REST

Lift’s RestHelper is a powerful and concise way to produce RESTful web services. At its heart, it transforms a request into a response, making use of Scala’s pattern matching, and other features. An example will help explain.

Sticking with the poetry theme, we can build a service that takes URLs like /poems/by/Larkin and returns JSON:

{
  "titles":["This Be The Verse","Aubade"]
}

The code to implement that in Lift might be this:

package code.rest

import net.liftweb.http.rest.RestHelper
import net.liftweb.json.JsonDSL._
import net.liftweb.json.JsonAST._

object PoemResource extends RestHelper {

  // The database of poem titles:
  val poems = Map(
    "Larkin" -> List("This Be The Verse", "Aubade")
  )

  // Helper to convert from titles to a JSON representation:
  def asJSON(titles: List[String]) : JValue =
    ("titles" -> titles)

  // Match on a request, return a LiftResponse:
  serve {
    case "poems" :: "by" :: author :: Nil Get request =>
      poems.get(author).map(asJSON)
  }

}

We have an embarrassingly small repertoire of poets associated to titles of their work, stored in a regular Scala Map called poems.

You need to know that JValue is the Lift way of representing JSON data, and we use this to create a function to turn titles into the JSON structure we want (in the asJSON function). The ("titles" -> titles) code (a regular Scala tuple) is, in this instance, triggering an implicit conversion in the JSON DSL to give us a JValue. If you don’t like that, you can construct JSON from more basic building blocks.

Finally, the serve block defines the pattern we want to match on. It has to be a GET request starting “poems,” followed with “by” and then some String value we’re calling author.

The right-hand side of the => is what we produce if the pattern matches. That’s going to be the list of titles from our “database,” transformed by asJSON.

Lift is figuring out that as we’re producing a JValue, it should send back the right kind of response, with the application/json mime type set. We could have been more implicit and constructed a JsonResponse instance ourselves, or even something else entirely, like an OutputStreamResponse or a RedirectResponse.

The point is that we’ve matching on a request, and returning some kind of LiftResponse. That’s what I like about this: the model is simple. At the same time, there are implicit conversions (a.k.a “magic,” “evil,” depending on your point of view) that you can make use of to get your job done; or you can be more explicit in what kind of response you send back.

There are also sensible default behaviors. If you ask our database for /poems/by/Tennyson, you’ll get a 404.

Learning About Lift

That was just two things of many. There’s great real-time support, security, and community. If you want to find out a bit more:

Lift 2.5 has just been released, and 3.0 is in the pipeline: it’s a great time to get involved.

tags: , , , , ,