Skip to Content
Technical Articles

SAP Cloud Platform Backend service: Tutorial [31]: API: called: from: UI5

or:

How to call
an OAuth protected Service
from UI5 app
using App Router

 

Quicklinks:
Diagram
Sample Project Files

Welcome to the second part of our tutorial about how to call our Backend service API from frontend application 
Our precondition: we don’t want to handle the OAuth flow in our application code.
So in the previous tutorial we learnt how to use the App Router to handle the OAuth, and how to configure it
The good news: in the user interface, we don’t need to care about OAuth at all.
We only have to use the configured route, instead of directly calling the protected OData service

In the present tutorial, we’ll focus on what needs to be done in a UI5 application

The user interface will be as minimalistic as possible, so we can focus on the required configuration of the UI5 app

BTW:
This blog is part of a series of tutorials explaining the usage of SAP Cloud Platform Backend service in detail.

Goal

Call an OAuth protected OData service from a UI5 application, without any extra code.
Use App Router to handle OAuth flow
Deploy to Cloud Foundry

This is how our simple UI5 app will look like.

What the screenshot doesn’t show: that there are quite some configuration files in between the backend data and the frontend display.

The diagram in the appendix tries to show the flow.

Prerequisites

Reading the previous tutorial is a (pleasant) prerequisite, as it explains App Router configuration.
Other prerequisites mentioned in previous tutorial are still valid

UI5 expertise is not required

Those who just need sample code may jump directly to the appendix

Create Folder Structure

We create a project folder called ui5_to_bs to make clear that our app should demonstrate how to call Backend service from UI5
The folder structure is very similar like in the previous tutorial. But instead of a simple HTML file, our webapp folder contains a UI5 application (also simple), with all relevant files

Create folder structure as follows:

C:\ui5_to_bs
appfolder
webapp
controller
App.controller.js
ProductList.controller.js
view
App.view.xml
ProductList.view.xml
Component.js
index.html
manifest.json
package.json
xs-app.json
manifest.yml

On my file system, it looks like this:

The following sections will go through each of these files and explain some relevant settings, but only those relevant settings which weren’t discussed in previous tutorial.

As usual, the full file content can be found in the appendix section at the end of this tutorial

Cloud App

We deploy one app to Cloud Foundry, it is described by the manifest.yml file

manifest.yml

In the manifest.yml we define a destination with URL pointing to the host of our OData service:

"url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com"

This allows to reuse this destination from multiple UI elements in our UI5 application, which may reach out to multiple services or entity sets

App Router

App Router configuration was described in detail in the corresponding section in previous tutorial.
So we just need to note the differences

package.json

In our package.json file we now need to add the dependencies required by ui5:

  "dependencies": {
    "@sap/approuter": "^6.0.1",
    "@openui5/sap.m": "^1",
    "@openui5/sap.ui.core": "^1",
    "@openui5/themelib_sap_belize": "^1"
  },

xs-app.json

