index.js in RESTApps

index.js is the entry point and route configuration for a RESTApp. This is where the RESTApp's endpoints are created. The Sitevision Public API is accessible.

The URL to a RESTApp looks like this: <domain>/rest-api/<addonName>/<restAppRoute>

A simple index.js:

(function() {
   'use strict';

   var router = require('router');

   router.get('/', function(req, res) {
      res.json({message: 'Hello World'});
   });
}());	

Routing

Routes handle how a RESTApp responds requests. The router object provides methods to respond to the following request methods:

  • GET
  • PUT
  • POST
  • DELETE
  • PATCH (@since 2023.02.1)

A route definition has the following structure:

router.method(path, handler);	

Basic examples:

var router = require('router'); // retrieve an instance of the router object

// GET [domain]/rest-api/[headlessCustomModuleName]/user
router.get('/user', function(req, res) {
   res.json({user: 'Foo'});
});

A RESTApp also has implicit support for the HEAD method.

HEAD requests will trigger the corresponding GET route, but the response will be stripped from body payload (headers only) [@since 10.1]

Middleware [@since 5.1]

A middleware function is a function that have access to the request object (req), response object (res) and a function (next) triggering the next function in the request-response chain. The next function will either be another middleware or the actual route function. Middleware functions are executed every time the app receives a request matching a route.

Middleware functions are registered using the router.use-method:

var router = require('router');

// register a middleware that will be executed every time the app recives a request
router.use((req, res, next) => { 
   if (req.method === 'PUT') {
      // do something
   }
	// trigger next function in the stack (next middleware or route function)   
   next(); 
});

router.get('/', (req, res) => {
   res.send('Hello');
});

Requests may be terminated and altered within a middleware. Keep in mind that the next()-function must be called in order to pass over control to the next function in the stack.

Multiple middleware functions may be registered and they will be executed in the order they are registered, i.e. top-down.

const router = require('router'),
   logUtil = require('LogUtil');

router.use((req, res, next) => { 
   logUtil.info('Hello from first');
   next(); 
});

router.use((req, res, next) => { 
   logUtil.info('Hello from second');
   next(); 
});

router.get('/', (req, res) => {
   logUtil.info('Hello from route');
   res.send('Hello');
});

// => 'Hello from first'
// => 'Hello from second'
// => 'Hello from route'

Route parameters

Route parameters are used to capture a value in the URL. Below is an example of a path that will capture userId on the /user route.

// GET [domain]/rest-api/[headlessCustomModuleName]/user/123
router.get('/user/:id', function(req, res) {
 // route is matched and {id: 123} will be populated in the req.params object
});

Request object (req)

The req object is a representation of the HTTP-request.

Properties

req.params

Object that contains parameters passed to a route. The object contains route parameters ('/user/:userId'), query string parameters and values in the request body from POST, PUT and DELETE.

// GET [domain]/rest-api/[headlessCustomModuleName]/user/456
router.get('/user/:userId', function(req, res) {
   logger.info(req.params.userId); // 456
});
// GET [domain]/rest-api/[headlessCustomModuleName]/user?foo=bar
router.get('/user', function(req, res) {
   logger.info(req.params.foo); // bar
});
// POST [domain]/rest-api/[headlessCustomModuleName]/addToBasket (productId=1)
router.post('/addToBasket', function(req, res) {
   logger.info(req.params.productId); // 1
});

req.files

Object containing files from a multipart request. Files are exposed as sv:temporaryFile-nodes (since 4.5.4).

FileUtil and ImageUtil provide useful methods when working with temporary file nodes (since 4.5.4).

router.post('/', function(req, res) {
   var myFile = req.files.name;
});

req.cookies

Object that contains cookies from the request.

// Cookie basketId=789
router.get('/user', function(req, res){
   logger.info(req.cookies.basketId); // 789
});

req.xhr

Boolean indicating whether or not the request is an XHR (ajax) request.

router.get('/', function(req, res) {
   if (req.xhr) {
      res.json({foo: 'ajax-bar'});
   } else {
      res.json({foo: 'bar'});
   }
});	

req.session

Session data is stored and accessed through the req.session property. Session attributes are namespaced for WebApps/RESTapps, i.e. "global" session attributes cannot be accessed. Note that session data must be JSON serializable.

// WebApp/RESTApp x
router.get('/', function(req, res) {
   req.session.name = 'John Doe';
});
// WebApp/RESTApp y
router.get('/', function(req, res) {
   logger.info(req.session.name); // 'John Doe';
});

