Rapid prototyping with Spring Data Rest and Knockout.js

Summary: Tutorial showing how to rapidly develop a REST api in Java using Spring Data Rest, with Javascript frontend using Knockout.js. Complete with sample application (CORS enabled) and code available on Github1.

Time again to get our hands dirty. In this post I demonstrate how to create a REST based web application in Java with minimal effort. I’m going to use following frameworks:

I’m going to develop a complete application, with separate backend and frontend. As an example I chose to create a simple bookmark service. As I’m using Intellij Idea and Mac, the examples are geared towards those but it shouldn’t be too difficult to adjust to any other IDE like Eclipse.

Spring notes

If you are new to Spring, the Spring website can be a bit overwhelming in the beginning as there are dozens of projects to choose from. There are basically two ways to learn about Spring: reading a project documentation (which I always recommend to do first before going anywhere else) or starting with a guide.

  • http://spring.io/docs is the starting point for getting to the in-depth documentation about a Spring project. From there you can get to the projects main site (including “quick start” installation guide) or jump directly to the reference documentation or javadocs.

  • http://spring.io/guides collects short intro guides about a wide range of subjects. Start here if you want to quickly try out a feature, I find the guide quite helpful for getting started quickly.

If you haven’t done anything yet in Spring I’d recommend to have a look at the main documentation first: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/

Build tool: Springboot

Docs: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#cli-init

Nowadays, every stack worth it’s salt needs a proper build tool to get up to speed quickly. I’m using Spring boot for that.

Installation: installed on my Mac the Spring Boot CLI via brew.

With Spring Boot, we can create the project and already specify what modules we are going to use. To get an overview of what is available run spring init --list.

Setup

Execute:

$ spring init --build=gradle --java-version=1.8 --dependencies=data-rest,data-jpa spring-data-rest-knockout-bookmarks

Edit gradle.build

Change into the new directory. Let’s edit the Gradle build file:

$ edit gradle.build

Remove eclipse plugin and method.

As we use Intellij we can remove the eclipse plugin and method.

Add H2 database

We are going to need some means to persist our data. For demonstration purposes the H2 in memory database is fine.

In dependencies add compile("com.h2database:h2")

Note: if you want to persist the data, you can use H2 in file based mode. I have already prepared the Github project, just remove the comments in src/main/resources/application.properties and data gets persisted between sessions.

Optional: enable debugging in the Idea

While developing I prefer to be able to debug in Idea. Add following lines to the build file to enable:

applicationDefaultJvmArgs = [
    "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
]

Optional: enable hot code swapping

Docs: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-hotswapping.html#howto-reload-springloaded-gradle-and-intellij

Another small tweak that can come in handy is hot code swapping. The springloaded module can do that for us.

Add dependency it in buildscript:

buildscript {
	dependencies {
		....
		classpath("org.springframework:springloaded:${springBootVersion}")
	}
}


idea {
	module {
		inheritOutputDirs = false
		outputDir = file("$buildDir/classes/main/")
	}
}

However, keep in mind that Intellij does not automatically re-compile classes on saving. In order to make hot code swapping possible, either manually compile classes (eg ctrl+shit+F9 or make project) or enable “Make project automatically” in IntelliJ settings. In the beginning of a project, I prefer the latter. It has no difference on performance (on my 2013 Mac at least).

The finished gradle.build:

