SAP CAP Fiori Frontends
Basics of Creating Fiori Frontends
Introduction
In this article we look at setting up a basic Fiori frontend for a SAP CAP application.
Project Structure
my-bookshop/
├── db/
│ ├── schema.cds
│ └── data/
│ └── my.bookshop-Books.csv
├── srv/
│ ├── cat-service.cds
│ ├── cat-service.js
│ └── annotations.cds
├── app/
│ └── books/ <- Fiori UI
│ ├── webapp/
│ │ ├── i18n/
│ │ │ └── i18n.properties
│ │ ├── Component.ts
│ │ ├── index.html
│ │ └── manifest.json
│ ├── package.json
│ ├── tsconfig.json
│ └── ui5.yaml
└── package.jsonSetting up the Server
Run the following to set up a new SAP CAP project:
mkdir my-bookshop && cd my-bookshop
cds init .
cds add nodejs
mkdir -p db/data
mkdir -p app/books/webapp/i18nEdit ./package.json to add an SQLite database:
{
...
“cds”: {
“requires”: {
“db”: {
“kind”: “sqlite”,
“credentials”: {
“database”: “db.sqlite”
}
}
}
}
}Install packages the dependencies as follows:
npm installSet up the database at ./db/schema.cds as follows:
namespace my.bookshop;
entity Books {
key ID : UUID;
title : String(100);
author : String(100);
stock : Integer;
price : Decimal(10,2);
}Add some test data at ./db/data/my.bookshop-Books.csv:
ID,title,author,stock,price
00000000-0000-0000-0000-000000000000,The Great Gatsby,F. Scott Fitzgerald,50,9.99
00000000-0000-0000-0000-000000000001,To Kill a Mockingbird,Harper Lee,30,12.99
00000000-0000-0000-0000-000000000002,1984,George Orwell,75,8.99Define the service at ./srv/cat-service.cds as follows:
using my.bookshop as db from ‘../db/schema’;
service CatalogService @(path: ‘/odata/v4/catalog’) {
entity Books as projection on db.Books actions {
action restock(quantity: Integer) returns Books;
};
}Implementation this server at ./srv/cat-service.js:
const cds = require(’@sap/cds’)
module.exports = class CatalogService extends cds.ApplicationService {
async init() {
const { Books } = this.entities
this.on(’restock’, Books, async (req) => {
const { quantity } = req.data
const id = req.params[0]?.ID
if (!quantity || quantity < 1) {
return req.error(400, ‘Quantity must be at least 1’)
}
await UPDATE(Books).set({ stock: quantity }).where({ ID: id })
return SELECT.one(Books).where({ ID: id })
})
await super.init()
}
}Add UI annotations at ./srv/annotations.cds:
using CatalogService from ‘./cat-service’;
annotate CatalogService.Books with @(
UI.HeaderInfo: {
TypeName : ‘Book’,
TypeNamePlural: ‘Books’,
Title : { Value: title }
},
UI.SelectionFields: [ title, author ],
UI.LineItem: [
{ Value: title, Label: ‘Title’ },
{ Value: author, Label: ‘Author’ },
{ Value: stock, Label: ‘Stock’ },
{ Value: price, Label: ‘Price’ }
],
UI.FieldGroup#Main: {
Label: ‘Book Details’,
Data : [
{ Value: title },
{ Value: author },
{ Value: stock },
{ Value: price }
]
},
UI.Facets: [{
$Type : ‘UI.ReferenceFacet’,
Label : ‘Details’,
Target: ‘@UI.FieldGroup#Main’
}],
UI.Identification: [{
$Type : ‘UI.DataFieldForAction’,
Action: ‘CatalogService.restock’,
Label : ‘Restock’
}]
);Next, deploy database by running the following:
cds deployNow you can run server using the following:
npm run watchFinally test server by running the following:
curl ‘http://localhost:4004/odata/v4/catalog/$metadata’Make sure the UI annotations are visible in the output.
Setting up the Fiori Frontend
Edit ./app/books/package.json as follows:
{
“name”: “books-ui”,
“version”: “0.0.1”,
“private”: true,
“devDependencies”: {
“@ui5/cli”: “^4”,
“@sap/ux-ui5-tooling”: “1”,
“@sapui5/types”: “~1.130.0”,
“ui5-tooling-transpile”: “^3”,
“typescript”: “^5”
},
“scripts”: {
“start”: “fiori run --open index.html”,
“build”: “ui5 build --all”,
“tsc”: “tsc --noEmit”
}
}Set up the TypeScript configuration at ./app/books/tsconfig.json as follows:
{
“compilerOptions”: {
“target”: “es2022”,
“module”: “es2022”,
“skipLibCheck”: true,
“allowJs”: true,
“strict”: true,
“strictPropertyInitialization”: false,
“moduleResolution”: “node”,
“rootDir”: “./webapp”,
“outDir”: “./dist”,
“baseUrl”: “.”,
“paths”: {
“my/bookshop/books/*”: [”./webapp/*”]
},
“typeRoots”: [
“./node_modules/@types”,
“./node_modules/@sapui5/types”
]
},
“include”: [”./webapp/**/*”]
}Configure ui5 at ./app/books/ui5.yaml as follows:
specVersion: “4.0”
metadata:
name: my.bookshop.books
type: application
server:
customMiddleware:
- name: fiori-tools-proxy
afterMiddleware: compression
configuration:
ignoreCertErrors: false
backend:
- path: /odata/v4
url: http://localhost:4004
ui5:
path:
- /resources
- /test-resources
url: https://sapui5.hana.ondemand.com
- name: fiori-tools-appreload
afterMiddleware: compression
configuration:
port: 35729
path: webapp
delay: 300
- name: ui5-tooling-transpile-middleware
afterMiddleware: compression
configuration:
transformModulesToUI5:
overridesToOverride: true
excludePatterns:
- /Component-preload.js
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
configuration:
transformModulesToUI5:
overridesToOverride: trueAdd a landing page at ./app/books/webapp/index.html:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8”>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0”>
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
<title>Bookshop</title>
<style>
html, body, body > div, #container, #container-uiarea { height: 100%; }
</style>
<script
id=”sap-ui-bootstrap”
src=”/resources/sap-ui-core.js”
data-sap-ui-theme=”sap_horizon”
data-sap-ui-resource-roots=’{”my.bookshop.books”: “./”}’
data-sap-ui-on-init=”module:sap/ui/core/ComponentSupport”
data-sap-ui-compat-version=”edge”
data-sap-ui-async=”true”
data-sap-ui-frame-options=”trusted”>
</script>
</head>
<body class=”sapUiBody sapUiSizeCompact” id=”content”>
<div
data-sap-ui-component
data-name=”my.bookshop.books”
data-id=”container”
data-settings=’{”id”: “my.bookshop.books”}’
data-handle-validation=”true”>
</div>
</body>
</html>Add the main component ./app/books/webapp/Component.ts:
import AppComponent from “sap/fe/core/AppComponent”;
/**
* @namespace my.bookshop.books
*/
export default class Component extends AppComponent {
public static metadata = {
manifest: “json”
};
}Add manifest at ./app/books/webapp/manifest.json:
{
“_version”: “1.65.0”,
“sap.app”: {
“id”: “my.bookshop.books”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: { “version”: “0.0.1” },
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“dataSources”: {
“mainService”: {
“uri”: “/odata/v4/catalog/”,
“type”: “OData”,
“settings”: {
“odataVersion”: “4.0”
}
}
}
},
“sap.ui5”: {
“flexEnabled”: false,
“dependencies”: {
“minUI5Version”: “1.120.0”,
“libs”: {
“sap.m”: {},
“sap.ui.core”: {},
“sap.fe.core”: {},
“sap.fe.templates”: {}
}
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “my.bookshop.books.i18n.i18n”,
“supportedLocales”: [”“],
“fallbackLocale”: “”
}
},
“@i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“uri”: “i18n/i18n.properties”
},
“”: {
“dataSource”: “mainService”,
“preload”: true,
“settings”: {
“operationMode”: “Server”,
“autoExpandSelect”: true,
“earlyRequests”: true
}
}
},
“routing”: {
“config”: {},
“routes”: [
{
“pattern”: “:?query:”,
“name”: “BookList”,
“target”: “BookList”
},
{
“pattern”: “Books({key}):?query:”,
“name”: “BookObjectPage”,
“target”: “BookObjectPage”
}
],
“targets”: {
“BookList”: {
“type”: “Component”,
“id”: “BookList”,
“name”: “sap.fe.templates.ListReport”,
“options”: {
“settings”: {
“contextPath”: “/Books”,
“variantManagement”: “None”,
“initialLoad”: true,
“navigation”: {
“Books”: {
“detail”: {
“route”: “BookObjectPage”
}
}
}
}
}
},
“BookObjectPage”: {
“type”: “Component”,
“id”: “BookObjectPage”,
“name”: “sap.fe.templates.ObjectPage”,
“options”: {
“settings”: {
“contextPath”: “/Books”,
“editableHeaderContent”: false
}
}
}
}
}
}
}Add the translations at ./app/books/webapp/i18n/i18n.properties:
appTitle=Bookshop
appDescription=A simple SAP Fiori bookshop appNext, install the dependencies:
cd app/books
npm installFinally, run the Fiori application:
npm startThe Fiori frontend should open up in the browser automatically.

