Skip to content

SAP Application Router @sap/approuter

Node.js based application router.

When a business application consists of several different apps (microservices), the SAP Application Router is used to provide a single entry point to that business application

Not part of the SAP Discovery Center!

Almost every application in Cloud Foundry requires an Application Router to authenticate users and dispatch requests to microservices. It needs to be configured and deployed by using the offered @sap/approuter npm package.

SAP Application Router
FeatureDescription
Reverse proxyDispatch requests to backend microservices
UAAAuthenticate users
Static resourcesServe static content

Routes

The following configurations are examples of how to integrate the respective services into the Approuter.

SAP HTML5 App Repository

If using a HTML5 App Repo, generally all unknown requests are forward to it by the last rule:

json
{
    "source": "^(/.*)",
    "target": "$1",
    "service": "html5-apps-repo-rt",
    "authenticationType": "xsuaa" 
}

The route name of an UI5 app is defined in the manifest.json file:

json
{
	"sap.app": {
		"id": "namespace.app"
        ...
    }
}

The target route name looks like this:

APP_ID = namespaceapp (chars other than [A..Z][0..9] will be removed!)

With this name, you can reference the app behind the approuter out-of-the-box.

Different Route for HTML5 app

If you need a different route (besides above) you have to take care of some specialities! The Repo uses a cache proxy mechanism using ~(.*)~ cache parts, that need to be handled be the route. Also some files should not be cached, so they need a special cache control.

Usage of a custom route ROUTE_NAME for app APP_ID

json
{
    "source": "^/ROUTE_NAME/index.html(.*)$",
    "target": "/APP_ID/index.html$1",
    "service": "html5-apps-repo-rt",
    "cacheControl": "no-cache, no-store, must-revalidate"
},
{
    "source": "^/ROUTE_NAME/sap-ui-cachebuster-info.json(.*)$",
    "target": "/APP_ID/sap-ui-cachebuster-info.json$1",
    "service": "html5-apps-repo-rt",
    "cacheControl": "no-cache, no-store, must-revalidate"
},        
{
    "source": "^/ROUTE_NAME/~(.*)~/(.*)$",
    "target": "/APP_ID/$2",
    "service": "html5-apps-repo-rt"
},
{
    "source": "^/ROUTE_NAME/(.*)$",
    "target": "/APP_ID/$1",
    "service": "html5-apps-repo-rt"
}

UI Theme Designer Runtime

Using a custom theme, an additional route is nessessary to deliver the endpoint to the com.sap.ui.theming service with the approuter:

json
{
    "source": "^/themes/(.*)$",
    "target": "$1",
    "service": "com.sap.ui.theming",
    "endpoint": "runtime",
    "authenticationType": "xsuaa"
}

Standalone SAPUI5 App

UI5 Versioning

SAPUI5 can either be loaded locally with a relative path from an SAP Web server or externally from a Content Delivery Network (CDN).

Bootstrapping from SAPUI5 CDN

Loading SAPUI5 from a CDN improves your app performance: You can load from a server that (in most cases) is much closer to your location, and you can benefit from the caching mechanism and the language fallback logic.

SAP removes SAPUI5 versions from CDN one year after end of maintenance!

To ensure outdated versions are no longer posing a potential security risk, SAP removes SAPUI5 versions from the SAPUI5 CDN one year after their end of maintenance. In addition, also patches of versions in maintenance older than one year will be removed. Details see in SAP Note 3001696. It's recommended to always upgrade to the latest version in order to get the latest fixes and improvements!

Variants for Bootstrapping from CDN

Default VersionEvergreen VersionSpecific Version
New releases approximately 2 weeks after the release becomes the default version.The evergreen version allows you to automatically select the latest available patch level of a specific (minor) long-term maintenance version for bootstrapping. You refer to a particular <majorVersion>.<minorVersion> long-term maintenance version using a versioned URL.The specific version allows you to select a particular fixed version for bootstrapping. You can refer to a specific version by using a versioned URL.
../resources/..../1.120/resources/..../1.120.25/resources/..

Deployment Options

OptionDefault VersionEvergreen VersionSpecific VersionSwitch TasksBandwidth Costs
Destination-Yes
App Router ExtensionRestartNo

Recommendation from a maintenance and cost perspective

The AppRouter variant allows the use of Evergreen bootstrapping to automatically benefit from security updates within an LTS release. In case of a problem, it is possible to immediately switch to a previous specific version without the need for a deployment. So you get the best of both options.

Always using the latest version is not an option, as there may be incompatible changes that need to be tested in advance.

Services are being retired by SAP!

When a service is retired, the plan is no longer available and the solution cannot be redeployed. Therefore, dependencies such as the UI5_VERSION must be controllable externally.

App Router Extension

Replacement allows to manipulate text content before delivering using placeholder replacement in static text resources.

The version part of the index.html cdn reference can be outsourced to a environment variable set in the btp approuter context

Use UI5_VERSION placeholder inside index.html template.

hbs
src="https://ui5.sap.com/{{UI5_VERSION}}/resources/sap-ui-core.js"

Create my-ui5version-ext.js ui5 version replacement middleware

js
'use strict';