buildscript {
	ext {
		springBootVersion = '1.2.1.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
		classpath("org.springframework:springloaded:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot' 

jar {
	baseName = 'demo'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}

applicationDefaultJvmArgs = [  
	"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"  
]

idea {  
	module {  
		inheritOutputDirs = false  
		outputDir = file("$buildDir/classes/main/")  
	}
}

dependencies {
	compile("org.springframework.boot:spring-boot-starter-data-rest")
	compile("org.springframework.boot:spring-boot-starter-data-jpa")
	compile("com.h2database:h2")
	testCompile("org.springframework.boot:spring-boot-starter-test")
}

task wrapper(type: Wrapper) {
	gradleVersion = '1.12'
}

Create idea project

Finally, lets create the Idea files. Run:

$ gradle idea

Open in idea.

One last clean up step is to remove the automatically generated demo files (DemoApplication.java and DemoApplicationTest.java). Also, Idea might complain about Spring not being configured, you can add the Spring facet to the project (and later add context files, see below).

We are done with setting up the project. Let’s begin to create the backend!

How to create a REST api in Java

The Spring Data Rest module takes care of a lot of boilerplate code without becoming too heavy weight or getting in the way.

Next, I’m basically doing similar steps as in the official quick start guide: http://spring.io/guides/gs/accessing-data-rest/, have a look there as well.

Let’s start with creating first the Spring context.

Add application Spring context

The modern way of Spring configuration is Java-based via annotations (even though in some cases it makes sense to mix old-style xml configuration with annotations). Create following class:

package bookmarks;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

@Configuration
@EnableJpaRepositories
@Import(RepositoryRestMvcConfiguration.class)
@EnableAutoConfiguration
public class Application
{
	public static void main(String[] args)
	{
		SpringApplication.run(Application.class, args);
	}
}

The important detail here are the annotations of our Application class. It tells Spring to auto-configure our project as a REST application.

The domain model

As already mentioned, Spring Data Rest spares us from developing lots of boilerplate code. All we need to do is to focus on our domain model and Spring will take care of making it available via REST, including HATEOAS compatible communication (more about that later). So next we add the domain model.

Bookmark model

For demonstration purposes I’m going to develop a simple Bookmark application that has only one model class, called (guess what) Bookmark. And gosh is it simple, it has only one field called url (well ok it has two fields, the id is a mandatory identifier):

package bookmarks.domain;

import javax.persistence.*;

@Entity
public class Bookmark
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(nullable=false)
    private String url;

    public String getUrl()
    {
        return url;
    }

    public void setUrl(String url)
    {
        this.url = url;
    }
}

Not too complicated, is it? Next we need to add a Repository to tell Spring that we want to expose it as a REST resource. Create following interface along the model:

package bookmarks.domain;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource
public interface BookmarkRepository extends CrudRepository<Bookmark, Long>
{
}

That was even simpler.

And now what? That’s it! We are done and have a fully fledged REST api at our hands. It can’t get simpler than that.

Run and test

Let’s try it out. Run:

$ gradle run

and send some requests to our backend. I’m using the HTTPie2 client to send requests instead of CURL but use whatever you like.

Send a request to the root url (often called “discovery”):

$ http :8080/
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Date: Fri, 10 Oct 2014 13:29:59 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
{
	"_links": {
		"bookmarks": {
			"href": "http://localhost:8080/bookmarks"
		}, 
		"profile": {
			"href": "http://localhost:8080/alps"
		}
	}
}

Voilà, the answer is a complete REST response. The Content-Type tells us another interesting fact:

Content-Type: application/hal+json;charset=UTF-8

Spring Data Rest automatically exposes our resources in HATEOAS manner. Specifically, it is using the HAL3 format. (I give more background about HATEOAS here).

So let’s see the bookmarks then, lets do a GET on the bookmarks resource (I’m omitting the headers in the response for now):

$ http :8080/bookmarks
HTTP/1.1 200 OK
{}

Not much going on yet. Let’s create a bookmark:

$ http POST :8080/bookmarks url=news.ycombinator.com
HTTP/1.1 201 Created
Content-Length: 0
Date: Fri, 10 Oct 2014 13:57:20 GMT
Location: http://localhost:8080/bookmarks/1
Server: Apache-Coyote/1.1

Note the Location header:

Location: http://localhost:8080/bookmarks/1

Looks good. Let’s check:

$ http :8080/bookmarks
{
	"_embedded": {
		"bookmarks": [
			{
				"_links": {
					"self": {
						"href": "http://localhost:8080/bookmarks/1"
					}
				}, 
				"url": "news.ycombinator.com"
			}
		]
	}
}

$ http :8080/bookmarks/1
{
	"_links": {
		"self": {
			"href": "http://localhost:8080/bookmarks/1"
		}
	}, 
	"url": "news.ycombinator.com"
}

It works. If you feel like it, try PUT, PATCH, DELETE, HEAD and so on.

Let’s try this…

Ok, so now lets make the bookmarks a tiny bit more interesting. We add a Note property, which we can use to add notes to a bookmark and that we want to make searchable, and a created property that should be automatically set when we create a new bookmark.

Extend the domain model:

package bookmarks.domain;

import javax.persistence.*;
import java.util.Date;

@Entity
public class Bookmark
{
	...
	
    private String note;

    @Column(nullable=false)
    private Date created;

	... getter and setter omitted ...
}

Set created field before saving

To add behaviour to the default CRUD resource handling, all we have to do is to add a Handler that registers to different events. In the case of the created field we want to set it before creating a new resource (and only then). As usual in Spring, this is done via annotations (read more about events here: http://docs.spring.io/spring-data/rest/docs/2.2.1.RELEASE/reference/html/#events-chapter)

Add following file:

package bookmarks.domain;

import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;

import java.util.Date;

@RepositoryEventHandler(Bookmark.class)
public class BookmarkEventHandler
{
    @HandleBeforeCreate
    public void handleBookmarkCreate(Bookmark bookmark)
    {
        bookmark.setCreated(new Date());
    }
}

In the method with @HandleBeforeCreate annotation we set the created date of the resource before it gets saved.

All left to do is to tell our main Application configuration about this handler. Edit Application.java:

public class Application
{
... main method ...
	@Bean
	BookmarkEventHandler bookmarkEventHandler()
	{
		return new BookmarkEventHandler();
	}
}

Restart and try (adding also a note this time):

$ http POST :8080/bookmarks url=news.ycombinator.com note="this is hacker news"
HTTP/1.1 201 Created
Location: http://localhost:8080/bookmarks/1

$ http :8080/bookmarks/1
{
	"_links": {
		"self": {
			"href": "http://localhost:8080/bookmarks/1"
		}
	}, 
	"created": "2014-10-10T14:18:19.401+0000", 
	"note": "this is hacker news", 
	"url": "news.ycombinator.com"
}

Works!

Search for notes

I haven’t explained yet how the resource get exposed and why it is working out of the box. It is all happening in BookmarkRespository, by, as you probably have guessed already, extending the CrudRepository interface. If you look into that interface, you will find typical CRUD operations like save, delete, exists, findAll. Those get mapped to POST, DELETE, HEAD, GET and so on.

Knowing that, we can further extend the Repository Interface with custom queries. Spring Data Rest is a sub project of Spring Data JPA. You can find more info about how to create queries here: http://docs.spring.io/spring-data/jpa/docs/1.7.1.RELEASE/reference/html/#repositories. All queries that are added to the Repository are made available under the same name (unless otherwise configured).

Let’s extend the repository with a method that searches for the content of a note in an SQL LIKE manner:

@RepositoryRestResource
public interface BookmarkRepository extends CrudRepository<Bookmark, Long>
{
    @RestResource(path="note")
    List<Bookmark> findByNoteContaining(@Param("text")String note);
}

We want to expose it under /note endpoint with text as the search text.

Let’s have a look at the bookmarks resource:

$ http :8080/bookmarks
{
	"_links": {
		"search": {
			"href": "http://localhost:8080/bookmarks/search"
		}
	}
}

There it is, a new “search” resource for bookmarks:

$ http :8080/bookmarks/search
{
	"_links": {
		"findByNoteContaining": {
			"href": "http://localhost:8080/bookmarks/search/note{?text}", 
			"templated": true
		}
	}
}

I’d say that’s all pretty easy to grasp.

Let’s try it out:

$ http POST :8080/bookmarks url=news.ycombinator.com note="this is hacker news"
HTTP/1.1 201 Created

$ http :8080/bookmarks/search/note text=="hacker"
{
	"_embedded": {
		"bookmarks": [
			{
				"_links": {
					"self": {
						"href": "http://localhost:8080/bookmarks/1"
					}
				}, 
				"created": "2014-10-10T14:58:13.133+0000", 
				"note": "this is hacker news", 
				"url": "news.ycombinator.com"
			}
		]
	}
}

(Note the double equals sign == of HTTPie request, indicating that this is a path parameter and not a field of a JSON payload). It works. We don’t need anything else for our tiny bookmark service.

The Frontend

As we went ahead so fast I thought I add a frontend as well to make this a complete web application. I’m using Knockout.js (which fullfils my requirement to be light-weight and unobtrusive), jQuery and a bit of Bootstrap.

If you checkout the Github repo, you’ll see that the whole frontend is made out of two files: index.html and bookmark.js.

The Knockout Model and ViewModel defined in bookmark.js:

// The bookmark model
function Bookmark(selfHref, url, created, note) {
    var self = this;
    self.selfHref = selfHref;
    self.url = ko.observable(url);
    self.created = created;
    self.note = ko.observable(note);
}

// The bookmark view model
function BookmarkViewModel() {
    var self = this;

    self.newUrl = ko.observable();
    self.newNote = ko.observable();
    self.bookmarks = ko.observableArray([]);

    // add bookmark: send POST to bookmarks resource
    self.addBookmark = function () {
        // a little bit of pre-processing of user entered url and note
        var newUrl = self.newUrl();
		var newNote = self.newNote();
		...
        // make POST request
        $.ajax("http://localhost:8080/bookmarks", {
            data: '{"url": "' + newUrl + ' ", "note": "' + newNote + '"}',
            type: "post",
            contentType: "application/json",
            success: function (allData) {
                self.loadBookmarks();
                self.newUrl("");
                self.newNote("");
            }
        });
    };

    // update bookmark: send PUT to existing bookmarks resource
    self.updateBookmark = function (bookmark) {

        // same as in "addBookmark" a little bit of parameter checking. Some code duplication here
        // but we leave it for demonstration purposes
        var newUrl = bookmark.url();
        var newNote = bookmark.note();
		...
        // make PUT request (or send PATCH then we don't need to include the created date)
        $.ajax(bookmark.selfHref, {
            data: '{"url": "' + newUrl + ' ", "note": "' + newNote + '", "created": "' + bookmark.created +'"}',
            type: "put",
            contentType: "application/json",
            success: function (allData) {
                self.loadBookmarks();
            }
        });
    };

    // delete bookmark: send DELETE to bookmarks resource
    self.deleteBookmark = function (bookmark) {
        $.ajax(bookmark.selfHref, {
            type: "delete",
            success: function (allData) {
                self.loadBookmarks();
            }
        });
    };

    // load bookmarks from server: GET on bookmarks resource
    self.loadBookmarks = function () {
        $.ajax("http://localhost:8080/bookmarks", {
            type: "get",
            success: function (allData) {
                var json = ko.toJSON(allData);
                var parsed = JSON.parse(json);
                if (parsed._embedded) {
                    var parsedBookmarks = parsed._embedded.bookmarks;
                    var mappedBookmarks = $.map(parsedBookmarks, function (bookmark) {
                        return new Bookmark(bookmark._links.self.href, bookmark.url, bookmark.created, bookmark.note)
                    });
                    self.bookmarks(mappedBookmarks);
                } else {
                    self.bookmarks([]);
                }
            }
        });
    };
    // Load initial data
    self.loadBookmarks();
}
// Activates knockout.js
ko.applyBindings(new BookmarkViewModel());

It’s out of scope to explain Knockout here (have a look at their interactive tutorial, it’s quite good). The things to note besides the standard Knockout data-bindings (which are used in index.html, see next) are the jQuery calls to our backend. We are using the bookmarks resource and if you look at the Javascript Bookmark model you notice that we store the self ref of a resource:

function Bookmark(selfHref, url, created, note) {
	...
    self.selfHref = selfHref;
	...
}

With that we can comfortably access the ressource (bookmark.selfHref), e.g. in delete:

// delete bookmark: send DELETE to bookmarks resource
self.deleteBookmark = function (bookmark) {
	$.ajax(bookmark.selfHref, {
		type: "delete",
		success: function (allData) {
			self.loadBookmarks();
		}
	});
};

In index.html we add a form and table and include the Knockout bindings:

<form data-bind="submit: addBookmark" class="form-horizontal">
	<div class="form-group">
		<label for="inputUrl" class="col-sm-2 control-label">Url</label>

		<div class="col-sm-10">
			<input data-bind="value: newUrl" type="text" class="form-control" id="inputUrl" placeholder="Url">
		</div>
	</div>
	<div class="form-group">
		<label for="inputNote" class="col-sm-2 control-label">Note</label>

		<div class="col-sm-10">
			<input data-bind="value: newNote" type="text" class="form-control" id="inputNote"
				   placeholder="Note (optional)">
		</div>
	</div>
	<div class="form-group">
		<div class="col-sm-offset-2 col-sm-10">
			<button type="submit" class="btn btn-default">Add</button>
		</div>
	</div>
</form>

...

<tbody data-bind="foreach: bookmarks">
<tr>
	<td><a data-bind="attr: {href: url}">open</a></td>
	<td><input style="width: 100%" data-bind="value: url"/></td>
	<td><span data-bind="text: created"></span></td>
	<td><input style="width: 100%" data-bind="value: note"/></td>
	<td>
		<button type="button" class="btn btn-default btn-sm" data-bind="click: $root.updateBookmark">Update
		</button>
	</td>
	<td>
		<button type="button" class="btn btn-default btn-sm" data-bind="click: $root.deleteBookmark">Delete
		</button>
	</td>
</tr>
</tbody>

The resulting UI looks like this:

Bookmarks web app

The app lets you do all that is necessary: create, edit, delete and open bookmarks.

Now, serve up the file from IntelliJ Idea (open index.html and select chrome for example). Fill in an url and a note and hit “Add” …

Nothing happens? Doesn’t work?

A look into the Javascript console of Chrome gives us a hint:

CORS error

Yes indeed, we are building an actually separated client-server web application and have forgotten about this thing called CORS4. It is also out of scope to say more about it (sorry, but maybe in another post), but you can read up on it here: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing. In short, our server needs to allow cross-domain requests and I wanted to show how we can do that.

The last piece: enable CORS on the backend

CORS requires us to respond with certain headers. During our prototyping phase we are going to allow all. This can be done easily with adding a servlet Filter to our backend.

Add following file:

package bookmarks.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CORSFilter implements Filter
{
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
    {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, PATCH, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        chain.doFilter(req, res);
    }
    public void init(FilterConfig filterConfig){}
    public void destroy(){}
}

And similar to the event handler, we modify Application.java to configure Spring to use the filter. Add following method:

@Bean
public FilterRegistrationBean commonsRequestLoggingFilter()
{
	final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
	registrationBean.setFilter(new CORSFilter());
	return registrationBean;
}

Restart and the app will work. Happy bookmarking :-)

Checkout the source code on Github.

Exercise

If you feel like playing with the tutorial app: can you extend it and add a search field for notes to the UI? And then make url searchable, and after that you could add pagination and ordering (hint: you use a different repository to inherit from)…

In a follow-up post, I have extended the bookmark application with a scraping and source code extraction feature (implemented as a microservice).

Thanks for reading

I hope this post was helpful for you. If you have comments, questions or found a bug please let me know, either in the comments below or contact me directly.

Resources

Subscribe to Human Intelligence Engineering
Explorations of human ingenuity in a world of technology.