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-grok 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 the land of JavaScript.

Part 1 - Basic Features

Welcome Spring community,

In this section, you will see how to get a bare-bones Spring Data REST application up and running quickly. Then you will build a simple UI on top of it 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 these items:

  • Rest Repositories

  • Thymeleaf

  • JPA

  • H2

  • Lombok (May want to ensure your IDE has support for this as well.)

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 above.

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, like HTTP verbs, standardized media types, and IANA-approved link names.

Declaring your domain

The cornerstone of any Spring Data REST-based application are the domain objects. For this section, you will build an application to track the employees for a company. Kick that off by creating a data type like this:

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

	private @Id @GeneratedValue Long id;
	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;
	}
}
  • @Entity is a JPA annotation that denotes the whole class for storage in a relational table.

  • @Id and @GeneratedValue are JPA annotations to note the primary key and that is generated automatically when needed.

  • @Data is a Project Lombok annotation to autogenerate getters, setters, constructors, toString, hash, equals, and other things. It cuts down on the boilerplate.

This entity is used to track employee information. In this case, their name and job description.

Spring Data REST isn’t confined to JPA. It supports many NoSQL data stores, but you won’t be covering those here.

Defining the repository

Another key piece of a Spring Data REST application is to create a corresponding repository definition.

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

}
  • 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 don’t even have to annotate this if it’s top-level and visible. If you use your IDE and open up CrudRepository, you’ll find a fist full of pre-built methods already defined.

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 like this:

src/main/java/com/greglturnquist/payroll/DatabaseLoader.java
@Component
public class DatabaseLoader implements CommandLineRunner {

	private final EmployeeRepository repository;

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

	@Override
	public void run(String... strings) throws Exception {
		this.repository.save(new Employee("Frodo", "Baggins", "ring bearer"));
	}
}
  • This class is marked with Spring’s @Component annotation so that it is automatically picked up by @SpringBootApplication.

  • It implements Spring Boot’s CommandLineRunner so that it gets run after all the beans are created and registered.

  • It uses constructor injection and autowiring to get Spring Data’s automatically created EmployeeRepository.

  • 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 also reduces the risk of bugs and errors. Spring Data looks at the name of methods in a repository class and figures out the operation 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 the same path, you need to change the root URI.

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 using Spring Boot:

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 type ./mvnw spring-boot:run on the command line. (mvnw.bat for Windows users).

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

Touring your REST service

With the app running, you can check things out on the command line using cURL (or any other tool you like).

$ 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 up in a HAL-formatted JSON document.

  • _links is a the collection of links available.

  • 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’ll explore this in a later section.

You can further dig into this service by navigating the employees link.

$ 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.

What’s included along with the data you pre-loaded earlier is a _links attribute with a self link. 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 a link like /api/orders/1/processor, in which the employee is assocated 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 everytime 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 instead the navigation route is maintained, then it becomes easy and flexible to make such adjustments.

You can decide to view that one employee if you wish.

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

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

That’s all well and good, but you are probably itching to create some new entries.

$ 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. But let’s not dig into that. You have already spent way too much time interacting with this REST service manually. Don’t you want to build a slick UI instead?

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.

src/main/java/com/greglturnquist/payroll/HomeController.java
@Controller
public class HomeController {

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

}
  • @Controller marks this class as a Spring MVC controller.

  • @RequestMapping flags the index() method to support the / route.

  • 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 won’t really use many of its features.

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 be wondering where that bundle.js file came from. The way it’s built is shown in the next section.

Loading JavaScript modules

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

The frontend-maven-plugin used to build JavaScript bits
<plugin>
	<groupId>com.github.eirslett</groupId>
	<artifactId>frontend-maven-plugin</artifactId>
	<version>1.6</version>
	<configuration>
		<installDirectory>target</installDirectory>
	</configuration>
	<executions>
		<execution>
			<id>install node and npm</id>
			<goals>
				<goal>install-node-and-npm</goal>
			</goals>
			<configuration>
				<nodeVersion>v10.11.0</nodeVersion>
				<npmVersion>6.4.1</npmVersion>
			</configuration>
		</execution>
		<execution>
			<id>npm install</id>
			<goals>
				<goal>npm</goal>
			</goals>
			<configuration>
				<arguments>install</arguments>
			</configuration>
		</execution>
		<execution>
			<id>webpack build</id>
			<goals>
				<goal>webpack</goal>
			</goals>
		</execution>
	</executions>
</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 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 like the one below:

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"
  },
  "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 - toolkit used by this tutorial

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

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

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

To build the JavaScript code you’ll poke at further down, you need to define a build file for webpack.

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 does the following:

  • Defines the entry point as ./src/main/js/app.js. In essence, app.js (a module we’ll 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 when debugging JS code in the browser, is able to 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 a la 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’s broken down into parts as shown below:

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

src/main/js/app.js
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./client');
  • React and ReactDOM are the main libraries from Facebook used to build this app.

  • 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.

Diving into React

React is based on defining components. Oftentimes, one component can hold multiple instances of another in a parent-child relationship. It’s easy for this concept to extend several layers.

To start things off, it’s 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 let’s start with this:

src/main/js/app.js - App component
class App extends React.Component {

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

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

	render() {
		return (
			<EmployeeList employees={this.state.employees}/>
		)
	}
}
  • class Foo extends React.Component{…​} is the method to create a React component.

  • componentDidMount is the API invoked after React renders a component in the DOM.

  • render is the API to "draw" 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 a fast and efficient updates to your UI.

The common convention is to initialize state with all your attributes empty in the constructor. Then you lookup data from the server using componentDidMount and populate 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’ll soon see.

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

In this code, the function loads data via 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). You might remember 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 creation of the <EmployeeList /> React component as an input parameter.