const UI5_VERSION = process.env.UI5_VERSION || 'latest'
const UI5_VERSION_RE = (UI5_VERSION === 'latest') 
    ? '' : `${UI5_VERSION}/`

module.exports = {
    insertMiddleware: {
        beforeRequestHandler: [
            {
                // manifest sap.app.id (replaced dots with '')
                path: '/MYNSAPP/index.html',
                handler: replaceConstant('{{UI5_VERSION}}/', 
                    UI5_VERSION_RE)
            }
        ]
    }
}

function replaceConstant(constant, replacement) {
    return (_req, res, next) => {
        let write = res.write;
        let end = res.end;
        let chunks = [];

        res.write = function (chunk) {
            chunks.push(chunk);
        };

        res.end = function (chunk) {
            if (chunk) chunks.push(chunk);

            let body = Buffer.concat(chunks).toString('utf8');

            if (res.getHeader('Content-Type') 
                && res.getHeader('Content-Type').includes('text/html')) {
                body = body.replace(new RegExp(constant, 'g'), replacement);
            }

            res.setHeader('Content-Length', Buffer.byteLength(body));
            write.call(res, body);
            end.call(res);
        };

        next();
    };
}

Use mustache for complex replacements

js
// mustache is availavle as an App Router dependency
const Mustache = require('mustache');
// Logic-less {{mustache}} templates with JavaScript
const body = Mustache.render(body, { constant: replacement });

Create approuter.js launcher file that includes extension

js
const approuter = require('@sap/approuter');
const ar = approuter();
ar.start({
    extensions: [
        require('./my-ui5version-ext.js')
    ]
});

Change package.json start script to launch custom approuter.js

json
{
    "scripts": {
        "start": "node approuter.js"
    }
}

Add User-Provided Variable inside mta.yaml

yaml
modules:
- name: approuter
    type: approuter.nodejs
    path: app/router
    properties:        
    # UI5_VERSION: latest    # Default Version (latest)
    UI5_VERSION: 1.120       # Evergreen Version
    # UI5_VERSION: 1.120.25  # Specific Version

Set UI5 Version with BTP Cockpit

After redeployment there will be a custom UI5_VERSION variable available inside:
Cloud Foundry -> Space -> Service -> Apps -> AppRouter -> User-Provided Variables
or create the variable manually using Create Variable button.
Select User-Provided Variables and set UI5_VERSION to the needed version.
Inside Overview press Restart button (reload env vars).

Destination

The Destination allows to externalize and configure the used cdn path using an ui5 alias inside the AppRouter as destination.

Automatically created by Fiori Tools

This is the default behavior by the SAP Fiori Tools, but SAPUI5 does not support Evergreen Versions without having version string inside uri path!

Use sub part of the CDN API inside index.html template.

html
src="/resource/sap-ui-core.js"

Add replacement rule to xs-app.json

json
{
    "source": "^/resources/(.*)$",
    "target": "/resources/$1",
    "destination": "ui5"
}

Create default destination inside mta.yaml

yaml
resources:
    - name: APP-destination-service
    type: org.cloudfoundry.managed-service
    parameters:
        init_data:
            subacount:
                destinations:
                    - Name: ui5
                    Authentication: NoAuthentication
                    ProxyType: Internet
                    Type: HTTP
                    # Default Version (not for production!)
                    # URL: https://ui5.sap.com
                    # Evergreen Version (not working/supported!)
                    # URL: https://ui5.sap.com/1.120
                    # Specific Version
                    URL: https://ui5.sap.com/1.120.25

Extending

See extending for information how to extend the application router with custom logic.

You can use the application router as a regular Node.js package. Insead of starting the application router directly, your application can have its own start script:

approuter.js

js
var approuter = require('@sap/approuter');

var ar = approuter();
ar.start({
  extensions: [
    require('./my-ext.js')
  ]
});

Implement the addtional logic in custiom middlewares:

my-ext.js

js
'use strict';
const jwtDecode = require('jwt-decode')

module.exports = {
    insertMiddleware: {
        first: []
        beforeRequestHandler: [
            {
                path: '/',
                handler: function forwardUserInfo(req, res, next) {
                    res.setHeader('x-authenticated', !!req.user)
                    next()
                }
            },
            {
                path: '/me',
                handler: function getUserInfo(req, res, next) {
                    if (!req.user) {
                        res.statusCode = 403;
                        res.end('Missing JWT Token');
                    } else {
                        const { user } = req
                        const token = 
                            jwtDecode(user.token.accessToken);
                        res.statusCode = 200;
                        res.setHeader('Content-Type", 
                            'application/json');
                        res.setHeader('Cache-Control', 
                            'no-cache, no-store');
                        res.end(JSON.stringify({
                            'id': user.id,
                            'loginName': user.id,
                            'firstName': token.given_name,
                            'lastName': token.family_name,
                            'email': token.email,
                            'scopes': user.scopes,
                            'roles': token['xs.system.attributes']
                                ['xs.rolecollections'],
                            'sessionTimeout': 
                                req.routerConfig.sessionTimeout
                        }));
                    }
                }
            },
        ],
        afterRequestHandler: []
    }
};

Support

Component NameComponent Description
BC-XS-APRSupport component for this service