Skip to main content

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 API#

Head 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 Controller#

Add simple arithmetic functions to the controller

CalcController.java
@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

service/calcapi.js
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

__tests__/service/calcapi.js
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.

handle/index.js
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