JavaScript Tutorial

This tutorial demonstrates using JavaScript within a Spring Boot application via GraalVM.

A practical use of JavaScript in a server-side pipeline is to re-use client-side validation logic on the server. The same rules that guard form inputs in the browser can run again at the HTTP boundary — written once, applied in both places.

The REST endpoint chains three steps: a Java step parses the request body, a JavaScript step validates it, then a Java step sends the response:

receive:
  class: net.officefloor.tutorial.javascripthttpserver.ReceiveLogic
  next: validate

validate:
  resource: net/officefloor/tutorial/javascripthttpserver/Validate.js
  procedure: JavaScript
  method: validate
  next: send

send:
  class: net.officefloor.tutorial.javascripthttpserver.ResponseLogic

Tutorial Source

Maven dependency

Add GraalVM JavaScript support alongside the Spring Boot starter:

		<dependency>
			<groupId>net.officefloor.javascript</groupId>
			<artifactId>officejavascript</artifactId>
		</dependency>

Application class

@SpringBootApplication
public class Application {

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

Receive step

The first step is a Java class that reads the @RequestBody and returns the parsed object as the parameter for the next step in the pipeline:

public class ReceiveLogic {

	public Request receive(@RequestBody Request request) {
		return request;
	}
}

JavaScript validate step

The JavaScript function receives the Request as a pipeline parameter (param) from the previous step. It throws HttpException to abort the pipeline with the appropriate HTTP error status:

function validate(request) {
	if (request.getId() < 1) {
		throw new HttpException(HttpStatus.BAD_REQUEST, null, "Invalid identifier");
	}
	if (!request.getName()) {
		throw new HttpException(HttpStatus.BAD_REQUEST, null, "Must provide name");
	}
}
validate.officefloor = [
	{ param : Request }
];

The officefloor meta-data array tells OfficeFloor the type of each function parameter. param means the value is passed from the previous step's return value rather than from a managed object source. For further parameter options see the JavaScript polyglot tests.

Send step

After JavaScript validation passes, the send step returns a successful response:

public class ResponseLogic {

	public void send(ObjectResponse<Response> response) {
		response.send(new Response("successful"));
	}
}

Request and Response

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Request {
	private int id;
	private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Response {
	private String message;
}

Note on scripting and threading

GraalVM script engines are created per-thread rather than shared. This avoids concurrency problems — all state is injected into the function — but it does mean memory scales with the number of threads. For validation at the socket boundary, that is bounded by the number of CPU cores, so the overhead is modest.

Testing

@SpringBootTest
@AutoConfigureMockMvc
public class JavaScriptHttpServerTest {

	@Autowired
	private MockMvc mvc;

	@Autowired
	private ObjectMapper mapper;

	@Test
	public void invalidIdentifier() throws Exception {
		mvc.perform(get("/")
				.contentType(MediaType.APPLICATION_JSON)
				.content(mapper.writeValueAsString(new Request(-1, "Daniel"))))
				.andExpect(status().isBadRequest())
				.andExpect(content().string("Invalid identifier"));
	}

	@Test
	public void invalidName() throws Exception {
		mvc.perform(get("/")
				.contentType(MediaType.APPLICATION_JSON)
				.content(mapper.writeValueAsString(new Request(1, ""))))
				.andExpect(status().isBadRequest())
				.andExpect(content().string("Must provide name"));
	}

	@Test
	public void validRequest() throws Exception {
		mvc.perform(get("/")
				.contentType(MediaType.APPLICATION_JSON)
				.content(mapper.writeValueAsString(new Request(1, "Daniel")))
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(new Response("successful"))));
	}
}

Next

The next tutorial covers Cats Effect.