React.js and Spring Data REST

This tutorial shows a collection of apps that use Spring Data REST and its powerful backend functionality, combined with React’s sophisticated features to build an easy-to-understand UI.

  • Spring Data REST provides a fast way to build hypermedia-powered repositories.

  • React is Facebook’s solution to efficient, fast, and easy-to-use views in JavaScript.

Part 1 — Basic Features

Welcome, Spring community.

This section shows how to get a bare-bones Spring Data REST application up and running quickly. Then it shows how to build a simple UI on top of it by using Facebook’s React.js toolset.

Step 0 — Setting up Your Environment

Feel free to grab the code from this repository and follow along.

If you want to do it yourself, visit https://start.spring.io and pick the following dependencies:

  • Rest Repositories

  • Thymeleaf

  • JPA

  • H2

This demo uses Java 8, Maven Project, and the latest stable release of Spring Boot. It also uses React.js coded in ES6. This will give you a clean, empty project. From there, you can add the various files shown explicitly in this section and/or borrow from the repository listed earlier.

In the Beginning…​

In the beginning, there was data. And it was good. But then people wanted to access the data through various means. Over the years, people cobbled together lots of MVC controllers, many using Spring’s powerful REST support. But doing over and over cost a lot of time.

Spring Data REST addresses how simple this problem can be if some assumptions are made:

  • The developer uses a Spring Data project that supports the repository model.

  • The system uses well accepted, industry standard protocols, such as HTTP verbs, standardized media types, and IANA-approved link names.

Declaring your domain

Domain objects form the cornerstone of any Spring Data REST-based application. In this section, you will build an application to track the employees for a company. Kick that off by creating a data type, as follows:

Example 1. src/main/java/com/greglturnquist/payroll/Employee.java
@Entity (1)
public class Employee {

	private @Id @GeneratedValue Long id; (2)
	private String firstName;
	private String lastName;
	private String description;

	private Employee() {}

	public Employee(String firstName, String lastName, String description) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.description = description;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Employee employee = (Employee) o;
		return Objects.equals(id, employee.id) &&
			Objects.equals(firstName, employee.firstName) &&
			Objects.equals(lastName, employee.lastName) &&
			Objects.equals(description, employee.description);
	}

	@Override
	public int hashCode() {

		return Objects.hash(id, firstName, lastName, description);
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "Employee{" +
			"id=" + id +
			", firstName='" + firstName + '\'' +
			", lastName='" + lastName + '\'' +
			", description='" + description + '\'' +
			'}';
	}
}
1 @Entity is a JPA annotation that denotes the whole class for storage in a relational table.
2 @Id and @GeneratedValue are JPA annotations to note the primary key and that is generated automatically when needed.

This entity is used to track employee information — in this case, their names and job descriptions.

Spring Data REST is not confined to JPA. It supports many NoSQL data stores, though you will not see those in this tutorial. For more information, see Accessing Neo4j Data with REST, Accessing JPA Data with REST, and Accessing MongoDB Data with REST.

Defining the Repository

Another key piece of a Spring Data REST application is a corresponding repository definition, as follows:

Example 2. src/main/java/com/greglturnquist/payroll/EmployeeRepository.java
public interface EmployeeRepository extends CrudRepository<Employee, Long> { (1)

}
1 The repository extends Spring Data Commons' CrudRepository and plugs in the type of the domain object and its primary key

That is all that is needed! In fact, you need not even annotate interface if it is top-level and visible. If you use your IDE and open up CrudRepository, you will find a collection of pre-defined methods.

You can define your own repository if you wish. Spring Data REST supports that as well.

Pre-loading the Demo

To work with this application, you need to pre-load it with some data, as follows:

Example 3. src/main/java/com/greglturnquist/payroll/DatabaseLoader.java
@Component (1)
public class DatabaseLoader implements CommandLineRunner { (2)

	private final EmployeeRepository repository;

	@Autowired (3)
	public DatabaseLoader(EmployeeRepository repository) {
		this.repository = repository;
	}

	@Override
	public void run(String... strings) throws Exception { (4)
		this.repository.save(new Employee("Frodo", "Baggins", "ring bearer"));
	}
}
1 This class is marked with Spring’s @Component annotation so that it is automatically picked up by @SpringBootApplication.
2 It implements Spring Boot’s CommandLineRunner so that it gets run after all the beans are created and registered.
3 It uses constructor injection and autowiring to get Spring Data’s automatically created EmployeeRepository.
4 The run() method is invoked with command line arguments, loading up your data.

One of the biggest, most powerful features of Spring Data is its ability to write JPA queries for you. This not only cuts down on your development time, but it also reduces the risk of bugs and errors. Spring Data looks at the name of methods in a repository class and figures out the operations you need, including saving, deleting, and finding.

That is how we can write an empty interface and inherit already built save, find, and delete operations.

Adjusting the Root URI

By default, Spring Data REST hosts a root collection of links at /. Because you will host a web UI on that path, you need to change the root URI, as follows:

Example 4. src/main/resources/application.properties
spring.data.rest.base-path=/api

Launching the Backend

The last step needed to get a fully operational REST API off the ground is to write a public static void main method by using Spring Boot, as follows:

Example 5. src/main/java/com/greglturnquist/payroll/ReactAndSpringDataRestApplication.java
@SpringBootApplication
public class ReactAndSpringDataRestApplication {

	public static void main(String[] args) {
		SpringApplication.run(ReactAndSpringDataRestApplication.class, args);
	}
}

Assuming the previous class as well as your Maven build file were generated from https://start.spring.io, you can now launch it either by running that main() method inside your IDE or by typing ./mvnw spring-boot:run on the command line. (mvnw.bat for Windows users).

If you are not up-to-date on Spring Boot and how it works, you should watch one of Josh Long’s introductory presentations. Did it? Press on!

Touring Your REST Service

With the application running, you can check things out on the command line by using cURL (or any other tool you like). The following command (shown with its output) lists the links in the application:

$ curl localhost:8080/api
{
  "_links" : {
    "employees" : {
      "href" : "http://localhost:8080/api/employees"
    },
    "profile" : {
      "href" : "http://localhost:8080/api/profile"
    }
  }
}

When you ping the root node, you get back a collection of links wrapped in a HAL-formatted JSON document.

  • _links is the collection of available links.

  • employees points to an aggregate root for the employee objects defined by the EmployeeRepository interface.

  • profile is an IANA-standard relation and points to discoverable metadata about the entire service. We explore this in a later section.

You can further dig into this service by navigating the employees link. The following command (shown with its output) does so:

$ curl localhost:8080/api/employees
{
  "_embedded" : {
    "employees" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "description" : "ring bearer",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/employees/1"
        }
      }
    } ]
  }
}

At this stage, you are viewing the entire collection of employees.

Along with the data you pre-loaded earlier, a _links attribute with a self link is included. This is the canonical link for that particular employee. What is canonical? It means “free of context”. For example, the same user could be fetched through /api/orders/1/processor, in which the employee is associated with processing a particular order. Here, there is no relationship to other entities.

Links are a critical facet of REST. They provide the power to navigate to related items. It makes it possible for other parties to navigate around your API without having to rewrite things every time there is a change. Updates in the client is a common problem when the clients hard-code paths to resources. Restructuring resources can cause big upheavals in code. If links are used and the navigation route is maintained, it becomes easy and flexible to make such adjustments.

You can decide to view that one employee if you wish. The following command (shown with its output) does so:

$ curl localhost:8080/api/employees/1
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "description" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/employees/1"
    }
  }
}

Little has changed here, except that there is no need for the _embedded wrapper since there is only the domain object.

That is all well and good, but you are probably itching to create some new entries. The following command (shown with its output) does so:

$ curl -X POST localhost:8080/api/employees -d "{\"firstName\": \"Bilbo\", \"lastName\": \"Baggins\", \"description\": \"burglar\"}" -H "Content-Type:application/json"
{
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "description" : "burglar",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/employees/2"
    }
  }
}

You can also PUT, PATCH, and DELETE, as shown in this related guide. For now, though, we will move on to building a slick UI.

Setting up a Custom UI Controller

Spring Boot makes it super simple to stand up a custom web page. First, you need a Spring MVC controller, as follows:

Example 6. src/main/java/com/greglturnquist/payroll/HomeController.java
@Controller (1)
public class HomeController {

	@RequestMapping(value = "/") (2)
	public String index() {
		return "index"; (3)
	}

}
1 @Controller marks this class as a Spring MVC controller.
2 @RequestMapping flags the index() method to support the / route.
3 It returns index as the name of the template, which Spring Boot’s autoconfigured view resolver will map to src/main/resources/templates/index.html.

Defining an HTML template

You are using Thymeleaf, although you will not really use many of its features. To get started you need an index page, as follows:

Example 7. src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8"/>
    <title>ReactJS + Spring Data REST</title>
    <link rel="stylesheet" href="/main.css" />
</head>
<body>

    <div id="react"></div>

    <script src="built/bundle.js"></script>

</body>
</html>

The key part in this template is the <div id="react"></div> component in the middle. It is where you will direct React to plug in the rendered output.

You may also wonder where that bundle.js file came from. The way it is built is shown in the next section.

This tutorial does not show main.css, but you can see it linked up above. When it comes to CSS, Spring Boot will automatically serve anything found in src/main/resources/static. Put your own main.css file there. It is not shown in the tutorial, since our focus is on React and Spring Data REST, not CSS.

Loading JavaScript Modules

This section contains the barebones information to get the JavaScript bits off the ground. While you can install all of JavaScripts command line tools, you need not do so — at least, not yet. Instead, all you need to do is add the following to your pom.xml build file:

Example 8. The frontend-maven-plugin used to build JavaScript bits
<plugin>
	<groupId>com.github.eirslett</groupId>
	<artifactId>frontend-maven-plugin</artifactId>
</plugin>

This little plugin perform multiple steps:

  • The install-node-and-npm command will install node.js and its package management tool, npm, into the target folder. (This ensures the binaries are NOT pulled under source control and can be cleaned out with clean).

  • The npm command will execute the npm binary with the provided argument (install). This installs the modules defined in package.json.

  • The webpack command will execute webpack binary, which compiles all the JavaScript code based on webpack.config.js.

