Skip to Content
Technical Articles
Author's profile photo Mio Yasutake

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.

  1. How can I consume CAP based OData service from Fiori elements app?
  2. Can I use Fiori tools?
  3. 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

 

Steps

  1. Create a CAP project
  2. Add OData v2 proxy
  3. Add UI module using Fiori tools
  4. Add Approuter
  5. Add Deployer
  6. Add Launchpad module
  7. Add XSUAA configuration
  8. Generate mta.yaml
  9. 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

About Cloud Application Programming Model

Assigned Tags

      14 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Denys van Kempen
      Denys van Kempen

      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. 

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      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

       

       

      Author's profile photo Marius Obert
      Marius Obert

      This is a very good example of how different SAP technologies can be combined for an E2E full-stack project!

      Good job!

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Marius,

      Thank you for your comment.

      Your blog helped me write this article!

       

       

      Author's profile photo Helmut Tammen
      Helmut Tammen

      Great blog, thanks for this holistic example

      Author's profile photo Li Chen
      Li Chen

      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?

       

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      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.

       

       

      Author's profile photo Klaus Kopecz
      Klaus Kopecz

      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

      Author's profile photo Klaus Kopecz
      Klaus Kopecz

      Hi Mio,

      You might want to streamline your cds database configuration in package json as:

      "cds": {
         "requires": {
           "db": {"kind": "sql"}
         }
      }

      If the development profile is active, this automatically resolves to "kind": "sqlite". Wit active production profile this resolves to "kind": "hana".

      Regards,

      Klaus

      Author's profile photo Nicolai Schoenteich
      Nicolai Schoenteich

      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:

       

       "cds": {
          "hana": {
            "deploy-format": "hdbtable"
          },
          "requires": {
            "db": {
              "kind": "hana"
            }
          },
          "[development]": {
            "requires": {
              "db": {
                "kind": "sql"
              }
            }
          }
        }

       

      BR, Nico

      Author's profile photo MASSIMILIANO CARDOSI
      MASSIMILIANO CARDOSI

      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

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      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

       

       

      Author's profile photo MASSIMILIANO CARDOSI
      MASSIMILIANO CARDOSI

      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

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      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