Just like in the previous blog, we define a route which points to the Backend service – destination
In this tutorial, we’re free to give any name of our choice, so let’s give it a clear name so that we can recognize it later.
This route will be used by our UI5 app.
The name will make clear that UI5 won’t point directly to a URL, but to an approuter route:

    {
      "source": "^/approuter/route/bas/productsrv/(.*)$",
      "destination": "env_destination_bs_api",
      . . . 

UI5 application

Our UI5 application is very simple: It just displays a list of products.
It is a standard UI5 app with a list-element which is bound to an OData model
The only interesting topics about it:

How is the UI5 app started?
Answer: just like in previous tutorial, there’s a default route from App Router to UI5 app

How does it connect to the OAuth protected OData service?
Answer: via App Router

How does it connect to App Router?
Answer: it calls a route defined in App Router

How does it circumvent the OAuth protection?
Answer: it doesn’t. OAuth is handled by App Router

How is the list bound to App Router?
Answer: in manifest.json we point to the AppRouter-route

How…?
Enough questions, now let’s see it in detail

manifest.json

In the present blog, the manifest.json is the most prominent file.
It contains some app and UI5 relevant settings, but the following 2 are interesting for us:

First we define a so-called “data source”:

"dataSources": {
   "productDataSource": {
      "uri": "/approuter/route/bas/productsrv/odatav2/DEFAULT/PRODUCTSERVICE;v=1/",

This data source has a “uri” property which could be a hard-coded URL pointing to an OData service
But in our case: it points to the route which we defined earlier in our App Router config:

/approuter/route/bas/

This route points to the host of our OData service

As such, here, in manifest.json, we have to append the relative path to our service:

productsrv/odatav2/DEFAULT/PRODUCTSERVICE;v=1/”

Note:
The data source has to point to the root URL of the OData service, the so-called service-document URL
Not to an entity set (collection), e.g. “…PRODUCTSERVICE;v=1/products”
We will specify the entity set name when binding a ui control in order to get the data (see below)

The second interesting setting in manifest:
We define a ui5-“model” which points to the data source defined above:

"sap.ui5": {
   "models": {
      "productModel": {
         "dataSource": "productDataSource"
	. . .

The ui5-model can be bound to ui controls and like that, data can be automatically fetched.
We’ll see it below

Note:
Most ui5 apps which use only one data model will specify an empty string as default model. Like that, the xml is less verbosy.
But as usual, I like to make everything very clear, so I’m giving a name here and later we will have to specify it

index.html

We’re using a standard index.html, just like any other ui5 app, so we don’t need to describe anything.

Component.js

Same here

App.view.xml

Same here again, nothing special.
We use a standard app.view which instantiates a child view

ProductList.view.xml

Here we design out User Interface
This is the child view which contains one ui element: the list.
This list is supposed to display the products provided by the backend OData service
In order to bind a ui element to an OData service, it has to be bound to a ui5-model
As such, we bind the list control to the ui5-model which we defined in the manifest.json:

<List
   items="{
      path : 'productModel>/Products'

The snippet shows:
The list contains multiple “items”, the number of items depends on the amount of data in the backend. But we don’t need to care, we just tell the list to get the items from the model with name productModel
However, that is not enough.
As mentioned earlier, we have to specify which “entity set” we want to use
Remember:
An OData service can have multiple entity sets, which represent different collections of data (e.g. Products, salesOrders, customers, invoices, etc)
As such, we have to declare not only the model name but also the entity set name, to complete the path:
‘productModel>/Products’

Next step:
Still: binding the list items to an entity set is not enough:
We also need to tell the items what pieces of data they should display.
With other words: each entity of an OData service consists of multiple properties which carry the actual data.
So here we’re supposed to name one or more property names.
The syntax is shown below:

<items>
   <ObjectListItem
      title="{productModel>name}">

Inside curly brackets we write the property name. Since we decided not to use default model (without name) we have to prepend the model name before the property name
Remember:
Our OData model has an “EntityType” with name “Products” and it has a property with name “name”
Instead of
{productModel>name}
we could write
{productModel>description}
or even
title=”{productModel>name} – (with ID: {productModel>productID})”>

How to know what to write?
As mentioned, the productModel” name is taken from manifest.json.
The “productID” is taken from the OData service metadata:
https://backend-service-api…odatav2/DEFAULT/PRODUCTSERVICE;v=1/$metadata

In my example, it looks like this:

Note:
Same property names can also be taken from CDS file

App.controller.js

As promised, we’re not writing any custom code.
We only want to demonstrate how the OAuth protected resources do find their way from backend to user interface

ProductList.controller.js

Also no custom implementation

Summary

What have we learned in this tutorial?
The message:

Yes, OAuth protected OData service can be accessed from UI5 app

The solution:
Deploy App Router.
Configure 2 routes:
First route to open web app
Second route to call the OAuth proteced OData service
Define destination pointing to the OData service
Configure App Router to handle OAuth flow
And the UI5 app?
It uses the second route instead of a directly using a destination or OData service
Nothing else to do in the UI5 app

Links

App Router documentation in SAP Help Portal
UI5 documentation: https://ui5.sap.com
SAP Cloud Platform Backend service in SAP Help Portal documentation

Ads

Good moment for some advertisements…

Series of tutorials, lot of info around Backend service
Getting started with App Router: part 1, part 2, part 3
See how App Router forwards a token
Understanding OAuth part 1 and part 2
Manually accessing OAuth protected service
Implementing OAuth flow in node.js application part 1 and part 2
Install node.js and configure SAP registry
Use command line client to deploy to Cloud Foundry
Use Cloud Platform Cockpit to deploy to Cloud Foundry
Create API (OData service) in Backend service (EASY!!)
Create XSUAA instance with correct parameters for Backend service
Learn about JWT token and how to view the content

Appendix 1: Diagram

Appendix 2: All Project Files

In this appendix you can find all files required to run the described sample application.
For your convenience, see here again the folder structure:

CDS
This CDS file was used as target OData service for this sample project.
You may use it to create an API (OData service) in SAP Cloud Platform Backend service

service ProductService {
    entity Products{
        key productID : String;
        name : String;
        description : String;
    }
}

manifest.yml

---
applications:
  - name: ui5_to_bs
    memory: 256M
    path: appfolder 
    services:
    - XsuaaForHtml
    env:
      destinations: >
        [
          {
              "name": "env_destination_bs_api",
              "url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com",
              "forwardAuthToken": true
          }
        ]

package.json

{
  "name": "app03",
  "dependencies": {
    "@sap/approuter": "^6.0.1",
    "@openui5/sap.m": "^1",
    "@openui5/sap.ui.core": "^1",
    "@openui5/themelib_sap_belize": "^1"
  },
  "scripts": {
    "start": "node node_modules/@sap/approuter/approuter.js"
  }
}

xs-app.json

{
  "welcomeFile": "approuterhome/index.html",
  "authenticationMethod": "route",
  "routes": [
    {
      "source": "^/approuterhome/(.*)$",
      "target": "$1",
      "localDir": "webapp",
      "authenticationType": "xsuaa"
    },
    {
      "source": "^/approuter/route/bas/productsrv/(.*)$",
      "target": "$1",
      "destination": "env_destination_bs_api",
      "authenticationType": "xsuaa"
    }
  ]
}

index.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<script
		id="sap-ui-bootstrap"
		src="https://ui5.sap.com/resources/sap-ui-core.js"
		data-sap-ui-theme="sap_belize"
		data-sap-ui-resourceroots='{
			"com.example.bas": "./"
		}'
		data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
		data-sap-ui-compatVersion="edge"
		data-sap-ui-async="true">
	</script>
</head>
<body class="sapUiBody" id="content">
	<div data-sap-ui-component data-name="com.example.bas" data-id="container" data-settings='{"id" : "bas"}'></div>
</body>
</html>

Component.js

sap.ui.define([
	"sap/ui/core/UIComponent"
], function (UIComponent) {
	"use strict";

	return UIComponent.extend("com.example.bas.Component", {

		metadata : {
			manifest: "json"
		},

		init : function () {
			UIComponent.prototype.init.apply(this, arguments);
		}
	});
});

manifest.json

{
	"_version": "1.12.0",
	"sap.app": {
		"id": "com.example.bas",
		"type": "application",
		"applicationVersion": {
			"version": "1.0.0"
		},
		"dataSources": {
			"productDataSource": {
				"uri": "/approuter/route/bas/productsrv/odatav2/DEFAULT/PRODUCTSERVICE;v=1/",
				"type": "OData",
				"settings": {
					"odataVersion": "2.0"
				}
			}
		}
	},
	"sap.ui": {
		"technology": "UI5",
		"deviceTypes": {
			"desktop": true
		}
	},
	"sap.ui5": {
		"rootView": {
			"viewName": "com.example.bas.view.App",
			"type": "XML",
			"async": true,
			"id": "app"
		},
		"dependencies": {
			"minUI5Version": "1.60",
			"libs": {
				"sap.m": {}
			}
		},
		"models": {
			"productModel": {
				"dataSource": "productDataSource"
			}
		}
	}
}

App.controller.js

sap.ui.define([	"sap/ui/core/mvc/Controller"], 
   function (Controller) {
	"use strict";
	return Controller.extend("com.example.bas.controller.App", {
	});
});

ProductList.controller.js

sap.ui.define(["sap/ui/core/mvc/Controller"], 
   function (Controller) {
	"use strict";
	return Controller.extend("com.example.bas.controller.ProductList", {
	});
});

App.view.xml

<mvc:View
   controllerName="com.example.bas.controller.App" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" displayBlock="true">
   <Shell>
      <App>
         <pages>
            <Page title="User Interface for SAP Cloud Platform Backend service ">
		<content>
		   <mvc:XMLView viewName="com.example.bas.view.ProductList"/>
		</content>
            </Page>
         </pages>
      </App>
   </Shell>
</mvc:View>

ProductList.view.xml

<mvc:View
   controllerName="com.example.bas.controller.ProductList"	xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
   <List id="productList" class="sapUiResponsiveMargin" width="auto"
      items="{
         path : 'productModel>/Products'
      }">
      <headerToolbar>
         <Toolbar>
            <Title text="List of Products available in Backend service via App Router"/>
         </Toolbar>
      </headerToolbar>
      <items>
         <ObjectListItem
            title="{productModel>name} - (with ID: {productModel>productID})">
         </ObjectListItem>
      </items>
   </List>
</mvc:View>

 

2 Comments
You must be Logged on to comment or reply to a post.
  • Hi Carlos,

    Thank you for this clear tutorial, it’s the first one I come across that is helping me with my app.

    May I hear your thoughts about an issue I have in relation to this topic?

    Do you think it’s possible to make the Authentication service work even though the Ui5 app is deployed on Neo and uses SAML 2.O tokens and the java backend service is deployed on Cloud Foundry and uses JWT tokens ? Any idea?

    Best regards,

    Vincent

    • Hello Vincent Tessier
      Thanks very much for your comment and your feedback!

      I’m very glad that it could help you.
      About your question, I fear that I cannot help you. Right now, I’m not the right person to ask, as I’ve never tried it.
      But I assume that it is possible (configuring something like principal propagation between neo and Cf) and the knowledge and experience must be around.
      So I propose to raise a question in the community
      Kind Regards,
      Carlos