These steps are run in sequence, essentially installing node.js, downloading JavaScript modules, and building the JS bits.

What modules are installed? JavaScript developers typically use npm to build up a package.json file, such as the following:

Example 9. package.json
{
  "name": "spring-data-rest-and-reactjs",
  "version": "0.1.0",
  "description": "Demo of ReactJS + Spring Data REST",
  "repository": {
    "type": "git",
    "url": "[email protected]:spring-guides/tut-react-and-spring-data-rest.git"
  },
  "keywords": [
    "rest",
    "hateoas",
    "spring",
    "data",
    "react"
  ],
  "author": "Greg L. Turnquist",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/spring-guides/tut-react-and-spring-data-rest/issues"
  },
  "homepage": "https://github.com/spring-guides/tut-react-and-spring-data-rest",
  "dependencies": {
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "rest": "^1.3.1"
  },
  "scripts": {
    "watch": "webpack --watch -d --output ./target/classes/static/built/bundle.js"
  },
  "devDependencies": {
    "@babel/core": "^7.1.0",
    "@babel/preset-env": "^7.1.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.2",
    "webpack": "^4.19.1",
    "webpack-cli": "^3.1.0"
  }
}

Key dependencies include:

  • react.js: The toolkit used by this tutorial

  • rest.js: CujoJS toolkit used to make REST calls

  • webpack: A toolkit used to compile JavaScript components into a single, loadable bundle

  • babel: To write your JavaScript code using ES6 and compile it into ES5 to run in the browser

To build the JavaScript code you’ll use later, you need to define a build file for webpack, as follows:

Example 10. webpack.config.js
var path = require('path');

module.exports = {
    entry: './src/main/js/app.js',
    devtool: 'sourcemaps',
    cache: true,
    mode: 'development',
    output: {
        path: __dirname,
        filename: './src/main/resources/static/built/bundle.js'
    },
    module: {
        rules: [
            {
                test: path.join(__dirname, '.'),
                exclude: /(node_modules)/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: ["@babel/preset-env", "@babel/preset-react"]
                    }
                }]
            }
        ]
    }
};

This webpack configuration file:

  • Defines the entry point as ./src/main/js/app.js. In essence, app.js (a module you will write shortly) is the proverbial public static void main() of our JavaScript application. webpack must know this in order to know what to launch when the final bundle is loaded by the browser.

  • Creates sourcemaps so that, when you are debugging JS code in the browser, you can link back to original source code.

  • Compile ALL of the JavaScript bits into ./src/main/resources/static/built/bundle.js, which is a JavaScript equivalent to a Spring Boot uber JAR. All your custom code AND the modules pulled in by the require() calls are stuffed into this file.

  • It hooks into the babel engine, using both es2015 and react presets, in order to compile ES6 React code into a format able to be run in any standard browser.

For more details on how each of these JavaScript tools operates, please read their corresponding reference docs.

Want to see your JavaScript changes automatically? Run npm run-script watch to put webpack into watch mode. It will regenerate bundle.js as you edit the source.

With all that in place, you can focus on the React bits, which are fetched after the DOM is loaded. It is broken down into parts, as follows:

Since you are using webpack to assemble things, go ahead and fetch the modules you need:

Example 11. src/main/js/app.js
const React = require('react'); (1)
const ReactDOM = require('react-dom'); (2)
const client = require('./client'); (3)
1 React is one of the main libraries from Facebook used to build this app.
2 ReactDOM is the package that serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package.
3 client is custom code that configures rest.js to include support for HAL, URI Templates, and other things. It also sets the default Accept request header to application/hal+json. You can read the code here.
The code for client is not shown because what you use to make REST calls is not important. Feel free to check the source, but the point is, you can plugin Restangular (or anything you like), and the concepts still apply.

Diving into React

React is based on defining components. Often, one component can hold multiple instances of another in a parent-child relationship. This concept can extend to several layers.

To start things off, it is very handy to have a top level container for all components. (This will become more evident as you expand upon the code throughout this series.) Right now, you only have the employee list. But you might need some other related components later on, so start with the following:

Example 12. src/main/js/app.js - App component
class App extends React.Component { (1)

	constructor(props) {
		super(props);
		this.state = {employees: []};
	}

	componentDidMount() { (2)
		client({method: 'GET', path: '/api/employees'}).done(response => {
			this.setState({employees: response.entity._embedded.employees});
		});
	}

	render() { (3)
		return (
			<EmployeeList employees={this.state.employees}/>
		)
	}
}
1 class App extends React.Component{…​} is the method that creates a React component.
2 componentDidMount is the API invoked after React renders a component in the DOM.
3 render is the API that “draws” the component on the screen.
In React, uppercase is the convention for naming components.

In the App component, an array of employees is fetched from the Spring Data REST backend and stored in this component’s state data.

React components have two types of data: state and properties.

State is data that the component is expected to handle itself. It is also data that can fluctuate and change. To read the state, you use this.state. To update it, you use this.setState(). Every time this.setState() is called, React updates the state, calculates a diff between the previous state and the new state, and injects a set of changes to the DOM on the page. This results in fast and efficient updates to your UI.

The common convention is to initialize state with all your attributes empty in the constructor. Then you look up data from the server by using componentDidMount and populating your attributes. From there on, updates can be driven by user action or other events.

Properties encompass data that is passed into the component. Properties do NOT change but are instead fixed values. To set them, you assign them to attributes when creating a new component, as you will soon see.

JavaScript does not lock down data structures as other languages do. You can try to subvert properties by assigning values, but this does not work with React’s differential engine and should be avoided.

In this code, the function loads data through client, a Promise-compliant instance of rest.js. When it is done retrieving from /api/employees, it then invokes the function inside done() and sets the state based on its HAL document (response.entity._embedded.employees). See the structure of curl /api/employees earlier and see how it maps onto this structure.

When the state is updated, the render() function is invoked by the framework. The employee state data is included in the creation of the <EmployeeList /> React component as an input parameter.

The following listing shows the definition for an EmployeeList:

Example 13. src/main/js/app.js - EmployeeList component
class EmployeeList extends React.Component{
	render() {
		const employees = this.props.employees.map(employee =>
			<Employee key={employee._links.self.href} employee={employee}/>
		);
		return (
			<table>
				<tbody>
					<tr>
						<th>First Name</th>
						<th>Last Name</th>
						<th>Description</th>
					</tr>
					{employees}
				</tbody>
			</table>
		)
	}
}

Using JavaScript’s map function, this.props.employees is transformed from an array of employee records into an array of <Element /> React components (which you will see a little later).

Consider the following listing:

<Employee key={employee._links.self.href} data={employee} />

The preceding listing creates a new React component (note the uppercase format) with two properties: key and data. These are supplied with values from employee._links.self.href and employee.

Whenever you work with Spring Data REST, the self link is the key for a given resource. React needs a unique identifier for child nodes, and _links.self.href is perfect.

Finally, you return an HTML table wrapped around the array of employees built with mapping, as follows:

<table>
    <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Description</th>
    </tr>
    {employees}
</table>

This simple layout of state, properties, and HTML shows how React lets you declaratively create a simple and easy-to-understand component.

Does this code contain both HTML and JavaScript? Yes, this is JSX. There is no requirement to use it. React can be written using pure JavaScript, but the JSX syntax is quite terse. Thanks to rapid work on the Babel.js, the transpiler provides both JSX and ES6 support all at once.

JSX also includes bits and pieces of ES6. The one used in this code is the arrow function. It avoids creating a nested function() with its own scoped this and avoids needing a self variable.

Worried about mixing logic with your structure? React’s APIs encourage nice, declarative structure combined with state and properties. Instead of mixing a bunch of unrelated JavaScript and HTML, React encourages building simple components with small bits of related state and properties that work well together. It lets you look at a single component and understand the design. Then they are easy to combine together for bigger structures.

Next, you need to actually define what an <Employee /> is, as follows:

Example 14. src/main/js/app.js - Employee component
class Employee extends React.Component{
	render() {
		return (
			<tr>
				<td>{this.props.employee.firstName}</td>
				<td>{this.props.employee.lastName}</td>
				<td>{this.props.employee.description}</td>
			</tr>
		)
	}
}

This component is very simple. It has a single HTML table row wrapped around the employee’s three properties. The property itself is this.props.employee. Notice how passing in a JavaScript object makes it easy to pass along data fetched from the server.

Because this component does not manage any state nor deal with user input, there is nothing else to do. This might tempt you to cram it into the <EmployeeList /> up above. Do not do it! Splitting your app up into small components that each do one job will make it easier to build up functionality in the future.

The last step is to render the whole thing, as follows:

Example 15. src/main/js/app.js - rendering code
ReactDOM.render(
	<App />,
	document.getElementById('react')
)

React.render() accepts two arguments: a React component you defined as well as a DOM node to inject it into. Remember how you saw the <div id="react"></div> item earlier from the HTML page? This is where it gets picked up and plugged in.

With all this in place, re-run the application (./mvnw spring-boot:run) and visit http://localhost:8080. The following image shows the updated application:

basic 1

You can see the initial employee loaded up by the system.

Remember using cURL to create new entries? Do that again with the following command:

curl -X POST localhost:8080/api/employees -d "{\"firstName\": \"Bilbo\", \"lastName\": \"Baggins\", \"description\": \"burglar\"}" -H "Content-Type:application/json"

Refresh the browser, and you should see the new entry:

basic 2

Now you can see both of them listed on the web site.

Review

In this section:

  • You defined a domain object and a corresponding repository.

  • You let Spring Data REST export it with full-blown hypermedia controls.

  • You created two simple React components in a parent-child relationship.

  • You fetched server data and rendered them in as a simple, static HTML structure.

Issues?

  • The web page was not dynamic. You had to refresh the browser to fetch new records.

  • The web page did not use any hypermedia controls or metadata. Instead, it was hardcoded to fetch data from /api/employees.

  • It is read only. While you can alter records using cURL, the web page offers no interactivity.

We address these shortcomings in the next section.

Part 2 - Hypermedia Controls

In the previous section, you found out how to create a backend payroll service to store employee data by using Spring Data REST. A key feature it lacked was using the hypermedia controls and navigation by links. Instead, it hard-coded the path to find data.

