GraalVM Nodejs Polyglot
Polyglot
adjective
knowing or using several languages.
noun
a person who knows and is able to use several languages.
#
Create a simple Java REST APIHead over to Spring's project generstor and generate a basic springboot REST api project
Spring Web WEBBuild web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
Lombok DEVELOPER TOOLSJava annotation library which helps to reduce boilerplate code.
Spring Boot DevTools DEVELOPER TOOLSProvides fast application restarts, LiveReload, and configurations for enhanced development experience.
Group: works.hopArtifact: java-calc-apiName: java-calc-apiDescription: Calc Service apiPackage name: works.hop.calcapi
In a location of your choice, unpack the generated bundle. Change into the project folder and make sure it builds. You can use whatever build system you prefer
cd java-calc-api./gradle clean build
#
Add simple Calculator ControllerAdd simple arithmetic functions to the controller
@RestController("/calc")public class CalcController { @GetMapping("/plus/{left}/{right}") public int plus(@PathVariable int left, @PathVariable int right){ return left + right; }
@GetMapping("/minus/{left}/{right}") public int minus(@PathVariable int left, @PathVariable int right){ return left - right; }
@GetMapping("/times/{left}/{right}") public int times(@PathVariable int left, @PathVariable int right){ return left * right; }
@GetMapping("/divide/{left}/{right}") public float divide(@PathVariable float left, @PathVariable float right){ return left / right; }}
Run the application to start serving Calc content
gradle clean build%GRAALVM_HOME%\\bin\\java -jar build\libs\java-calc-api-0.0.1-SNAPSHOT.jar
2021-07-29 23:09:17.145 INFO 17324 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.50]2021-07-29 23:09:17.221 INFO 17324 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2021-07-29 23:09:17.222 INFO 17324 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1343 ms2021-07-29 23:09:17.648 INFO 17324 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2021-07-29 23:09:17.662 INFO 17324 --- [main] w.hop.calcapi.JavaCalcApiApplication : Started JavaCalcApiApplication in 2.444 seconds (JVM running for 2.894)
With the application running on port 8080, verify that the REST API works.
$ curl -i http://localhost:8080/plus/2/5 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 1 0 1 0 0 58 0 --:--:-- --:--:-- --:--:-- 58HTTP/1.1 200Content-Type: application/jsonTransfer-Encoding: chunkedDate: Fri, 30 Jul 2021 04:16:44 GMT
7
$ curl -i http://localhost:8080/minus/2/5 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 2 0 2 0 0 285 0 --:--:-- --:--:-- --:--:-- 285HTTP/1.1 200Content-Type: application/jsonTransfer-Encoding: chunkedDate: Fri, 30 Jul 2021 04:16:23 GMT
-3
it's time to tweak the javascript from a previous article to use this service instead. To do this, we'll simply use a web client like axios or node-fetch. So in the Nodejs project, add an axios dependency
npm i -S axios
In the service folder, create a new file calcapi.js and add the content below
const axios = require('axios');
async function plus(left, right) { const sum = await axios.get(`http://localhost:8080/plus/${left}/${right}`) .then(res => res.data).catch(err => ({error: err.message})) return sum;}
async function minus(left, right) { const diff = await axios.get(`http://localhost:8080/minus/${left}/${right}`) .then(res => res.data).catch(err => ({error: err.message})) return diff;}
async function times(left, right) { const prod = await axios.get(`http://localhost:8080/times/${left}/${right}`) .then(res => res.data).catch(err => ({error: err.message})) return prod;}
async function divide(left, right) { const quot = await axios.get(`http://localhost:8080/divide/${left}/${right}`) .then(res => res.data).catch(err => ({error: err.message})) return quot;}
module.exports = { plus, minus, times, divide }
In the handlerfolder, change the first line to requires service/calcapi instead of service/calc
const { plus, minus, times, divide } = require('../service/calcapi')
Add a test mode for the new calcapi module. The tests will now take an async callback, since the service functions now require await
const { plus, minus, times, divide } = require('../../src/service/calcapi')
describe('Testing simple arithmetic ops', () => {
test('plus should yield the sum of two numbers', async () => { const sum = await plus(3, 4) expect(sum).toBe(7) })
test('minus should yield the differenrce bwteen two numbers', async () => { const diff = await minus(3, 4) expect(diff).toBe(-1) })
test('times should yield the product of two numbers', async () => { const product = await times(3, 4) expect(product).toBe(12) })
test('divide should yield the quotient of two numbers', async () => { const quot = await divide(3, 4) expect(quot).toBe(0.75) })})
Running tests should have both modules pass
npm run test
> js-as-java@1.0.0 test (should match your project folder)> %GRAALVM_HOME%\bin\node.cmd .\node_modules\jest\bin\jest.js
PASS __tests__/service/calcapi.test.js (5.289 s) PASS __tests__/service/calc.test.js------------|---------|----------|---------|---------|-------------------File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s------------|---------|----------|---------|---------|-------------------All files | 82.61 | 100 | 75 | 100 | calc.js | 100 | 100 | 100 | 100 | calcapi.js | 77.78 | 100 | 66.67 | 100 |------------|---------|----------|---------|---------|-------------------
Test Suites: 2 passed, 2 totalTests: 8 passed, 8 totalSnapshots: 0 totalTime: 7.18 sRan all test suites.
One last change needs to be made in the handler module. For each line where the service function is called, preceed that call with await. This is because the service module is now returning a Promise from the 3rd party call.
const sum = await plus(left, right)...const diff = await minus(left, right)...const mult = await times(left, right)...const quot = await divide(left, right)
The rest of the Nodejs project remains the same. Now fire up the project again
npm run start:server
> js-as-java@1.0.0 start:server (should match your nodejs project folder)> %GRAALVM_HOME%\bin\node.cmd index
Example app listening at http://localhost:3003
And amazingly, everything continues to work as expected. The key takeway here is that we are using the same GraalVM to run both the Java REST API and the NodeJs client application