Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

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>

 

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Vincent Tessier
      Vincent Tessier

      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

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      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

      Author's profile photo Bella Fang
      Bella Fang

      Hi Carlos,

      You describe and explanation the work in detail, Thanks for all the information.

      I have a case may need some guidance if you could provide more blog links to help.

      For the frontend, I create an easy-ui5 project Application Router @ Cloud Foundry, then I push it to cloud foundry, it is a destination in cockpit service, in the service there is a frontend application.

      and I bind an xsuaa-service to this frontend destination, this xsuaa-service is also binded to my backend service.

      My question is, then how the the frontend get the backend API?

      what is a destination? how it works when the frontend call the backend?

      Thanks a lot

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Bella Fang

      I understand your confusion.
      When writing an app which calls another service-URL, then usually you can just hardcode the URL in the app code
      But usually this is not recommended.
      You can move the URL out of the code into a property file
      At SAP BTP, you can move the URL into a destination.
      The advantage is e.g. once your app goes from dev landscape to prod landscape, the URL can be changed in the destination, no need to modify the app. That task can be done by an admin.
      Also, user/password don’t need to be hardcoded in the app, can be hidden in the destination (in case of technical user)

      This is about basic destinations. There are also more powerful destinations which provide more comfort for the developer

      In my blog, a simple destination is used. It is not created with the cockpit, it is defined in the manifest and accessed via env var (this is not productive way, but faster to describe in a blog)
      OK?
      You can find more info about destinations in SAP Help
      and a tutorial

       

       

      About UI5
      If not already done, you should go through the tutorials

       

      Step 26 explains how frontend calls backend

       

      Basically:
      In ui5 there’s built-in support for OData services.
      As such, you don’t need to write URL in the frontend code
      The ui5 user controls are bound to a model and they automatically fire requests to the odata service
      The mode knows the URL which has to be called
      And it also uses destinations

      I’ve tried to draw a diagram in this blog, specifically to clarify your question
      Does it not help?
      What can I improve to make it more understandable?

      Kind Regards,
      Carlos

       

       

       

       

      Author's profile photo Bella Fang
      Bella Fang

      Hi Carlos Roggan

      Thanks for your clear clarify of the destination, Actually I didn't hard code the destination in my frontend file, I create configuration file for the destination follow your series blogs, such as xs-app.json file , default-env.json file, mta.yaml file and ui5.yaml file.

      My confusion about the destination is that. usually when I deploy an app to CF, it is an application.

      But in my frontend app, after I deploy , It becomes a destination, my app involve in this destination, I want to ask what is the destination?

      FrontDestination

      My configuration is correct for the destination?

      FrontDestination

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Bella Fang

      I think 2 explanations will help to clarify:
      First
      When talking about “destination” we’re unprecise.
      “destination” consists of 2 artifacts:

      1. the “destination” service, which you can instantiate and bind to app
      2. the “Destination Configuration”, where you define the URL, user, pwd, etc

      OK?

      Second:
      When working with MTA, your destination instance and destination configuration are GENERATED automatically during deploy

      I already explained when answering your question here:
      https://answers.sap.com/questions/13393942/independent-deployed-frontend-web-page-call-the-re.html

      Kind Regards ,
      Carlos

      Author's profile photo Bella Fang
      Bella Fang

      Hi  Carlos Roggan,

      Have a nice day, Yes, What I want to ask is aways the first

      1. the “destination” service, which you can instantiate and bind to app

      What is a destination, is there any different between destination and other application? is is just used to bind?

      Maybe any link can explain this destination.

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Bella Fang ,

      maybe this blog post helps to clarify

      Docu about destinations

      And more

      Author's profile photo Bella Fang
      Bella Fang

      Great!That's indeed help.