Feel free to grab the code from this repository and follow along. This section is based on the previous section’s application, with extra things added.

In the Beginning, There Was Data…​and Then There Was REST

I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Today’s example is the SocialSite REST API. That is RPC. It screams RPC…​.What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed?
— Roy T. Fielding
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

So, what exactly ARE hypermedia controls (that is, hypertext) and how can you use them? To find out, we take a step back and look at the core mission of REST.

The concept of REST was to borrow ideas that made the web so successful and apply them to APIs. Despite the web’s vast size, dynamic nature, and low rate at which clients (that is, browsers) are updated, the web is an amazing success. Roy Fielding sought to use some of its constraints and features and see if that would afford similar expansion of API production and consumption.

One of the constraints is to limit the number of verbs. For REST, the primary ones are GET, POST, PUT, DELETE, and PATCH. There are others, but we will not get into them here.

  • GET: Fetches the state of a resource without altering the system

  • POST: Creates a new resource without saying where

  • PUT: Replaces an existing resource, overwriting whatever else (if anything) is already there

  • DELETE: Removes an existing resource

  • PATCH: Alters an existing resource (partially rather than creating a new resource)

These are standardized HTTP verbs with well known specifications. By picking up and using already coined HTTP operations, we need not invent a new language and educate the industry.

Another constraint of REST is to use media types to define the format of data. Instead of everyone writing their own dialect for the exchange of information, it would be prudent to develop some media types. One of the most popular ones to be accepted is HAL, media type application/hal+json. It is Spring Data REST’s default media type. A key value is that there is no centralized, single media type for REST. Instead, people can develop media types and plug them in and try them out. As different needs become available, the industry can flexibly move.

A key feature of REST is to include links to relevant resources. For example, if you were looking at an order, a RESTful API would include a link to the related customer, links to the catalog of items, and perhaps a link to the store from which the order was placed. In this section, you will introduce paging and see how to also use navigational paging links.

Turning on Paging from the Backend

To get underway with using frontend hypermedia controls, you need to turn on some extra controls. Spring Data REST provides paging support. To use it, tweak the repository definition as follows:

Example 16. src/main/java/com/greglturnquist/payroll/EmployeeRepository.java
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

Your interface now extends PagingAndSortingRepository, which adds extra options to set page size and adds navigational links to hop from page to page. The rest of the backend is the same (except for some extra pre-loaded data to make things interesting).

Restart the application (./mvnw spring-boot:run) and see how it works. Then run the following command (shown with its output) to see the paging in action:

$ curl "localhost:8080/api/employees?size=2"
{
  "_links" : {
    "first" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "http://localhost:8080/api/employees"
    },
    "next" : {
      "href" : "http://localhost:8080/api/employees?page=1&size=2"
    },
    "last" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    }
  },
  "_embedded" : {
    "employees" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "description" : "ring bearer",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/employees/1"
        }
      }
    }, {
      "firstName" : "Bilbo",
      "lastName" : "Baggins",
      "description" : "burglar",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/employees/2"
        }
      }
    } ]
  },
  "page" : {
    "size" : 2,
    "totalElements" : 6,
    "totalPages" : 3,
    "number" : 0
  }
}

The default page size is 20, but we do not have that much data. So, to see it in action, we set ?size=2. As expected, only two employees are listed. In addition, there are also first, next, and last links. There is also the self link, which is free of context, including page parameters.

If you navigate to the next link, you’ll see a prev link as well. The following command (shown with its output) does so:

$ curl "http://localhost:8080/api/employees?page=1&size=2"
{
  "_links" : {
    "first" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "prev" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "http://localhost:8080/api/employees"
    },
    "next" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    },
    "last" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    }
  },
...
When using & in URL query parameters, the command line thinks it is a line break. Wrap the whole URL with quotation marks to avoid that problem.

That looks neat, but it will be even better when you update the frontend to take advantage of it.

Navigating by Relationship

No more changes are needed on the backend to start using the hypermedia controls Spring Data REST provides out of the box. You can switch to working on the frontend. (That is part of the beauty of Spring Data REST: No messy controller updates!)

It is important to point out that this application is not “Spring Data REST-specific.” Instead, it uses HAL, URI Templates, and other standards. That is why using rest.js is a snap: That library comes with HAL support.

In the previous section, you hardcoded the path to /api/employees. Instead, the ONLY path you should hardcode is the root, as follows

...
var root = '/api';
...

With a handy little follow() function, you can now start from the root and navigate to where you want, as follows:

componentDidMount() {
	this.loadFromServer(this.state.pageSize);
}

In the previous section, the loading was done directly inside componentDidMount(). In this section, we are making it possible to reload the entire list of employees when the page size is updated. To do so, we have moved things into loadFromServer(), as follows:

loadFromServer(pageSize) {
	follow(client, root, [
		{rel: 'employees', params: {size: pageSize}}]
	).then(employeeCollection => {
		return client({
			method: 'GET',
			path: employeeCollection.entity._links.profile.href,
			headers: {'Accept': 'application/schema+json'}
		}).then(schema => {
			this.schema = schema.entity;
			return employeeCollection;
		});
	}).done(employeeCollection => {
		this.setState({
			employees: employeeCollection.entity._embedded.employees,
			attributes: Object.keys(this.schema.properties),
			pageSize: pageSize,
			links: employeeCollection.entity._links});
	});
}

loadFromServer is very similar to the previous section. However, it uses follow():

  • The first argument to the follow() function is the client object used to make REST calls.

  • The second argument is the root URI to start from.

  • The third argument is an array of relationships to navigate along. Each one can be a string or an object.

The array of relationships can be as simple as ["employees"], meaning when the first call is made, look in _links for the relationship (or rel) named employees. Find its href and navigate to it. If there is another relationship in the array, repeat the process.

Sometimes, a rel by itself is not enough. In this fragment of code, it also plugs in a query parameter of ?size=<pageSize>. There are other options that can be supplied, as you will see later.

Grabbing JSON Schema Metadata

After navigating to employees with the size-based query, the employeeCollection is available. In the previous section, we displayed that data inside <EmployeeList />. In this section, you are performing another call to grab some JSON Schema metadata found at /api/profile/employees/.

You can see the data yourself by running the following curl command (shown with its output):

$ curl http://localhost:8080/api/profile/employees -H "Accept:application/schema+json"
{
  "title" : "Employee",
  "properties" : {
    "firstName" : {
      "title" : "First name",
      "readOnly" : false,
      "type" : "string"
    },
    "lastName" : {
      "title" : "Last name",
      "readOnly" : false,
      "type" : "string"
    },
    "description" : {
      "title" : "Description",
      "readOnly" : false,
      "type" : "string"
    }
  },
  "definitions" : { },
  "type" : "object",
  "$schema" : "https://json-schema.org/draft-04/schema#"
}
The default form of metadata at /profile/employees is ALPS. In this case, though, you are using content negotiation to fetch JSON Schema.

By capturing this information in the`<App />` component’s state, you can make good use of it later when building input forms.

Creating New Records

Equipped with this metadata, you can now add some extra controls to the UI. You can start by creating a new React component <CreateDialog />, as follows:

class CreateDialog extends React.Component {

	constructor(props) {
		super(props);
		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleSubmit(e) {
		e.preventDefault();
		const newEmployee = {};
		this.props.attributes.forEach(attribute => {
			newEmployee[attribute] = ReactDOM.findDOMNode(this.refs[attribute]).value.trim();
		});
		this.props.onCreate(newEmployee);

		// clear out the dialog's inputs
		this.props.attributes.forEach(attribute => {
			ReactDOM.findDOMNode(this.refs[attribute]).value = '';
		});

		// Navigate away from the dialog to hide it.
		window.location = "#";
	}

	render() {
		const inputs = this.props.attributes.map(attribute =>
			<p key={attribute}>
				<input type="text" placeholder={attribute} ref={attribute} className="field"/>
			</p>
		);

		return (
			<div>
				<a href="#createEmployee">Create</a>

				<div id="createEmployee" className="modalDialog">
					<div>
						<a href="#" title="Close" className="close">X</a>

						<h2>Create new employee</h2>

						<form>
							{inputs}
							<button onClick={this.handleSubmit}>Create</button>
						</form>
					</div>
				</div>
			</div>
		)
	}

}

This new component has both a handleSubmit() function and the expected render() function.

We dig into these functions in reverse order, looking first at the render() function.

Rendering

Your code maps over the JSON Schema data found in the attributes property and converts it into an array of <p><input></p> elements.

  • key is again needed by React to distinguish between multiple child nodes.

  • It is a simple text-based entry field.

  • placeholder lets us show the user with field is which.

