Technical Articles
Developing a Fiori elements app with CAP and Fiori Tools
*Update*
I have published a new blog where I explain how to deploy a CAP based Fiori app to a central launchpad (without using Approuter).
Introduction
Recently, I’ve been learning about CAP (Cloud Application Programming Model).
With CAP you can add UI annotations to OData service and preview how the Fiori app will look like from local URL.
I had the following questions regarding CAP based Fiori elements apps.
- How can I consume CAP based OData service from Fiori elements app?
- Can I use Fiori tools?
- Can I use MTA style project and bundle UI and db modules together?
In this blog, I’m going to share how I have achieved these requirements.
The source code is available at GitHub.
I’m new to Cloud Foundry Development, so comments and suggestions are appreciated.
Developing Environment
- Visual Studio Code
- Cloud Foundry trial account
- Cloud Foundry CLI
- MTA Build Tool
- cds development kit
- Fiori Tools extension
Steps
- Create a CAP project
- Add OData v2 proxy
- Add UI module using Fiori tools
- Add Approuter
- Add Deployer
- Add Launchpad module
- Add XSUAA configuration
- Generate mta.yaml
- Build & Deploy
1. Create a CAP project
1.1. Generate a new CAP project.
cds init bookshop
1.2. Create db/schema.cds. Here, I’m using well-known bookshop model.
using { Currency, managed, sap } from '@sap/cds/common';
namespace sap.capire.bookshop;
entity Books : managed {
key ID : Integer @title : 'ID';
title : localized String(111) @title : 'Title';
descr : localized String(1111)@title : 'Description';
author : Association to Authors @title : 'Author';
stock : Integer @title : 'Stock';
price : Decimal(9, 2) @title : 'Price';
currency : Currency @title : 'Currency';
}
entity Authors : managed {
key ID : Integer @title : 'Author ID';
name : String(111) @title : 'Author Name';
dateOfBirth : Date @title : 'Date of Birth';
dateOfDeath : Date @title : 'Date of Death';
placeOfBirth : String @title : 'Place of Birth';
placeOfDeath : String @title : 'Place of Death';
books : Association to many Books on books.author = $self;
}
1.3. Create srv/cat-service.cds.
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService {
entity Books as projection on my.Books
entity Autors as projection on my.Authors
}
1.4. Just blow service definition, add UI annotations to srv/cat-service.cds.
annotate CatalogService.Books with @(
UI: {
HeaderInfo: {
TypeName: 'Book',
TypeNamePlural: 'Books',
Title: { Value: ID },
Description: { Value: title }
},
SelectionFields: [ ID, title, author.name ],
LineItem: [
{ Value: ID },
{ Value: title },
{ Value: author.name },
{ Value: price },
{ Value: currency_code }
],
Facets: [
{
$Type: 'UI.CollectionFacet',
Label: 'Book Info',
Facets: [
{$Type: 'UI.ReferenceFacet', Target: '@UI.FieldGroup#Main', Label: 'Main Facet'}
]
}
],
FieldGroup#Main: {
Data: [
{ Value: ID },
{ Value: title },
{ Value: author_ID },
{ Value: price },
{ Value: currency_code }
]
}
}
);
1.5. Add db/data/sap.capire.bookshop-Authors.csv as initial data.
1.6. Similarly, add db/data/sap.capire.bookshop-Books.csv.
1.7. Execute cds watch
and see the Fiori preview.
2. Add OData v2 proxy
As we are going to use Fiori Tools, the OData service needs to be adapted to v2 (Fiori tools currently do not fully support OData v4).
For this, we use @sap/cds-odata-v2-adapter-proxy
2.1. install @sap/cds-odata-v2-adapter-proxy to your project.
npm install @sap/cds-odata-v2-adapter-proxy -s
2.2. Create srv/server.js.
"use strict";
const cds = require("@sap/cds");
const proxy = require("@sap/cds-odata-v2-adapter-proxy");
cds.on("bootstrap", app => app.use(proxy()));
module.exports = cds.server;
Now, OData v2 service can be accessed with “/v2” prefix.
path to v4 service: http://localhost:4004/catalog/Books
path to v2 service: http://localhost:4004/v2/catalog/Books
3. Add UI module using Fiori tools
Next, let’s add UI module using Fiori tools.
3.1. Generate Fiori Project
1. Press Ctrl + Shift + P and launch Application Generator.
2. Select SAP Fiori elements application.
3. Select List Report Object Page V2.
4. Select Connect to an OData Source as Data source, and specify your local CAP OData URL: http://localhost:4004/v2/catalog/
5. Select “Books” as Main Entity.
6. Type below information.
Module Name | bookshopapp |
Title | Bookshop App |
Namespace | demo |
Description | Bookshop App |
Project Folder | Your CAP project’s root folder |
Once you press Finish, Fiori elements app will be generated.
7. Move to bookshopapp folder you’ve just created and run the app.
npm start
You can see your Fiori app running locally at http://localhost:8080/test/flpSandbox.html#masterDetail-display
.
3.2. Make adjustments for Cloud Foundry
We have to make some adjustments so that this Fiori app can be deployed as part of MTA.
1. Create bookshopapp/webapp/xs-app.json.
{
"welcomeFile": "flpSandbox.html",
"routes": [{
"source": "^(.*)",
"target": "$1",
"authenticationType": "xsuaa",
"service": "html5-apps-repo-rt"
}]
}
2. Add ui5-task-zipper as devDependencies to bookshopapp module.
npm install ui5-task-zipper --save-dev
3. Configure bookshopapp/package.json.
Add ui5-task-zipper to ui5>dependencies.
"ui5": {
"dependencies": [
"@sap/ux-ui5-tooling",
"ui5-task-zipper"
]
}
4. Replace bookshopapp/ui5.yaml with the following code. What I’ve done are:
- Remove “ui5Theme”
- Add resources section
- Add builder section
Resulting bookshopapp/ui5.yaml will look like below.
specVersion: '1.0'
metadata:
name: 'bookshopapp'
type: application
resources:
configuration:
paths:
webapp: webapp
server:
...
builder:
customTasks:
- name: "ui5-task-zipper"
afterTask: "uglify"
configuration:
archiveName: "uimodule"
additionalFiles:
- webapp/xs-app.json
3.3. Add Configuration for Launchpad
1. Add crossNavigation settings to bookshopapp/webapp/manifest.json
This configuration is necessary for executing the app from Fiori Launchpad.
"sap.app": {
"id": "demo.bookshopapp",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"crossNavigation": {
"inbounds": {
"intent1": {
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"semanticObject": "data",
"action": "display",
"title": "{{appTitle}}",
"icon": "sap-icon://search"
}
}
},
4. Add Approuter
We are adding Approuter module to route incoming requests and authenticate users.
4.1. Create approuter folder at the project root and add package.json inside it.
{
"name": "bookshop-approuter",
"version": "0.0.1",
"engines": {
"node": "12.x.x"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
},
"dependencies": {
"@sap/approuter": "^7.1.0"
}
}
4.2. Add approuter/xs-app.json.
Approuter redirects requests containing path /v2/catalog
to srv-api destination, which will be defined later in mta.yaml.
At this point, authenticationType is “none”, because we don’t restrict access to the CAP service.
If you require only users with certain authorization to access the service, you would set authenticationType as “xsuaa”. How to achieve user authentication is very well explained in Jhodel Cailan’s blog.
{
"welcomeFile": "/cp.portal",
"authenticationMethod": "route",
"logout": {
"logoutEndpoint": "/do/logout"
},
"routes": [
{
"source": "^/v2/catalog/(.*)$",
"authenticationType": "none",
"destination": "srv-api",
"csrfProtection": false
}
]
}
5. Add Deployer
Deployer module will upload html5 app to HTML5 Application Repository.
5.1. Create deployer folder at the project root and add package.json inside it.
{
"name": "bookshop-deployer",
"engines": {
"node": "12.x.x"
},
"dependencies": {
"@sap/html5-app-deployer": "2.1.0"
},
"scripts": {
"start": "node node_modules/@sap/html5-app-deployer/index.js"
}
}
6. Add Launchpad module
This module is necessary for the app to be able to run in CF Launchpad.
6.1. Create launchpad folder at the project root and add package.json inside it.
{
"name": "launchpad-site-content",
"description": "Portal site content deployer package",
"engines": {
"node": "12.X"
},
"dependencies": {
"@sap/portal-cf-content-deployer": "3.32.0-20200312112659"
},
"scripts": {
"start": "node node_modules/@sap/portal-cf-content-deployer/src/index.js"
}
}
6.2. Add launchpad/portal-site/CommonDataModel.json.
Here, you configure Launchpad Tile and Group.
{
"_version": "3.0.0",
"identification": {
"id": "c9aae627-9601-4a11-83c3-41b94a3c8026-1576776549699",
"entityType": "bundle"
},
"payload": {
"catalogs": [
{
"_version": "3.0.0",
"identification": {
"id": "defaultCatalogId",
"title": "{{catalogTitle}}",
"entityType": "catalog",
"i18n": "i18n/i18n.properties"
},
"payload": {
"viz": [
{
"id": "demo.bookshopapp",
"vizId": "data-display"
}
]
}
}
],
"groups": [{
"_version": "3.0.0",
"identification": {
"id": "defaultGroupId",
"title": "{{groupTitle}}",
"entityType": "group",
"i18n": "i18n/i18n.properties"
},
"payload": {
"viz": [
{
"id": "demo.bookshopapp-1",
"appId": "demo.bookshopapp",
"vizId": "data-display"
}
]
}
}],
...
}
6.3. Add launchpad/portal-site/i18n/i18n.properties.
catalogTitle=Default Catalog
groupTitle=Default Group
7. Add XSUAA configuration
We have specified in bookshopapp/webapp/xs-app.json that HTML5 Application Repository requires user authentication as below, so we need XSUAA service instance bound to Approuter.
"authenticationType": "xsuaa"
7.1. Add xs-security.json to the project’s root.
{
"xsappname": "bookshop",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "uaa.user",
"description": "UAA"
}
],
"role-templates": [
{
"name": "Token_Exchange",
"description": "UAA",
"scope-references": [
"uaa.user"
]
}
]
}
8. Generate mta.yaml
This is the final step before deploy. We are connecting everything together in mta.yaml.
8.1. Add below configuration to package.json, which is at the project root.
It indicates that HANA DB will be used in production, while SQLite DB will be used for development.
"cds": {
"requires": {
"db": {
"kind": "hana"
}
}
}
8.2. Generate mta.yaml.
cds add mta
Next, we need to make some changes to mta.yaml.
8.3. Modify bookshop-db resource
As I’m using trial account, I need to change the parameter “service” from hana to hanatrial.
# ------------------------------------------------------------
- name: bookshop-db
# ------------------------------------------------------------
type: com.sap.xs.hdi-container
parameters:
service: hanatrial
service-plan: hdi-shared
properties:
hdi-service-name: ${service-name}
8.4. Add Approuter module.
- name: bookshop-app-router
type: approuter.nodejs
path: approuter
parameters:
disk-quota: 512M
memory: 512M
requires:
- name: bookshop_uaa
- name: bookshop_html5_repo_runtime
- name: bookshop_portal
- name: srv-api
group: destinations
properties:
name: srv-api
url: "~{srv-url}"
forwardAuthToken: true
8.5. Add UI and Deployer modules.
- name: bookshop_ui_deployer
type: com.sap.application.content
path: deployer
requires:
- name: bookshop_html5_repo_host
parameters:
content-target: true
build-parameters:
build-result: resources
requires:
- name: bookshopapp
artifacts:
- dist/uimodule.zip
target-path: resources/
- name: bookshopapp
type: html5
path: bookshopapp
build-parameters:
builder: custom
commands:
- npm install
- npm run build
supported-platforms: []
8.6. Add Launchpad deployer module.
- name: bookshop_launchpad_deployer
type: com.sap.portal.content
path: launchpad
deployed-after:
- bookshop_ui_deployer
requires:
- name: bookshop_portal
- name: bookshop_html5_repo_host
- name: bookshop_uaa
8.7. Add the following resources.
- name: bookshop_uaa
type: org.cloudfoundry.managed-service
parameters:
path: ./xs-security.json
service-plan: application
service: xsuaa
- name: bookshop_html5_repo_runtime
type: org.cloudfoundry.managed-service
parameters:
service-plan: app-runtime
service: html5-apps-repo
- name: bookshop_html5_repo_host
type: org.cloudfoundry.managed-service
parameters:
service-plan: app-host
service: html5-apps-repo
config:
sizeLimit: 1
- name: bookshop_portal
type: org.cloudfoundry.managed-service
parameters:
service-plan: standard
service: portal
In the end, mta.yaml will look like this.
8.8. After you’ve generated mta.yaml, add cds configuration for local development.
"cds": {
"requires": {
"db": {
"kind": "hana"
}
},
"[development]": {
"requires": {
"db": {
"kind": "sql"
}
}
}
}
9. Build & Deploy
Finally, we deploy the MTA project to Cloud Foundry.
9.1. Run below command to build the project.
mbt build
The first time I ran build, I received below error.
The error was caued by @sap/cds version which was ^3
. After I upgraded it to ^4
and executed npm install
, the error was gone. I also found that without @sap/hana-client, bookshop-srv could not be deployed.
Below is package.json after upgrade.
"dependencies": {
"@sap/cds": "^4",
"@sap/cds-odata-v2-adapter-proxy": "^1.4.43",
"express": "^4",
"@sap/hana-client": "^2.4.177"
}
9.2. Run below command to deploy the project to Cloud Foundry.
Make sure you have logged in to the target CF subaccount.
cf deploy mta_archives/bookshop_1.0.0.mtar
If everything goes well, you’ll see bookshop-app-router running in your space.
When you click Approuter URL, Fiori launchpad screen opens.
Press the Tile and… Fiori app is running!
Conclusion
In this blog I have explained how to develop a Fiori elements app which consumes CAP service in the same project.
One drawback of putting everything together in one MTA project is that it takes a lot of time to build & deploy. So it may be better to split it into UI project and CAP project.
In that case, you can register CAP service as CF destination and consume the destination from your UI app.
References
About development in Cloud Foundry
- Understanding the nuts and bolts of SAP Fiori Development in the Cloud Foundry Environment
- How to build End-to-End custom applications in Cloud Foundry
Hi Mio,
Great blog!
Minor suggestion: consider creating a GitHub repository to store the code and include a link in the blog to the code repository.
Using only snippets in the blog to highlight certain aspects or walkthrough improves the readability. In addition, this will avoid any formatting issues with the code.
Hi Denys,
Thank you for your advice.
I have included link to my GitHub repository and cut short some of the code.
Best regards,
Mio
This is a very good example of how different SAP technologies can be combined for an E2E full-stack project!
Good job!
Hi Marius,
Thank you for your comment.
Your blog helped me write this article!
Great blog, thanks for this holistic example
Hi Mio,
I got the error.
ervice "bookshop-db" could not be created because none of the service offering(s) "[hanatrial, hanatrial]" match with existing service offerings or provide service plan "hdi-shared"
Seems the trial account can't create the HANA Service. Did you get this?
Hi Li Chen,
I didn’t have that issue.
Please check your subaccount entitlements.
You need “SAP HANA Cloud + SAP HANA Schemas & HDI Containers” or, “SAP HANA Schemas & HDI Containers(Trial)”. I used the latter for my project.
Hi all,
Just a word of clarification in case you didn't know ...
On trial accounts, there are indeed two variants available of the service "SAP HANA Schemas & HDI Containers". One is with technical name "hana", the other one with "hanatrial". hanatrial connects to an "older" HANA as a Service instance on trial. "hana" instead connects to a new HANA Cloud Service instance which is the standard behavior for customer accounts (non-trial). If you use "hana" in your trial account you also have to create an instance of a HANA Cloud Service in one of your spaces. Trial-entitlements allow to create only one instance of HANA Cloud Service. if you want to connect to this HANA Cloud Service instance from your local PC (e.g. with "cds deploy --to hana", make sure to configure the IP-whitlelist appropriately when creating the HANA Cloud Service instance.
Regards,
Klaus
Hi Mio,
You might want to streamline your cds database configuration in package json as:
If the development profile is active, this automatically resolves to "kind": "sqlite". Wit active production profile this resolves to "kind": "hana".
Regards,
Klaus
Hi all,
Just a little add-on from my side: I had to specify the deploy-format as "hdbtable" for Hana Cloud (see here). This is the final cds configuration in my package.json that worked:
BR, Nico
Hi Mio,
great blog - thank you!!, there is a point I didin't fully got.
After deploying I see 2 tables named SAP_COMMON_CURRENCY_xxx - they come from declaration in @sap/cds/common BUT they are both empty: is there a standard way for filling them or should I "IMPORT DATA" for each standard table for each project?
I assume there must be a standard way but so far I've found nothing
Many thanks, KR
Massimiliano
Hi Massimiliano,
Thanks for your comment.
The capire document says:
To fill code lists with data, a business application would frequently connect to and use [Business Configuration] services. This allows customers to adjust the data for code lists individually. Alternatively or in addition, you can also provide initial data for the code lists by placing CSV files in a folder called
csv
next to your data models.But I'm not sure what Business Configuration is. Maybe it is S/4HANA or any other applications containing currency data.
Regards,
Mio
Hi Mio, thanks for your answer.
Meanwhile, trying same exercise also via command shall I've found that folder created for libreary "@sap/cds/common" contains 6 csv files containing example simple data for Currency, Language and Country, so importing those files we can populate mentioned tables.
Massimiliano
Mio Yasutake
Hi Mio, thank you very much for this wonderful tutorial!
The development via app router is still valid. Though, I just wanted to note that the content deployer is now deprecated: Please see here: https://www.npmjs.com/package/@sap/portal-cf-content-deployer. I noticed it this week when I used cds 5.1.x, I got that message during deployment to CF and npm install....The documentation describes how to replace that portalf cf content deployer...
BR
Rufat