Develop Core Plugins in JavaScript
Introduction
Services and Interceptors can be developed in JavaScript and TypeScript when running RESTHeart with the GraalVM or using RESTHeart native.
Tip
|
See example JS plugins |
Note
|
The default RESTHeart native binaries, can only be extended with JavaScript plugins. With Java plugins, you need to build RESTHeart and the plugins with the native-image tool. See Build RESTHeart with custom plugins as native-image
|
RESTHeart Concurrency Model
RESTHeart is a multi-threaded application and it handles each request in a dedicated thread. This makes all code running in a request thread safe, and this also applies to JavaScript services and interceptors.
This is different than usual JavaScript concurrency model, where all code is usually executed in a single thread and requires asynchronous programming, with callbacks, promises, observable, etc.
In RESTHeart, you can write simple JavaScript code that also includes blocking calls, without incurring in performance problems.
Service
The JavaScript code that defines a Service
must export the object options
and the function handle(req, res)
.
The resulting service will be a special instance of StringService, that delegates the handling logic to the JavaScript code. For this reason, the request and response bodies are treated as strings.
export const options = {
name: "helloWorldService",
description: "just another Hello World",
uri: "/hello"
}
export function handle(req, res) {
res.setContent(`{ "msg": `Hello ${rc.name || 'World'}` } `);
res.setContentTypeAsJson();
}
The object options
The options
object must contain the following properties:
param | description | mandatory | default value |
---|---|---|---|
|
the name of the service |
yes |
none |
|
description of the service |
yes |
none |
|
the uri of the service |
yes |
none |
|
|
no |
|
|
|
no |
|
|
experimental support for module replacements. See Node.js core modules mockups on Using JavaScript Modules and Packages in GraalVM JavaScript for modules. Example |
no |
|
The function handle(req, res)
The handle
functions accepts two parameters: the request and the response. These are Java objects of StringRequest and StringResponse respectively.
Interceptor
The JavaScript code that defines an Interceptor
export the object options
and the functions handle(req, res)
and resolve(req)
.
export const options = {
name: "helloWorldInterceptor",
description: "modifies the response of helloWorldService",
interceptPoint: "RESPONSE"
}
export function handle(request, response) {
const rc = JSON.parse(response.getContent() || '{}');
let modifiedBody = {
msg: rc.msg + ' from Italy with Love';
}
response.setContent(JSON.stringify(modifiedBody));
response.setContentTypeAsJson();
}
export function resolve(request) {
return request.isHandledBy("helloWorldService");
}
The object options
The options
object must contain the following properties:
param | description | mandatory | default value |
---|---|---|---|
|
the name of the interceptor |
yes |
none |
|
description of the interceptor |
yes |
none |
|
the intercept point: |
no |
REQUEST_AFTER_AUTH |
|
the class of the interceptor that must match the service to intercept (e.g. |
no |
|
|
experimental support for module replacements. See Node.js core modules mockups on Using JavaScript Modules and Packages in GraalVM JavaScript for modules. Example |
no |
|
The function resolve(req)
The function resolve()
accepts one parameter req
, a Java object of the concrete subclass of Request defined by the parameter pluginClass
, e.g. with pluginClass: "StringInterceptor"
, the request class is StringRequest.
An interceptor of a given class, can intercept requests handled by all services with matching types, e.g. MongoInterceptor
can intercept requests handled by the MongoService
.
When resolve()
returns true
the interceptor will be actually invoked, i.e. this function allows to select the requests to intercept.
The function handle(req, res)
The handle()
functions accepts two parameters: the request and the response.
These a Java objects of the concrete subclasses of Request and Response respectively. For the default pluginClass: "StringInterceptor"
, these are StringRequest and StringResponse respectively.
Packaging
The plugins js files must be placed in a folder with a package.json
file.
Note
|
a single plugin folder can contain multiple Services and Interceptors. |
The package.json
muse declare the services in the rh:services
array and interceptors in the rh:interceptors
array.
{
"name": "restheart-js-foo",
"version": "1.0.0",
"description": "test js plugins for RESTHeart",
"rh:services": [ "foo.js" ],
"rh:interceptors": [ "foo-interceptor.js" ]
}
Modules
The plugins can use npm modules
via require
statements. See require-module-service.js for an example.
Important
|
The imported modules cannot use functionalities that are available in Node.js’ built-in modules (e.g., 'fs' and 'buffer', etc.). |
For instance, you cannot use the module http
, and there is no pure JS implementation available. In this case, you can rely on Java/Javascript interoperability and use the standard Java libraries and all the libraries that are available in RESTHeart.
See GraalVM Modules for more details.
Deploy
To the JavaScript plugin, just copy the folder containing the scripts and the file package.json
into the plugins
directory of RESTHeart.
Note
|
JS plugins can be added or updated without requiring to restart the server, ie RESTHeart supports JS plugins hot deployment. |
If you modify the code, you can force RESTHeart to update it by touching the plugin folder.
$ touch plugins/my-plugin
Configuration parameters
It is possible to defined configuration parameters for the plugins by just defining them in the yml configuration file, under the plugins-args
section.
plugins-args:
foo: # <-- name of the plugin
arg: value
The arguments are available in the pluginArgs
object.
const arg = pluginArgs.arg
Java/JavaScript interoperability
GraalVM allows to execute JavaScript code from RESTHeart and allows interoperability with Java code.
This means that all the Java classes shipped with RESTHeart can be used in JavaScript code.
For example, see the http-client.js plugins, which uses java.net.http.HttpClient
to execute an HTTP request.
MongoDB driver
The MongoDb Java driver, configured by RESTHeart configuration file and already connected to MongoDB, is available in the JavaScript code as mclient
.
See mclient-service.js for an example of how to use it.
Logging
The RESTHeart Java logger can be used from JavaScript code.
LOGGER.debug("pluginArgs {}", pluginArgs);
Pay attention to logging null values. With:
var foo = null;
LOGGER.debug("this is null {}", foo);
An error will be raised.
org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (debug) on ch.qos.logback.classic.Logger@697713cb failed due to: Multiple applicable overloads found for method name debug...
To avoid it, use the following code:
var foo = null;
LOGGER.debug("this is null {}", foo ? foo : "null");