  • You may be used to having a name attribute, but it is not necessary. With React, ref is the mechanism for grabbing a particular DOM node (as you will soon see).

This represents the dynamic nature of the component, driven by loading data from the server.

Inside this component’s top-level <div> is an anchor tag and another <div>. The anchor tag is the button to open the dialog. And the nested <div> is the hidden dialog itself. In this example, you are using pure HTML5 and CSS3. No JavaScript at all! You can see the CSS code used to show and hide the dialog. We will not dive into that here.

Nestled inside <div id="createEmployee"> is a form where your dynamic list of input fields are injected followed by the Create button. That button has an onClick={this.handleSubmit} event handler. This is the React way of registering an event handler.

React does not create event handlers on every DOM element. Instead, it has a much more performant and sophisticated solution. You need not manage that infrastructure and can instead focus on writing functional code.

Handling User Input

The handleSubmit() function first stops the event from bubbling further up the hierarchy. It then uses the same JSON Schema attribute property to find each <input>, by using React.findDOMNode(this.refs[attribute]).

this.refs is a way to reach out and grab a particular React component by name. Note that you are getting ONLY the virtual DOM component. To grab the actual DOM element, you need to use React.findDOMNode().

After iterating over every input and building up the newEmployee object, we invoke a callback to onCreate() for the new employee record. This function is inside App.onCreate and was provided to this React component as another property. Look at how that top-level function operates:

onCreate(newEmployee) {
	follow(client, root, ['employees']).then(employeeCollection => {
		return client({
			method: 'POST',
			path: employeeCollection.entity._links.self.href,
			entity: newEmployee,
			headers: {'Content-Type': 'application/json'}
		})
	}).then(response => {
		return follow(client, root, [
			{rel: 'employees', params: {'size': this.state.pageSize}}]);
	}).done(response => {
		if (typeof response.entity._links.last !== "undefined") {
			this.onNavigate(response.entity._links.last.href);
		} else {
			this.onNavigate(response.entity._links.self.href);
		}
	});
}

Once again, we use the follow() function to navigate to the employees resource where POST operations are performed. In this case, there was no need to apply any parameters, so the string-based array of rel instance is fine. In this situation, the POST call is returned. This allows the next then() clause to handle processing the outcome of the POST.

New records are typically added to the end of the dataset. Since you are looking at a certain page, it is logical to expect the new employee record to not be on the current page. To handle this, you need to fetch a new batch of data with the same page size applied. That promise is returned for the final clause inside done().

Since the user probably wants to see the newly created employee, you can then use the hypermedia controls and navigate to the last entry.

First time using a promise-based API? Promises are a way to kick off asynchronous operations and then register a function to respond when the task is done. Promises are designed to be chained together to avoid “callback hell”. Look at the following flow:

when.promise(async_func_call())
	.then(function(results) {
		/* process the outcome of async_func_call */
	})
	.then(function(more_results) {
		/* process the previous then() return value */
	})
	.done(function(yet_more) {
		/* process the previous then() and wrap things up */
	});

For more details, check out this tutorial on promises.

The secret thing to remember with promises is that then() functions need to return something, whether it is a value or another promise. done() functions do NOT return anything, and you do not chain anything after one. In case you have not yet noticed, client (which is an instance of rest from rest.js) and the follow function return promises.

Paging Through Data

You have set up paging on the backend and have already starting taking advantage of it when creating new employees.

In the previous section, you used the page controls to jump to the last page. It would be really handy to dynamically apply it to the UI and let the user navigate as desired. Adjusting the controls dynamically, based on available navigation links, would be great.

First, let’s check out the onNavigate() function you used:

onNavigate(navUri) {
	client({method: 'GET', path: navUri}).done(employeeCollection => {
		this.setState({
			employees: employeeCollection.entity._embedded.employees,
			attributes: this.state.attributes,
			pageSize: this.state.pageSize,
			links: employeeCollection.entity._links
		});
	});
}

This is defined at the top, inside App.onNavigate. Again, this is to allow managing the state of the UI in the top component. After passing onNavigate() down to the <EmployeeList /> React component, the following handlers are coded up to handle clicking on some buttons:

handleNavFirst(e){
	e.preventDefault();
	this.props.onNavigate(this.props.links.first.href);
}

handleNavPrev(e) {
	e.preventDefault();
	this.props.onNavigate(this.props.links.prev.href);
}

handleNavNext(e) {
	e.preventDefault();
	this.props.onNavigate(this.props.links.next.href);
}

handleNavLast(e) {
	e.preventDefault();
	this.props.onNavigate(this.props.links.last.href);
}

Each of these functions intercepts the default event and stops it from bubbling up. Then it invokes the onNavigate() function with the proper hypermedia link.

Now you can conditionally display the controls based on which links appear in the hypermedia links in EmployeeList.render:

render() {
	const employees = this.props.employees.map(employee =>
		<Employee key={employee._links.self.href} employee={employee} onDelete={this.props.onDelete}/>
	);

	const navLinks = [];
	if ("first" in this.props.links) {
		navLinks.push(<button key="first" onClick={this.handleNavFirst}>&lt;&lt;</button>);
	}
	if ("prev" in this.props.links) {
		navLinks.push(<button key="prev" onClick={this.handleNavPrev}>&lt;</button>);
	}
	if ("next" in this.props.links) {
		navLinks.push(<button key="next" onClick={this.handleNavNext}>&gt;</button>);
	}
	if ("last" in this.props.links) {
		navLinks.push(<button key="last" onClick={this.handleNavLast}>&gt;&gt;</button>);
	}

	return (
		<div>
			<input ref="pageSize" defaultValue={this.props.pageSize} onInput={this.handleInput}/>
			<table>
				<tbody>
					<tr>
						<th>First Name</th>
						<th>Last Name</th>
						<th>Description</th>
						<th></th>
					</tr>
					{employees}
				</tbody>
			</table>
			<div>
				{navLinks}
			</div>
		</div>
	)
}

As in the previous section, it still transforms this.props.employees into an array of <Element /> components. Then it builds up an array of navLinks as an array of HTML buttons.

Because React is based on XML, you cannot put < inside the <button> element. You must instead use the encoded version.

Then you can see {navLinks} inserted towards the bottom of the returned HTML.

Deleting Existing Records

Deleting entries is much easier. All you need to do is get its HAL-based record and apply DELETE to its self link:

class Employee extends React.Component {

	constructor(props) {
		super(props);
		this.handleDelete = this.handleDelete.bind(this);
	}

	handleDelete() {
		this.props.onDelete(this.props.employee);
	}

	render() {
		return (
			<tr>
				<td>{this.props.employee.firstName}</td>
				<td>{this.props.employee.lastName}</td>
				<td>{this.props.employee.description}</td>
				<td>
					<button onClick={this.handleDelete}>Delete</button>
				</td>
			</tr>
		)
	}
}

This updated version of the Employee component shows an extra entry (a delete button) at the end of the row. It is registered to invoke this.handleDelete when clicked. The handleDelete() function can then invoke the callback passed down while supplying the contextually important this.props.employee record.

This again shows that it is easiest to manage state in the top component, in one place. This might not always be the case. However, oftentimes, managing state in one place makes it easier to keep things straight and simpler. By invoking the callback with component-specific details (this.props.onDelete(this.props.employee)), it is very easy to orchestrate behavior between components.

By tracing the onDelete() function back to the top at App.onDelete, you can see how it operates:

onDelete(employee) {
	client({method: 'DELETE', path: employee._links.self.href}).done(response => {
		this.loadFromServer(this.state.pageSize);
	});
}

The behavior to apply after deleting a record with a page-based UI is a bit tricky. In this case, it reloads the whole data from the server, applying the same page size. Then it shows the first page.

If you are deleting the last record on the last page, it will jump to the first page.

Adjusting the Page Size

One way to see how hypermedia really shines is to update the page size. Spring Data REST fluidly updates the navigational links based on the page size.

There is an HTML element at the top of ElementList.render: <input ref="pageSize" defaultValue={this.props.pageSize} onInput={this.handleInput}/>.

  • ref="pageSize" makes it easy to grab that element with this.refs.pageSize.

  • defaultValue initializes it with the state’s pageSize.

  • onInput registers a handler, as shown below:

    handleInput(e) {
    	e.preventDefault();
    	const pageSize = ReactDOM.findDOMNode(this.refs.pageSize).value;
    	if (/^[0-9]+$/.test(pageSize)) {
    		this.props.updatePageSize(pageSize);
    	} else {
    		ReactDOM.findDOMNode(this.refs.pageSize).value =
    			pageSize.substring(0, pageSize.length - 1);
    	}
    }

It stops the event from bubbling up. Then it uses the ref attribute of the <input> to find the DOM node and extract its value, all through React’s findDOMNode() helper function. It tests whether the input is really a number by checking if it is a string of digits. If so, it invokes the callback, sending the new page size to the App React component. If not, the character just entered is stripped off the input.

What does App do when it gets a updatePageSize()? Check it out:

updatePageSize(pageSize) {
	if (pageSize !== this.state.pageSize) {
		this.loadFromServer(pageSize);
	}
}

Because a new page size value causes changes to all the navigation links, it is best to refetch the data and start from the beginning.

Putting It All Together

With all these nice additions, you now have a really vamped up UI, as the following image shows:

hypermedia 1

You can see the page size setting at the top, the delete buttons on each row, and the navigational buttons at the bottom. The navigational buttons illustrate a powerful feature of hypermedia controls.

In the following image, you can see the CreateDialog with the metadata plugged into the HTML input placeholders:

hypermedia 2

This really shows the power of using hypermedia coupled with domain-driven metadata (JSON Schema). The web page does not have to know which field is which. Instead, the user can see it and know how to use it. If you added another field to the Employee domain object, this pop-up would automatically display it.

Review

In this section:

  • You turned on Spring Data REST’s paging feature.

  • You threw out hardcoded URI paths and started using the root URI combined with relationship names or “rels”.

  • You updated the UI to dynamically use page-based hypermedia controls.

  • You added the ability to create & delete employees and update the UI as needed.

  • You made it possible to change the page size and have the UI flexibly respond.

Issues?

You made the webpage dynamic. But open another browser tab and point it at the same app. Changes in one tab do not update anything in the other.

We address that issue in the next section.

Part 3 - Conditional Operations

In the previous section, you found out how to turn on Spring Data REST’s hypermedia controls, have the UI navigate by paging, and dynamically resize based on changing the page size. You added the ability to create and delete employees and have the pages adjust. But no solution is complete without taking into consideration updates made by other users on the same bit of data you are currently editing.

Feel free to grab the code from this repository and follow along. This section is based on the previous section but with extra features added.

To PUT or Not to PUT? That is the Question.

When you fetch a resource, the risk is that it might go stale if someone else updates it. To deal with this, Spring Data REST integrates two technologies: versioning of resources and ETags.

By versioning resources on the backend and using ETags in the frontend, it is possible to conditionally PUT a change. In other words, you can detect whether a resource has changed and prevent a PUT (or a PATCH) from stomping on someone else’s update.

Versioning REST Resources

To support versioning of resources, define a version attribute for your domain objects that need this type of protection. The following listing shows how to do so for the Employee object:

Example 17. src/main/java/com/greglturnquist/payroll/Employee.java
@Entity
public class Employee {

	private @Id @GeneratedValue Long id;
	private String firstName;
	private String lastName;
	private String description;

	private @Version @JsonIgnore Long version;

	private Employee() {}

	public Employee(String firstName, String lastName, String description) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.description = description;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Employee employee = (Employee) o;
		return Objects.equals(id, employee.id) &&
			Objects.equals(firstName, employee.firstName) &&
			Objects.equals(lastName, employee.lastName) &&
			Objects.equals(description, employee.description) &&
			Objects.equals(version, employee.version);
	}