Below is the definition for an EmployeeList.

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’ll see a little further down).

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

This shows a new React component (note the uppercase format) being created along with two properties: key and data. These are supplied the 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 identifer for child nodes, and _links.self.href is perfect.

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

<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 declaritively 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 the 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.

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 doesn’t manage any state nor does it deal with user input, there is nothing else to do. This might tempt you to cram it into the <EmployeeList /> up above. Don’t do it! Instead, 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.

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.

basic 1

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

Remember using cURL to create new entries? Do that again.

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

And 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 wasn’t dynamic. You had to refresh the browser to fetch new records.

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

  • It’s read only. While you can alter records using cURL, the web page offers none of that.

These are things we can address in the next section.

Part 2 - Hypermedia Controls

In the previous section, you found out how to stand up a backend payroll service to store employee data 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 app 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, i.e. hypertext, and how can you use them? To find out, let’s 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 that clients, i.e. 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 won’t get into them here.

  • GET - fetch the state of a resource without altering the system

  • POST - create a new resource without saying where

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

  • DELETE - remove an existing resource

  • PATCH - alter an existing resource partially

These are standardized HTTP verbs with well written specs. By picking up and using already coined HTTP operations, we don’t have to 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 keen value is that there is no centralized, single media type for REST. Instead, people can develop media types and plug them in. 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, just tweak the repository definition:

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 also adds navigational links to hop from page to page. The rest of the backend is the same (exception for some extra pre-loaded data to make things interesting).

Restart the application (./mvnw spring-boot:run) and see how it works.

$ 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, so to see it in action, ?size=2 applied. As expected, only two employees are listed. In addition, there is also a first, next, and last link. There is also the self link, free of context including page parameters.

If you navigate to the next link, you’ll then see a prev link as well:

$ 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’s a line break. Wrap the whole URL with quotation marks to bypass that.

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

Navigating by relationship

That’s it! 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’s part of the beauty of Spring Data REST. No messy controller updates!)

It’s important to point out, this application isn’t "Spring Data REST-specific." Instead, it uses HAL, URI Templates, and other standards. That’s how 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.

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

With a handy little follow() function, you can now start from the root and navigate to where you need!

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().

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 the previous section, but instead if 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, rinse and repeat.

Sometimes, a rel by itself isn’t 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’ll see further along.

Grabbing JSON Schema metadata

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

You can see the data yourself:

$ 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 negotation to fetch JSON Schema.

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

Creating new records

Equipped with this metadata, you can now add some extra controls to the UI. Create a new React component, <CreateDialog />.

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 as well as the expected render() function.

Let’s dig into these functions in reverse order, and first look 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’s a simple text-based entry field.

  • placeholder is where we can show the user with field is which.

  • You may used to having a name attribute, but it’s not necessary. With React, ref is the mechanism to grab a particular DOM node (as you’ll 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 use pure HTML5 and CSS3. No JavaScript at all! You can see the CSS code used to show/hide the dialog. We won’t 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 doesn’t create a fistful of event handlers on every DOM element. Instead, it has a much more performant and sophisticated solution. The point being you don’t have to 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> using React.findDOMNode(this.refs[attribute]).

this.refs is a way to reach out and grab a particular React component by name. In that sense, you are ONLY getting 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() the new employee. This function is up top 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, 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 rels 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’s 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.

This introduces the concept of paging in our UI. Let’s tackle that next!

First time using a promise-based API? Promises are a way to kick of 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’s a value or another promise. done() functions do NOT return anything, and you don’t chain anything after it. In case you haven’t noticed yet, client (which is an instance of rest from rest.js) as well as the follow function return promises.

Paging through data

You 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 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, an array of HTML buttons.

Because React is based on XML, you can’t 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. Get a hold of its HAL-based record and apply DELETE to its self link.

Unresolved directive in hypermedia/README.adoc - include::src/main/js/app.js[tag=employee]

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

This shows again that it is easiest to manage state in the top component, in one place. This might not always be the case, but oftentimes, managing state in one place makes it easier to keep 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.

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

Unresolved directive in hypermedia/README.adoc - include::src/main/js/app.js[tag=delete]

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 via this.refs.pageSize.

  • defaultValue initializes it with the state’s pageSize.

  • onInput registers a handler as shown below.

Unresolved directive in hypermedia/README.adoc - include::src/main/js/app.js[tag=handle-page-size-updates]

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 if the input is really a number by checking if it’s 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:

Unresolved directive in hypermedia/README.adoc - include::src/main/js/app.js[tag=update-page-size]

Because a new page size causes changes to all the navigation links, it’s 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.

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.

Down below, 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 doesn’t 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 won’t update anything in the other.

That is something we can address in the next section.

Unresolved directive in <stdin> - include::conditional/README.adoc[leveloffset=+1] Unresolved directive in <stdin> - include::events/README.adoc[leveloffset=+1] Unresolved directive in <stdin> - include::security/README.adoc[leveloffset=+1]

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.