req.hostname [@since 4.5.3]

Hostname from the HTTP request.

router.post('/', function(req, res) {
   logger.info(req.hostname); //'developer.sitevision.se'
});

req.protocol [@since 5.0]

Protocol from the request (http or https).

router.get('/', (req, res) => {
   logger.info(req.protocol); //'https'
});

req.secure [@since 5.0]

Boolean shorthand checking req.protocol === 'https'.

router.get('/', (req, res) => {
   if (req.secure) {
      // ...
   }
});

If a load balancer or proxy is used, make sure that the client request protocol is forwarded to Sitevision.

req.uri [@since 2023.04.1]

The client request uri (the uri that triggered rendering of this app). Note! The uri is not always equal to "the uri of current page".

// example.com/anything/something
router.get('/', (req, res) => {
   logger.info(req.uri); // '/anything/something'
});

req.serverside [@since 2023.08.1]

Whether the request is a server-side one or not. State is true when the RestApp is invoked via RestAppInvoker.

router.get('/', (req, res) => {
   if (req.serverside) {
      // ...
   }
});

Methods

req.invalidateSession()

Invalidates current session.

req.updateSession() [@since 2023.03.1]

Re-synchronizes local req.session state with the global/shared App session state. Typically needed when multiple apps are executed intertwined

req.header(name) [@since 4.5.2]

Request headers from the HTTP-request

router.post('/', function(req, res) {
   logger.info(req.header('user-agent')); //mozilla....
});

req.method [@since 5.1]

HTTP method of the request.

router.use((req, res, next) => {
   if (req.method === 'PUT') {
      return res.status(403);
   }
   
   next();
});

Response object (res)

The res object is a representation of the HTTP-response.

Methods

res.set(name, value)

Sets a HTTP-header in the response. Returns this for chaining purposes.

res.set('X-Content-Type-Options', 'nosniff')
   .set('Cache-Control', 'no-cache');

res.type(type) [@since 4.5.2]

Sets the response Content-Type header. If type contains the “/” character, then it will be used as-is. Returns this for chaining purposes.

res.type('.html');      // => 'text/html'
res.type('html');       // => 'text/html'
res.type('text/html');  // => 'text/html'
res.type('svg');        // => 'image/svg+xml'
res.type('txt');        // => 'text/plain'

res.send(response)

Sends a character response. Type will be text/html if not explicitly set.

res.send('<p>Hello from RESTApp!</p>');	
res.type('svg')
   .send('<svg version="1.1" ... </svg>');	

res.json(response)

Sends a application/json-response.

res.json({
   id: '123',
   foo: 'bar'
});

res.sendFile(file)

Sends a file as response. Uses best effort for content type matching if no type is explicitly set. File argument must be a Sitevision JCR-Node of type sv:file, sv:image or sv:temporaryFile . Byte array (byte[]) is also valid as of Sitevision 5.0.1 (java.io.File is also allowed for legacy reasons).

res.sendFile(file);
res.type('application/xml; charset=ISO-8859-1')
   .sendFile(file);

res.status(code)

Sets the HTTP-status for the response. Returns this for chaining purposes.

res.status(404).send('Not found');

res.cookie(cookie)

Adds a cookie. The cookie parameter is an object that can have the following properties.

Property

Type

Description

Default

Since

name

String

The name of the cookie



value

String

The value of the cookie



httpOnly

Boolean

Flag to prevent client side scripts from accessing the cookie

false

5.0

secure

Boolean

Sets the cookie to be used with HTTPS only

false

5.0

maxAge

Number

The maximum age of the cookie in seconds.

  • A positive value indicates that the cookie will expire after that many seconds.
  • A negative value indicates a session cookie.
  • A zero value deletes the cookie.

-1

5.0

sameSite

String

Determines in what contexts this cookie will be available/sent. Valid values (case-insensitive) are:

  • "None"
  • "Lax"
  • "Strict"

Invalid values will be treated as "no value is set by the app" (i.e. no same site attribute will be added to the cookie).


7.1

res.cookie({
   name: 'basketId',
   value: '123',
   httpOnly: true,
   secure: true,
   maxAge: 3600,
   sameSite: 'Strict'
});

res.clearCookie(name [, path])

Clears a cookie given a name. If path is missing, it defaults to '/'.

res.clearCookie('basketId');