	@Override
	public int hashCode() {

		return Objects.hash(id, firstName, lastName, description, version);
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Long getVersion() {
		return version;
	}

	public void setVersion(Long version) {
		this.version = version;
	}

	@Override
	public String toString() {
		return "Employee{" +
			"id=" + id +
			", firstName='" + firstName + '\'' +
			", lastName='" + lastName + '\'' +
			", description='" + description + '\'' +
			", version=" + version +
			'}';
	}
}
  • The version field is annotated with javax.persistence.Version. It causes a value to be automatically stored and updated every time a row is inserted and updated.

When fetching an individual resource (not a collection resource), Spring Data REST automatically adds an ETag response header with the value of this field.

Fetching Individual Resources and Their Headers

In the previous section, you used the collection resource to gather data and populate the UI’s HTML table. With Spring Data REST, the _embedded data set is considered a preview of data. While useful for glancing at data, to get headers like ETags, you need to fetch each resource individually.

In this version, loadFromServer is updated to fetch the collection. Then you can use the URIs to retrieve each individual resource:

Example 18. src/main/js/app.js - Fetching each resource
loadFromServer(pageSize) {
	follow(client, root, [ (1)
		{rel: 'employees', params: {size: pageSize}}]
	).then(employeeCollection => { (2)
		return client({
			method: 'GET',
			path: employeeCollection.entity._links.profile.href,
			headers: {'Accept': 'application/schema+json'}
		}).then(schema => {
			this.schema = schema.entity;
			this.links = employeeCollection.entity._links;
			return employeeCollection;
		});
	}).then(employeeCollection => { (3)
		return employeeCollection.entity._embedded.employees.map(employee =>
				client({
					method: 'GET',
					path: employee._links.self.href
				})
		);
	}).then(employeePromises => { (4)
		return when.all(employeePromises);
	}).done(employees => { (5)
		this.setState({
			employees: employees,
			attributes: Object.keys(this.schema.properties),
			pageSize: pageSize,
			links: this.links
		});
	});
}
1 The follow() function goes to the employees collection resource.
2 The first then(employeeCollection ⇒ …​) clause creates a call to fetch JSON Schema data. This has an inner then clause to store the metadata and navigational links in the <App/> component.

Notice that this embedded promise returns the employeeCollection. That way, the collection can be passed onto the next call, letting you grab the metadata along the way.

3 The second then(employeeCollection ⇒ …​) clause converts the collection of employees into an array of GET promises to fetch each individual resource. This is what you need to fetch an ETag header for each employee.
4 The then(employeePromises ⇒ …​) clause takes the array of GET promises and merges them into a single promise with when.all(), which is resolved when all the GET promises are resolved.
5 loadFromServer wraps up with done(employees ⇒ …​) where the UI state is updated using this amalgamation of data.

This chain is implemented in other places as well. For example, onNavigate() (which is used to jump to different pages) has been updated to fetch individual resources. Since it is mostly the same as what is shown here, it has been left out of this section.

Updating Existing Resources

In this section, you are adding an UpdateDialog React component to edit existing employee records:

Example 19. src/main/js/app.js - UpdateDialog component
class UpdateDialog extends React.Component {

	constructor(props) {
		super(props);
		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleSubmit(e) {
		e.preventDefault();
		const updatedEmployee = {};
		this.props.attributes.forEach(attribute => {
			updatedEmployee[attribute] = ReactDOM.findDOMNode(this.refs[attribute]).value.trim();
		});
		this.props.onUpdate(this.props.employee, updatedEmployee);
		window.location = "#";
	}

	render() {
		const inputs = this.props.attributes.map(attribute =>
			<p key={this.props.employee.entity[attribute]}>
				<input type="text" placeholder={attribute}
					   defaultValue={this.props.employee.entity[attribute]}
					   ref={attribute} className="field"/>
			</p>
		);

		const dialogId = "updateEmployee-" + this.props.employee.entity._links.self.href;

		return (
			<div key={this.props.employee.entity._links.self.href}>
				<a href={"#" + dialogId}>Update</a>
				<div id={dialogId} className="modalDialog">
					<div>
						<a href="#" title="Close" className="close">X</a>

						<h2>Update an employee</h2>

						<form>
							{inputs}
							<button onClick={this.handleSubmit}>Update</button>
						</form>
					</div>
				</div>
			</div>
		)
	}

};

This new component has both a handleSubmit() function and the expected render() function, similar to the <CreateDialog /> component.

We dig into these functions in reverse order and first look at the render() function.

Rendering

This component uses the same CSS/HTML tactics to show and hide the dialog as the <CreateDialog /> from the previous section.

It converts the array of JSON Schema attributes into an array of HTML inputs, wrapped in paragraph elements for styling. This is also the same as the <CreateDialog /> with one difference: The fields are loaded with this.props.employee. In the CreateDialog component, the fields are empty.

The id field is built differently. There is only one CreateDialog link on the entire UI, but a separate UpdateDialog link for every row displayed. Hence, the id field is based on the self link’s URI. This is used in the <div> element’s React key, the HTML anchor tag, and the hidden pop-up.

Handling User Input

The submit button is linked to the component’s handleSubmit() function. This handily uses React.findDOMNode() to extract the details of the pop-up by using React refs.

After the input values are extracted and loaded into the updatedEmployee object, the top-level onUpdate() method is invoked. This continues React’s style of one-way binding where the functions to call are pushed from upper-level components into the lower-level ones. This way, state is still managed at the top.

Conditional PUT

So you have gone to all this effort to embed versioning in the data model. Spring Data REST has served up that value as an ETag response header. Here is where you get to put it to good use:

Example 20. src/main/js/app.js - onUpdate function
onUpdate(employee, updatedEmployee) {
	client({
		method: 'PUT',
		path: employee.entity._links.self.href,
		entity: updatedEmployee,
		headers: {
			'Content-Type': 'application/json',
			'If-Match': employee.headers.Etag
		}
	}).done(response => {
		this.loadFromServer(this.state.pageSize);
	}, response => {
		if (response.status.code === 412) {
			alert('DENIED: Unable to update ' +
				employee.entity._links.self.href + '. Your copy is stale.');
		}
	});
}

A PUT with an If-Match request header causes Spring Data REST to check the value against the current version. If the incoming If-Match value does not match the data store’s version value, Spring Data REST will fail with an HTTP 412 Precondition Failed.

The specification for Promises/A+ actually defines their API as then(successFunction, errorFunction). So far, you have seen it used only with success functions. In the preceding code fragment, there are two functions. The success function invokes loadFromServer, while the error function displays a browser alert about the stale data.

Putting It All Together

With your UpdateDialog React component defined and nicely linked to the top-level onUpdate function, the last step is to wire it into the existing layout of components.

The CreateDialog created in the previous section was put at the top of the EmployeeList because there is only one instance. However, UpdateDialog is tied directly to specific employees. So you can see it plugged in below in the Employee React component:

Example 21. src/main/js/app.js - Employee with UpdateDialog
class Employee extends React.Component {

	constructor(props) {
		super(props);
		this.handleDelete = this.handleDelete.bind(this);
	}

	handleDelete() {
		this.props.onDelete(this.props.employee);
	}

	render() {
		return (
			<tr>
				<td>{this.props.employee.entity.firstName}</td>
				<td>{this.props.employee.entity.lastName}</td>
				<td>{this.props.employee.entity.description}</td>
				<td>
					<UpdateDialog employee={this.props.employee}
								  attributes={this.props.attributes}
								  onUpdate={this.props.onUpdate}/>
				</td>
				<td>
					<button onClick={this.handleDelete}>Delete</button>
				</td>
			</tr>
		)
	}
}

In this section, you switch from using the collection resource to using individual resources. The fields for an employee record are now found at this.props.employee.entity. It gives us access to this.props.employee.headers, where we can find ETags.

There are other headers supported by Spring Data REST (such as Last-Modified) that are not part of this series. So structuring your data this way is handy.

The structure of .entity and .headers is only pertinent when using rest.js as the REST library of choice. If you use a different library, you will have to adapt as necessary.

Seeing Things in Action

To see the modified application work:

  1. Start the application by running ./mvnw spring-boot:run.

  2. Open a browser tab and navigate to http://localhost:8080.

    You should see a page similar to the following image:

    conditional 1
  3. Pull up the edit dialog for Frodo.

  4. Open another tab in your browser and pull up the same record.

  5. Make a change to the record in the first tab.

  6. Try to make a change in the second tab.

    You should see the browser tabs change, as the following images show

    conditional 2
conditional 3

With these modifications, you have increased data integrity by avoiding collisions.

Review

In this section:

  • You configured your domain model with a @Version field for JPA-based optimistic locking.

  • You adjusted the frontend to fetch individual resources.

  • You plugged the ETag header from an individual resource into an If-Match request header to make PUTs conditional.

  • You coded a new UpdateDialog for each employee shown on the list.

With this plugged in, itis easy to avoid colliding with other users or overwriting their edits.

Issues?

It is certainly nice to know when you are editing a bad record. But is it best to wait until you click "Submit" to find out?

The logic to fetch resources is very similar in both loadFromServer and onNavigate. Do you see ways to avoid duplicate code?

You put the JSON Schema metadata to good use in building up the CreateDialog and the UpdateDialog inputs. Do you see other places to use the metadata to makes things more generic? Imagine you wanted to add five more fields to Employee.java. What would it take to update the UI?

Part 4 - Events

In the previous section, you introduced conditional updates to avoid collisions with other users when editing the same data. You also learned how to version data on the backend with optimistic locking. You got a notification if someone edited the same record so you could refresh the page and get the update.

That is good. But do you know what is even better? Having the UI dynamically respond when other people update the resources.

In this section, you will learn how to use Spring Data REST’s built in event system to detect changes in the backend and publish updates to ALL users through Spring’s WebSocket support. Then you will be able to dynamically adjust clients as the data updates.

Feel free to grab the code from this repository and follow along. This section is based on the previous section’s application, with extra things added.

Adding Spring WebSocket Support to the Project

Before getting underway, you need to add a dependency to your project’s pom.xml file:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

This dependency brings in Spring Boot’s WebSocket starter.

Configuring WebSockets with Spring

Spring comes with powerful WebSocket support. One thing to recognize is that a WebSocket is a very low-level protocol. It does little more than offer the means to transmit data between client and server. The recommendation is to use a sub-protocol (STOMP for this section) to actually encode data and routes.

The following code configures WebSocket support on the server side:

@Component
@EnableWebSocketMessageBroker (1)
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer { (2)

	static final String MESSAGE_PREFIX = "/topic"; (3)

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) { (4)
		registry.addEndpoint("/payroll").withSockJS();
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) { (5)
		registry.enableSimpleBroker(MESSAGE_PREFIX);
		registry.setApplicationDestinationPrefixes("/app");
	}
}
1 @EnableWebSocketMessageBroker turns on WebSocket support.
2 WebSocketMessageBrokerConfigurer provides a convenient base class to configure basic features.
3 MESSAGE_PREFIX is the prefix you will prepend to every message’s route.
4 registerStompEndpoints() is used to configure the endpoint on the backend for clients and server to link (/payroll).
5 configureMessageBroker() is used to configure the broker used to relay messages between server and client.

With this configuration, you can now tap into Spring Data REST events and publish them over a WebSocket.

Subscribing to Spring Data REST Events

Spring Data REST generates several application events based on actions occurring on the repositories. The following code shows how to subscribe to some of these events:

@Component
@RepositoryEventHandler(Employee.class) (1)
public class EventHandler {

