Channels ▼
RSS

Parallel

Building Web Apps with Lift: Lazy Loading, Push, and Parallel Execution


Creating a REST Service with Lift

Lift provides helpers that make it easy to create a REST service with just a few lines of code. I'll provide a simple example that doesn't consider security issues. However, the required authentication checking can be added with just a few additional lines of code. This sample REST service involves games: It must list all the games and a specific required game by its ID. The restapi/game/list URI must display all the games, and if there is a long parameter with an ID such as restapi/game/list/1, the service must find this game by ID and return the results.

The following listing shows the code for a new GameInformation.scala file that defines the GameInformation case class and a Singleton object with the same name. Notice that the code is included in the code.model package, which is the default package for models based on the rules I defined in Boot.scala in the previous article. You must place the file in src/main/scala. The GameInformation object defines games as a List of three GameInformation instances to include sample data to make it easy to test the REST service.

The find method returns the a Box[GameInformation] with a GameInformation instance when one element in the games List matches the ID received as a parameter.

package code
package model

import net.liftweb._
import util._
import Helpers._
import common._

case class GameInformation(id: Long, name: String, highestScore: Long)

object GameInformation {
    var games = List(
      new GameInformation(1, "Invaders 2013", 7200), 
      new GameInformation(2, "Candy Crush", 32500), 
      new GameInformation(3, "Angry Birds", 155500))
  
  def find(id: Long): Box[GameInformation] = synchronized {
	  games.find(_.id == id)
  }
}

Now that I have the necessary model, I can use it in a Singleton object named GameRest, which holds the code to process and respond to the API calls. GameRest extends RestHelper and makes use of Scala's pattern matching to match incoming HTTP requests, extract the required values as part of the pattern matching process, and return the results with the necessary conversions. Notice that the code is included in the code.lib package, which is the default package for a REST API in Lift. You must place the file in src/main/scala.

package code
package lib

import model._
import net.liftweb._
import net.liftweb.http._
import net.liftweb.http.rest._
import net.liftweb.util._
import common._
import http._
import rest._
import json._
import scala.xml._
import util._
import Helpers._
import js.JsCmds._

object GameRest extends RestHelper {
	serve {
		case "restapi" :: "game" :: "list" :: Nil Get _
			=> anyToJValue(listAllGames)
			
		case "restapi" :: "game" :: "list" :: AsLong(id) :: Nil Get _
			=> anyToJValue(listGame(id))
		}
		
	def listAllGames(): List[GameInformation] = {
		GameInformation.games.map { 
			  game => GameInformation(
			    game.id,
				game.name, 
				game.highestScore) 
		}
	}
		
	def listGame(id: Long): Box[GameInformation] = {
			for {
			  game <- GameInformation.find(id) ?~ "The requested game doesn't exist"
			} yield GameInformation(
						game.id,
						game.name, 
						game.highestScore)
	}
}

When Lift receives an HTTP request, it tests a PartialFunction[net.liftweb.http.Req, () => Box[net.liftweb.http.LiftResponse] to check whether it is defined. If Lift finds a match, it takes the resulting function and applies it to get a Box[net.liftweb.http.LiftResponse], then returns it when it is full.

The serve method defines the request handlers:

  • restapi/game/list with no additional values calls the listAllGames method.
  • restapi/game/list/{id}, where {id} is a long value with the desired id calls the listGame method with the received id as a parameter. For example, restapi/game/list/2 calls listGame(2).

The anyToJValue method is a helper method that creates JSON from case classes. The definition of the request handlers and its pattern matching is easy to understand and maintain, as shown in the following lines:

serve {
	case "restapi" :: "game" :: "list" :: Nil Get _
		=> anyToJValue(listAllGames)
		
	case "restapi" :: "game" :: "list" :: AsLong(id) :: Nil Get _
		=> anyToJValue(listGame(id))
	}

Finally, it is necessary to make Lift include the GameRest object in the request processing pipeline. Thus, you must make a small change in Boot.scala. Just add the following import:

  import code.lib. ...

Then, add the following lines within the Boot.boot method:

  // Make Lift include the GameRest object in the request processing pipeline
LiftRules.dispatch.append(GameRest)

As you might notice, it isn't necessary to list the REST URLs in Lift's SiteMap. The simple REST API is ready to process requests. The following request, http://localhost:8080/restapi/game/list, generates the response shown in the listing with all the games retrieved by the listAllGames method:

[{
  "id":1,
  "name":"Invaders 2013",
  "highestScore":7200
},{
  "id":2,
  "name":"Candy Crush",
  "highestScore":32500
},{
  "id":3,
  "name":"Angry Birds",
  "highestScore":155500
}]

The following request, http://localhost:8080/restapi/game/list/2, results in a call to the listGame method with 2 as the id value. The following lines show the result:

{
  "value":{
    "id":2,
    "name":"Candy Crush",
    "highestScore":32500
  }
}

The following request, http://localhost:8080/restapi/game/list/4, results in a call to the listGame method with 4 as the id value. There is no game that matches this idvalue; the following lines show the result:

{
  "msg":"The requested game doesn't exist",
  "exception":{
    
  },
  "chain":{
    
  }
}

Conclusion

Lift has a very active community and it is worth taking a look at the modules it has written that provide extra features such as PayPal and Open ID integration. Before you start working on any integration or additional feature, check the information for the modules listed on https://www.assembla.com/spaces/liftweb/wiki/Modules.

The Lift framework takes advantage of many helpful features in the Scala programming language, and the code is easy to read and maintain. Once you make the shift from MVC to Lift's ViewFirst model or after you work with a few REST APIs based on Lift, it is difficult to go back to MVC frameworks without missing the wonders of dozens of Lift's features.


Gaston Hillar is a frequent contributor to Dr. Dobb's.

Related Article

Building Web Applications with Lift


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video