	private final SimpMessagingTemplate websocket; (2)

	private final EntityLinks entityLinks;

	@Autowired
	public EventHandler(SimpMessagingTemplate websocket, EntityLinks entityLinks) {
		this.websocket = websocket;
		this.entityLinks = entityLinks;
	}

	@HandleAfterCreate (3)
	public void newEmployee(Employee employee) {
		this.websocket.convertAndSend(
				MESSAGE_PREFIX + "/newEmployee", getPath(employee));
	}

	@HandleAfterDelete (3)
	public void deleteEmployee(Employee employee) {
		this.websocket.convertAndSend(
				MESSAGE_PREFIX + "/deleteEmployee", getPath(employee));
	}

	@HandleAfterSave (3)
	public void updateEmployee(Employee employee) {
		this.websocket.convertAndSend(
				MESSAGE_PREFIX + "/updateEmployee", getPath(employee));
	}

	/**
	 * Take an {@link Employee} and get the URI using Spring Data REST's {@link EntityLinks}.
	 *
	 * @param employee
	 */
	private String getPath(Employee employee) {
		return this.entityLinks.linkForItemResource(employee.getClass(),
				employee.getId()).toUri().getPath();
	}

}
1 @RepositoryEventHandler(Employee.class) flags this class to trap events based on employees.
2 SimpMessagingTemplate and EntityLinks are autowired from the application context.
3 The @HandleXYZ annotations flag the methods that need to listen to events. These methods must be public.

Each of these handler methods invokes SimpMessagingTemplate.convertAndSend() to transmit a message over the WebSocket. This is a pub-sub approach so that one message is relayed to every attached consumer.

The route of each message is different, allowing multiple messages to be sent to distinct receivers on the client while needing only one open WebSocket — a resource-efficient approach.

getPath() uses Spring Data REST’s EntityLinks to look up the path for a given class type and id. To serve the client’s needs, this Link object is converted to a Java URI with its path extracted.

EntityLinks comes with several utility methods to programmatically find the paths of various resources, whether single or for collections.

In essence, you are listening for create, update, and delete events, and, after they are completed, sending notice of them to all clients. You can also intercept such operations BEFORE they happen, and perhaps log them, block them for some reason, or decorate the domain objects with extra information. (In the next section, we will see a handy use for this.)

Configuring a JavaScript WebSocket

The next step is to write some client-side code to consume WebSocket events. The following chunk in the main application pulls in a module:

var stompClient = require('./websocket-listener')

That module is shown below:

'use strict';

const SockJS = require('sockjs-client'); (1)
require('stompjs'); (2)

function register(registrations) {
	const socket = SockJS('/payroll'); (3)
	const stompClient = Stomp.over(socket);
	stompClient.connect({}, function(frame) {
		registrations.forEach(function (registration) { (4)
			stompClient.subscribe(registration.route, registration.callback);
		});
	});
}

module.exports.register = register;
1 Pull in the SockJS JavaScript library for talking over WebSockets.
2 Pull in the stomp-websocket JavaScript library to use the STOMP sub-protocol.
3 Point the WebSocket at the application’s /payroll endpoint.
4 Iterate over the array of registrations supplied so that each can subscribe for callback as messages arrive.

Each registration entry has a route and a callback. In the next section, you can see how to register event handlers.

Registering for WebSocket Events

In React, a component’s componentDidMount() function gets called after it has been rendered in the DOM. That is also the right time to register for WebSocket events, because the component is now online and ready for business. The following code does so:

componentDidMount() {
	this.loadFromServer(this.state.pageSize);
	stompClient.register([
		{route: '/topic/newEmployee', callback: this.refreshAndGoToLastPage},
		{route: '/topic/updateEmployee', callback: this.refreshCurrentPage},
		{route: '/topic/deleteEmployee', callback: this.refreshCurrentPage}
	]);
}

The first line is the same as before, where all the employees are fetched from the server using page size. The second line shows an array of JavaScript objects being registered for WebSocket events, each with a route and a callback.

When a new employee is created, the behavior is to refresh the data set and then use the paging links to navigate to the last page. Why refresh the data before navigating to the end? It is possible that adding a new record causes a new page to get created. While it is possible to calculate if this will happen, it subverts the point of hypermedia. Instead of cobbling together customized page counts, it is better to use existing links and only go down that road if there is a performance-driving reason to do so.

When an employee is updated or deleted, the behavior is to refresh the current page. When you update a record, it impacts the page your are viewing. When you delete a record on the current page, a record from the next page will get pulled into the current one — hence the need to also refresh the current page.

There is no requirement for these WebSocket messages to start with /topic. It is a common convention that indicates pub-sub semantics.

In the next section, you can see the actual operations to perform these operations.

Reacting to WebSocket Events and Updating the UI State

The following chunk of code contains the two callbacks used to update UI state when a WebSocket event is received:

refreshAndGoToLastPage(message) {
	follow(client, root, [{
		rel: 'employees',
		params: {size: this.state.pageSize}
	}]).done(response => {
		if (response.entity._links.last !== undefined) {
			this.onNavigate(response.entity._links.last.href);
		} else {
			this.onNavigate(response.entity._links.self.href);
		}
	})
}

refreshCurrentPage(message) {
	follow(client, root, [{
		rel: 'employees',
		params: {
			size: this.state.pageSize,
			page: this.state.page.number
		}
	}]).then(employeeCollection => {
		this.links = employeeCollection.entity._links;
		this.page = employeeCollection.entity.page;

		return employeeCollection.entity._embedded.employees.map(employee => {
			return client({
				method: 'GET',
				path: employee._links.self.href
			})
		});
	}).then(employeePromises => {
		return when.all(employeePromises);
	}).then(employees => {
		this.setState({
			page: this.page,
			employees: employees,
			attributes: Object.keys(this.schema.properties),
			pageSize: this.state.pageSize,
			links: this.links
		});
	});
}

refreshAndGoToLastPage() uses the familiar follow() function to navigate to the employees link with the size parameter applied, plugging in this.state.pageSize. When the response is received, you then invoke the same onNavigate() function from the last section and jump to the last page, the one where the new record will be found.

refreshCurrentPage() also uses the follow() function but applies this.state.pageSize to size and this.state.page.number to page. This fetches the same page you are currently looking at and updates the state accordingly.

This behavior tells every client to refresh their current page when an update or delete message is sent. It is possible that their current page may have nothing to do with the current event. However, it can be tricky to figure that out. What if the record that was deleted was on page two and you are looking at page three? Every entry would change. But is this desired behavior at all? Maybe. Maybe not.

Moving State Management Out of the Local Updates

Before you finish this section, there is something to recognize. You just added a new way for the state in the UI to get updated: when a WebSocket message arrives. But the old way to update the state is still there.

To simplify your code’s management of state, remove the old way. In other words, submit your POST, PUT, and DELETE calls, but do not use their results to update the UI’s state. Instead, wait for the WebSocket event to circle back and then do the update.

The follow chunk of code shows the same onCreate() function as the previous section, only simplified:

onCreate(newEmployee) {
	follow(client, root, ['employees']).done(response => {
		client({
			method: 'POST',
			path: response.entity._links.self.href,
			entity: newEmployee,
			headers: {'Content-Type': 'application/json'}
		})
	})
}

Here, the follow() function is used to get to the employees link, and then the POST operation is applied. Notice how client({method: 'GET' …​}) has no then() or done(), as before? The event handler to listen for updates is now found in refreshAndGoToLastPage(), which you just looked at.

Putting It All Together

With all these modifications in place, fire up the application (./mvnw spring-boot:run) and poke around with it. Open up two browser tabs and resize so you can see them both. Start making updates in one and see how they instantly update the other tab. Open up your phone and visit the same page. Find a friend and ask that person to do the same thing. You might find this type of dynamic updating more keen.

Want a challenge? Try the exercise from the previous section where you open the same record in two different browser tabs. Try to update it in one and NOT see it update in the other. If it is possible, the conditional PUT code should still protect you. But it may be trickier to pull that off!

Review

In this section, you:

  • Configured Spring’s WebSocket support with a SockJS fallback.

  • Subscribed for create, update, and delete events from Spring Data REST to dynamically update the UI.

  • Published the URI of affected REST resources along with a contextual message ("/topic/newEmployee", "/topic/updateEmployee", and so on).

  • Registered WebSocket listeners in the UI to listen for these events.

  • Wired the listeners to handlers to update the UI state.

With all these features, it is easy to run two browsers, side-by-side, and see how updating one ripples to the other.

Issues?

While multiple displays nicely update, polishing the precise behavior is warranted. For example, creating a new user will cause ALL users to jump to the end. Any thoughts on how this should be handled?

Paging is useful, but it offers a tricky state to manage. The costs are low on this sample application, and React is very efficient at updating the DOM without causing lots of flickering in the UI. But with a more complex application, not all of these approaches will fit.

When designing with paging in mind, you have to decide what is the expected behavior between clients and if there needs to be updates or not. Depending on your requirements and performance of the system, the existing navigational hypermedia may be sufficient.

Part 5 - Securing the UI and the API

In the previous section, you made the app dynamically response to updates from other users with Spring Data REST’s built-in event handlers and the Spring Framework’s WebSocket support. However, no application is complete without securing the whole thing so that only proper users have access to the UI and the resources behind it.

Feel free to grab the code from this repository and follow along. This section is based on the previous section’s app with extra things added.

Adding Spring Security to the Project

Before getting underway, you need to add a couple dependencies to your project’s pom.xml file:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

This bring in Spring Boot’s Spring Security starter as well as some extra Thymeleaf tags to do security lookups in the web page.

Defining the Security Model

In the past section, you have worked with a nice payroll system. It is handy to declare things on the backend and let Spring Data REST do the heavy lifting. The next step is to model a system where security controls need to be instituted.

If this is a payroll system, then only managers would be accessing it. So kick things off by modeling a Manager object:

@Entity
public class Manager {

	public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(); (1)

	private @Id @GeneratedValue Long id; (2)

	private String name; (2)

	private @JsonIgnore String password; (2)

	private String[] roles; (2)

	public void setPassword(String password) { (3)
		this.password = PASSWORD_ENCODER.encode(password);
	}

	protected Manager() {}

	public Manager(String name, String password, String... roles) {

		this.name = name;
		this.setPassword(password);
		this.roles = roles;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Manager manager = (Manager) o;
		return Objects.equals(id, manager.id) &&
			Objects.equals(name, manager.name) &&
			Objects.equals(password, manager.password) &&
			Arrays.equals(roles, manager.roles);
	}

	@Override
	public int hashCode() {

		int result = Objects.hash(id, name, password);
		result = 31 * result + Arrays.hashCode(roles);
		return result;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

	public String[] getRoles() {
		return roles;
	}

	public void setRoles(String[] roles) {
		this.roles = roles;
	}

	@Override
	public String toString() {
		return "Manager{" +
			"id=" + id +
			", name='" + name + '\'' +
			", roles=" + Arrays.toString(roles) +
			'}';
	}
}
1 PASSWORD_ENCODER is the means to encrypt new passwords or to take password inputs and encrypt them before comparison.
2 id, name, password, and roles define the parameters needed to restrict access.
3 The customized setPassword() method ensures that passwords are never stored in the clear.

There is a key thing to keep in mind when designing your security layer. Secure the right bits of data (like passwords) and do NOT let them get printed to console, into logs, or exported through JSON serialization.

  • @JsonIgnore applied to the password field protects from Jackson serializing this field.

Creating a Manager’s Repository

Spring Data is so good at managing entities. Why not create a repository to handle these managers? The following code does so:

@RepositoryRestResource(exported = false)
public interface ManagerRepository extends Repository<Manager, Long> {

	Manager save(Manager manager);

	Manager findByName(String name);

}

Instead of extending the usual CrudRepository, you do not need so many methods. Instead, you need to save data (which is also used for updates), and you need to look up existing users. Hence, you can use Spring Data Common’s minimal Repository marker interface. It comes with no predefined operations.

Spring Data REST, by default, will export any repository it finds. You do NOT want this repository exposed for REST operations! Apply the @RepositoryRestResource(exported = false) annotation to block it from export. This prevents the repository and its metadata from being served up.

Linking Employees with Their Managers

The last bit of modeling security is to associate employees with a manager. In this domain, an employee can have one manager while a manager can have multiple employees. The following code defines that relationship:

@Entity
public class Employee {

	private @Id @GeneratedValue Long id;
	private String firstName;
	private String lastName;
	private String description;

	private @Version @JsonIgnore Long version;

	private @ManyToOne Manager manager; (1)

	private Employee() {}

	public Employee(String firstName, String lastName, String description, Manager manager) { (2)
		this.firstName = firstName;
		this.lastName = lastName;
		this.description = description;
		this.manager = manager;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Employee employee = (Employee) o;
		return Objects.equals(id, employee.id) &&
			Objects.equals(firstName, employee.firstName) &&
			Objects.equals(lastName, employee.lastName) &&
			Objects.equals(description, employee.description) &&
			Objects.equals(version, employee.version) &&
			Objects.equals(manager, employee.manager);
	}

	@Override
	public int hashCode() {

		return Objects.hash(id, firstName, lastName, description, version, manager);
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Long getVersion() {
		return version;
	}

	public void setVersion(Long version) {
		this.version = version;
	}

	public Manager getManager() {
		return manager;
	}

	public void setManager(Manager manager) {
		this.manager = manager;
	}

	@Override
	public String toString() {
		return "Employee{" +
			"id=" + id +
			", firstName='" + firstName + '\'' +
			", lastName='" + lastName + '\'' +
			", description='" + description + '\'' +
			", version=" + version +
			", manager=" + manager +
			'}';
	}
}
1 The manager attribute is linked by JPA’s @ManyToOne attribute. Manager does not need the @OneToMany, because you have not defined the need to look that up.
2 The utility constructor call is updated to support initialization.

Securing Employees to Their Managers

Spring Security supports a multitude of options when it comes to defining security policies. In this section, you want to restrict things such that ONLY managers can view employee payroll data, and that saving, updating, and deleting operations are confined to the employee’s manager. In other words, any manager can log in and view the data, but only a given employee’s manager can make any changes. The following code achieves these goals:

@PreAuthorize("hasRole('ROLE_MANAGER')") (1)
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

	@Override
	@PreAuthorize("#employee?.manager == null or #employee?.manager?.name == authentication?.name")
	Employee save(@Param("employee") Employee employee);

	@Override
	@PreAuthorize("@employeeRepository.findById(#id)?.manager?.name == authentication?.name")
	void deleteById(@Param("id") Long id);

	@Override
	@PreAuthorize("#employee?.manager?.name == authentication?.name")
	void delete(@Param("employee") Employee employee);

}
1 @PreAuthorize at the top of the interface restricts access to people with ROLE_MANAGER.

On save(), either the employee’s manager is null (initial creation of a new employee when no manager has been assigned), or the employee’s manager’s name matches the currently authenticated user’s name. Here, you are using Spring Security’s SpEL expressions to define access. It comes with a handy ?. property navigator to handle null checks. It is also important to note using the @Param(…​) on the arguments to link HTTP operations with the methods.

On delete(), the method either has access to the employee, or if it has only an id, it must find the employeeRepository in the application context, perform a findOne(id), and check the manager against the currently authenticated user.

Writing a UserDetails Service

A common point of integration with security is to define a UserDetailsService. This is the way to connect your user’s data store into a Spring Security interface. Spring Security needs a way to look up users for security checks, and this is the bridge. Thankfully, with Spring Data, the effort is quite minimal:

@Component
public class SpringDataJpaUserDetailsService implements UserDetailsService {

	private final ManagerRepository repository;

	@Autowired
	public SpringDataJpaUserDetailsService(ManagerRepository repository) {
		this.repository = repository;
	}

	@Override
	public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
		Manager manager = this.repository.findByName(name);
		return new User(manager.getName(), manager.getPassword(),
				AuthorityUtils.createAuthorityList(manager.getRoles()));
	}

}

SpringDataJpaUserDetailsService implements Spring Security’s UserDetailsService. The interface has one method: loadUserByUsername(). This method is meant to return a UserDetails object so that Spring Security can interrogate the user’s information.

Because you have a ManagerRepository, there is no need to write any SQL or JPA expressions to fetch this needed data. In this class, it is autowired by constructor injection.

loadUserByUsername() taps into the custom finder you wrote a moment ago, findByName(). It then populates a Spring Security User instance, which implements the UserDetails interface. You are also using Spring Securiy’s AuthorityUtils to transition from an array of string-based roles into a Java List of type GrantedAuthority.

Wiring up Your Security Policy

The @PreAuthorize expressions applied to your repository are access rules. These rules are for nought without a security policy:

@Configuration
@EnableWebSecurity (1)
@EnableGlobalMethodSecurity(prePostEnabled = true) (2)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { (3)

	@Autowired
	private SpringDataJpaUserDetailsService userDetailsService; (4)

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.userDetailsService(this.userDetailsService)
				.passwordEncoder(Manager.PASSWORD_ENCODER);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception { (5)
		http
			.authorizeRequests()
				.antMatchers("/built/**", "/main.css").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.defaultSuccessUrl("/", true)
				.permitAll()
				.and()
			.httpBasic()
				.and()
			.csrf().disable()
			.logout()
				.logoutSuccessUrl("/");
	}

}

This code has a lot of complexity in it, so we will walk through it, first talking about the annotations and APIs. Then we will discuss the security policy it defines.

1 @EnableWebSecurity tells Spring Boot to drop its autoconfigured security policy and use this one instead. For quick demos, autoconfigured security is okay. But for anything real, you should write the policy yourself.
2 @EnableGlobalMethodSecurity turns on method-level security with Spring Security’s sophisticated @Pre and @Post annotations.
3 It extends WebSecurityConfigurerAdapter, a handy base class for writing policy.
4 It autowires the SpringDataJpaUserDetailsService by field injection and then plugs it in through the configure(AuthenticationManagerBuilder) method. The PASSWORD_ENCODER from Manager is also set up.
5 The pivotal security policy is written in pure Java with the configure(HttpSecurity) method call.

The security policy says to authorize all requests by using the access rules defined earlier:

  • The paths listed in antMatchers() are granted unconditional access, since there is no reason to block static web resources.

  • Anything that does not match that policy falls into anyRequest().authenticated(), meaning it requires authentication.

  • With those access rules set up, Spring Security is told to use form-based authentication (defaulting to / upon success) and to grant access to the login page.

  • BASIC login is also configured with CSRF disabled. This is mostly for demonstrations and not recommended for production systems without careful analysis.

  • Logout is configured to take the user to /.

BASIC authentication is handy when you are experimenting with curl. Using curl to access a form-based system is daunting. It is important to recognize that authenticating with any mechanism over HTTP (not HTTPS) puts you at risk of credentials being sniffed over the wire. CSRF is a good protocol to leave intact. It is disabled to make interaction with BASIC and curl easier. In production, it is best to leave it on.

Adding Security Details Automatically

One part of a good user experience is when the application can automatically apply context. In this example, if a logged-in manager creates a new employee record, it makes sense for that manager to own it. With Spring Data REST’s event handlers, there is no need for the user to explicitly link it. It also ensures the user does not accidentally assign records to the wrong manager. The SpringDataRestEventHandler handles that for us:

@Component
@RepositoryEventHandler(Employee.class) (1)
public class SpringDataRestEventHandler {

	private final ManagerRepository managerRepository;

	@Autowired
	public SpringDataRestEventHandler(ManagerRepository managerRepository) {
		this.managerRepository = managerRepository;
	}

	@HandleBeforeCreate
	@HandleBeforeSave
	public void applyUserInformationUsingSecurityContext(Employee employee) {

		String name = SecurityContextHolder.getContext().getAuthentication().getName();
		Manager manager = this.managerRepository.findByName(name);
		if (manager == null) {
			Manager newManager = new Manager();
			newManager.setName(name);
			newManager.setRoles(new String[]{"ROLE_MANAGER"});
			manager = this.managerRepository.save(newManager);
		}
		employee.setManager(manager);
	}
}
1 @RepositoryEventHandler(Employee.class) flags this event handler as applying only to Employee objects. The @HandleBeforeCreate annotation gives you a chance to alter the incoming Employee record before it gets written to the database.

In this situation, you can look up the current user’s security context to get the user’s name. Then you can look up the associated manager by using findByName() and apply it to the manager. There is a little extra glue code to create a new manager if that person does not exist in the system yet. However, that is mostly to support initialization of the database. In a real production system, that code should be removed and instead depend on the DBAs or Security Ops team to properly maintain the user data store.

Pre-loading Manager Data

Loading managers and linking employees to these managers is straightforward:

@Component
public class DatabaseLoader implements CommandLineRunner {

	private final EmployeeRepository employees;
	private final ManagerRepository managers;

	@Autowired
	public DatabaseLoader(EmployeeRepository employeeRepository,
						  ManagerRepository managerRepository) {

		this.employees = employeeRepository;
		this.managers = managerRepository;
	}

	@Override
	public void run(String... strings) throws Exception {

		Manager greg = this.managers.save(new Manager("greg", "turnquist",
							"ROLE_MANAGER"));
		Manager oliver = this.managers.save(new Manager("oliver", "gierke",
							"ROLE_MANAGER"));

		SecurityContextHolder.getContext().setAuthentication(
			new UsernamePasswordAuthenticationToken("greg", "doesn't matter",
				AuthorityUtils.createAuthorityList("ROLE_MANAGER")));

		this.employees.save(new Employee("Frodo", "Baggins", "ring bearer", greg));
		this.employees.save(new Employee("Bilbo", "Baggins", "burglar", greg));
		this.employees.save(new Employee("Gandalf", "the Grey", "wizard", greg));

		SecurityContextHolder.getContext().setAuthentication(
			new UsernamePasswordAuthenticationToken("oliver", "doesn't matter",
				AuthorityUtils.createAuthorityList("ROLE_MANAGER")));

		this.employees.save(new Employee("Samwise", "Gamgee", "gardener", oliver));
		this.employees.save(new Employee("Merry", "Brandybuck", "pony rider", oliver));
		this.employees.save(new Employee("Peregrin", "Took", "pipe smoker", oliver));

		SecurityContextHolder.clearContext();
	}
}

The one wrinkle is that Spring Security is active with access rules in full force when this loader runs. Thus, to save employee data, you must use Spring Security’s setAuthentication() API to authenticate this loader with the proper name and role. At the end, the security context is cleared out.

Touring Your Secured REST Service

With all these modifications in place, you can start the application (./mvnw spring-boot:run) and check out the modifications by using the following curl (shown with its output):

$ curl -v -u greg:turnquist localhost:8080/api/employees/1
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'greg'
> GET /api/employees/1 HTTP/1.1
> Host: localhost:8080
> Authorization: Basic Z3JlZzp0dXJucXVpc3Q=
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Set-Cookie: JSESSIONID=E27F929C1836CC5BABBEAB78A548DF8C; Path=/; HttpOnly
< ETag: "0"
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 25 Aug 2015 15:57:34 GMT
<
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "description" : "ring bearer",
  "manager" : {
    "name" : "greg",
    "roles" : [ "ROLE_MANAGER" ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/employees/1"
    }
  }
}

This shows a lot more detail than you saw in the first section. First of all, Spring Security turns on several HTTP protocols to protect against various attack vectors (Pragma, Expires, X-Frame-Options, and others). You are also issuing BASIC credentials with -u greg:turnquist which renders the Authorization header.

Amidst all the headers, you can see the ETag header from your versioned resource.

Finally, inside the data itself, you can see a new attribute: manager. You can see that it includes the name and roles but NOT the password. That is due to using @JsonIgnore on that field. Because Spring Data REST did not export that repository, its values are inlined in this resource. You will put that to good use as you update the UI in the next section.

Displaying Manager Information in the UI

With all these modifications in the backend, you can now shift to updating things in the frontend. First of all, you can show an employee’s manager inside the <Employee /> React component:

class Employee extends React.Component {

	constructor(props) {
		super(props);
		this.handleDelete = this.handleDelete.bind(this);
	}

	handleDelete() {
		this.props.onDelete(this.props.employee);
	}

	render() {
		return (
			<tr>
				<td>{this.props.employee.entity.firstName}</td>
				<td>{this.props.employee.entity.lastName}</td>
				<td>{this.props.employee.entity.description}</td>
				<td>{this.props.employee.entity.manager.name}</td>
				<td>
					<UpdateDialog employee={this.props.employee}
								  attributes={this.props.attributes}
								  onUpdate={this.props.onUpdate}
								  loggedInManager={this.props.loggedInManager}/>
				</td>
				<td>
					<button onClick={this.handleDelete}>Delete</button>
				</td>
			</tr>
		)
	}
}

This merely adds a column for this.props.employee.entity.manager.name.

Filtering out JSON Schema Metadata

If a field is shown in the data output, it is safe to assume it has an entry in the JSON Schema metadata. You can see it in the following excerpt:

{
	...
    "manager" : {
      "readOnly" : false,
      "$ref" : "#/descriptors/manager"
    },
    ...
  },
  ...
  "$schema" : "https://json-schema.org/draft-04/schema#"
}

The manager field is not something you want people to edit directly. Since it is inlined, it should be viewed as a read-only attribute. To filter out inlined entries from the CreateDialog and UpdateDialog, you can delete such entries after fetching the JSON Schema metadata in loadFromServer():

/**
 * Filter unneeded JSON Schema properties, like uri references and
 * subtypes ($ref).
 */
Object.keys(schema.entity.properties).forEach(function (property) {
	if (schema.entity.properties[property].hasOwnProperty('format') &&
		schema.entity.properties[property].format === 'uri') {
		delete schema.entity.properties[property];
	}
	else if (schema.entity.properties[property].hasOwnProperty('$ref')) {
		delete schema.entity.properties[property];
	}
});

this.schema = schema.entity;
this.links = employeeCollection.entity._links;
return employeeCollection;

This code trims out both URI relations as well as $ref entries.

Trapping for Unauthorized Access

With security checks configured on the backend, you can add a handler in case someone tries to update a record without authorization:

onUpdate(employee, updatedEmployee) {
	if(employee.entity.manager.name === this.state.loggedInManager) {
		updatedEmployee["manager"] = employee.entity.manager;
		client({
			method: 'PUT',
			path: employee.entity._links.self.href,
			entity: updatedEmployee,
			headers: {
				'Content-Type': 'application/json',
				'If-Match': employee.headers.Etag
			}
		}).done(response => {
			/* Let the websocket handler update the state */
		}, response => {
			if (response.status.code === 403) {
				alert('ACCESS DENIED: You are not authorized to update ' +
					employee.entity._links.self.href);
			}
			if (response.status.code === 412) {
				alert('DENIED: Unable to update ' + employee.entity._links.self.href +
					'. Your copy is stale.');
			}
		});
	} else {
		alert("You are not authorized to update");
	}
}

You had code to catch an HTTP 412 error. This traps an HTTP 403 status code and provides a suitable alert.

You can do the same for delete operations:

onDelete(employee) {
	client({method: 'DELETE', path: employee.entity._links.self.href}
	).done(response => {/* let the websocket handle updating the UI */},
	response => {
		if (response.status.code === 403) {
			alert('ACCESS DENIED: You are not authorized to delete ' +
				employee.entity._links.self.href);
		}
	});
}

This is coded similarly with a tailored error message.

Add some security details to the UI

The last thing to crown this version of the application is to display who is logged in as well provide a logout button by including this new <div> in the index.html file ahead of the react <div>:

<div>
    Hello, <span id="managername" th:text="${#authentication.name}">user</span>.
    <form th:action="@{/logout}" method="post">
        <input type="submit" value="Log Out"/>
    </form>
</div>

Putting It All Together

To see these changes in the frontend, restart the application and navigate to http://localhost:8080.

You are immediately redirected to a login form. This form is supplied by Spring Security, though you can create your own if you wish. Log in as greg / turnquist, as the following image shows:

security 1

You can see the newly added manager column. Go through a couple pages until you find employees owned by oliver, as the following image shows:

security 2

Click on Update, make some changes, and then click Update again. It should fail with the following pop-up:

security 3

If you try Delete, it should fail with a similar message. If you create a new employee, it should be assigned to you.

Review

In this section, you:

  • Defined the model of manager and linked it to an employee through a 1-to-many relationship.

  • Created a repository for managers and told Spring Data REST to not export.

  • Wrote a set of access rules for the employee repository and also write a security policy.

  • Wrote another Spring Data REST event handler to trap creation events before they happen so that the current user could be assigned as the employee’s manager.

  • Updated the UI to show an employee’s manager and also display error pop-ups when unauthorized actions are taken.

Issues?

The webpage has become quite sophisticated. But what about managing relationships and inlined data? The create and update dialogs are not really suited for that. It might require some custom written forms.

Managers have access to employee data. Should employees have access? If you were to add more details like phone numbers and addresses, how would you model it? How would you grant employees access to the system so they could update those specific fields? Are there more hypermedia controls that would be handy to put on the page?

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.

Get the Code