Learn how cap2UI5 plays with the rest of your stack: an app that reads an OData service, and an app that calls a function module in an SAP system.
A cap2UI5 app is a CAP handler, so it
reaches another system the way every CAP handler does — CAP's remote services:
the system's interface is imported as a CDS model, declared in cds.requires,
and the app cds.connect.tos it by name. Nothing in these samples is
cap2UI5's own; what they show is that none of it has to be.
Every sample here needs something beyond a cap2UI5 installation — an OData
service, an SAP system reachable over RFC. And every sample runs without
it: cds watch mocks the system while no credentials are configured, and
once they are, the same app talks to the real one — not a line of it changes.
The top of each sample's screen says which of the two answered.
| You want to … | Sample | To connect a real system you need |
|---|---|---|
Read an OData service — business partners from SAP S/4HANA, with cds.ql |
Z2UI5_CL_CAPS_APP_001 — OData |
API_BUSINESS_PARTNER of an S/4HANA system — or its sandbox on the SAP Business Accelerator Hub and an API key |
| Call a function module — a BAPI — in an SAP system over RFC | Z2UI5_CL_CAPS_APP_002 — RFC |
an ABAP system reachable over RFC, a user allowed to read users, and an RFC connector |
npm install
cds watch # needs @sap/cds-dk (npm i -g @sap/cds-dk) - or: npm run mockedcds watch prints the address of every sample. Log in as alice with an
empty password (CAP's mocked development user), then open
http://localhost:4004/sap/bc/z2ui5?app_start=Z2UI5_CL_CAPS_APP_001 or
http://localhost:4004/sap/bc/z2ui5?app_start=Z2UI5_CL_CAPS_APP_002.
The log shows what is mocked:
[cds] - mocking API_BUSINESS_PARTNER { at: [ '/odata/v4/api-business-partner' ], ... }
[adapters] - ignoring unknown protocol: rfc
[cds] - mocking SAP_RFC { ..., impl: 'srv/external/SAP_RFC.js' }
(The rfc line is CAP noticing that an RFC service has no HTTP endpoint: the
mock of the SAP system answers the apps, not the browser. The mock of the
OData service does both — its rows are at
http://localhost:4004/odata/v4/api-business-partner/A_BusinessPartner.)
srv/
apps/ the samples - one app, one file
external/
API_BUSINESS_PARTNER.cds the OData service's model ─┐ what cds import
SAP_RFC.cds the SAP system's function modules ─┘ makes of a system
SAP_RFC.js the SAP system, mocked
data/ the OData service, mocked - its rows
package.json cds.requires: API_BUSINESS_PARTNER, SAP_RFC
Z2UI5_CL_CAPS_APP_001 lists business
partners of SAP S/4HANA's OData service API_BUSINESS_PARTNER, filters them by
name and category, and shows a partner's addresses on a row press.
The app writes a query as it would against an entity of its own database:
const bupa = await cds.connect.to("API_BUSINESS_PARTNER");
const { A_BusinessPartner } = bupa.entities;
const partners = await bupa.run(SELECT.from(A_BusinessPartner)
.columns("BusinessPartner", "BusinessPartnerFullName", ...)
.where`contains(BusinessPartnerFullName, ${this.search})`
.orderBy("BusinessPartner")
.limit(20));and CAP's remote service sends it as OData V2:
GET /sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner
?$select=BusinessPartner,BusinessPartnerFullName,...&$orderby=BusinessPartner&$top=20
&$filter=substringof('Cust',BusinessPartnerFullName)&$inlinecount=allpages
Three pieces make that work, all of them CAP's:
- The model —
srv/external/API_BUSINESS_PARTNER.cds, whatcds importwrites from the service's$metadata, trimmed to what the sample reads. For the whole service: download the EDMX from the SAP Business Accelerator Hub andcds import API_BUSINESS_PARTNER.edmx --as cds --force. - The declaration —
cds.requires.API_BUSINESS_PARTNERinpackage.json:kind: "odata-v2"and the model. - The call —
cds.connect.to("API_BUSINESS_PARTNER")andcds.ql.
Credentials make the difference: with them, CAP stops mocking the service and
calls it. Put them in a .env file in the project root — it is git-ignored —
and restart cds watch.
No S/4HANA system at hand? The sandbox. The SAP Business Accelerator Hub
runs API_BUSINESS_PARTNER as a sandbox for everybody with an SAP account: log
on to api.sap.com, copy your API key, and
cds.requires.API_BUSINESS_PARTNER.credentials.url=https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER
cds.requires.API_BUSINESS_PARTNER.credentials.headers.APIKey=<your API key>Your own SAP S/4HANA system, with a user that may read business partners:
cds.requires.API_BUSINESS_PARTNER.credentials.url=https://<host>:<port>/sap/opu/odata/sap/API_BUSINESS_PARTNER
cds.requires.API_BUSINESS_PARTNER.credentials.username=<user>
cds.requires.API_BUSINESS_PARTNER.credentials.password=<password>The service has to be active in the system (/IWFND/MAINT_SERVICE).
On SAP BTP, the [production] profile in package.json points the service
at the destination S4HANA: create it in the BTP cockpit and bind the app to
the Destination service (and the Connectivity service, if the system is
on-premise, behind the Cloud Connector).
- The comparison is the service's.
contains( )becomessubstringof( ), and SAP Gateway compares case-sensitively; the mock — SQLite — does not. - A remote call can fail where the own database does not. The sample
catches it and says what happened in a message box — an uncaught error would
answer the roundtrip with
roundtrip failedand nothing else. - The call happens inside the roundtrip, so the user waits for it: the
sample reads the first page when it starts and then only on an event, and
keeps
$topsmall. - The row fields are the OData property names, and the view binds them
uppercased:
BusinessPartnerFullNameis{BUSINESSPARTNERFULLNAME}. - Every user of the app reads with the credentials of
.env. For more than a demo, restrict the app withcds.requires.cap2ui5.roles, or use a destination with principal propagation, so that S/4HANA checks the actual user.
Z2UI5_CL_CAPS_APP_002 calls two BAPIs
that every ABAP system has: BAPI_USER_GETLIST for a list of users — a pattern
like D* goes in as a ranges table — and, on a row press,
BAPI_USER_GET_DETAIL for one user: address, logon data, lock status, roles.
Between them they pass what a function module can take and give — single
values, structures and tables, in both directions — and read the errors from
RETURN, where a BAPI reports them.
The classic way to call a BAPI from Node.js is node-rfc, as in Martin Maruskin's How to call BAPI in SAP from nodejs app — in node-rfc's last form:
const client = new Client({ ashost, sysnr, client, user, passwd });
await client.open();
const result = await client.call("BAPI_USER_GET_DETAIL", { USERNAME: "DEVELOPER" });SAP has since archived node-rfc (the npm package is deprecated, the repository
read-only — SAP-archive/node-rfc#329).
Its place in a CAP project is @sap/cds-rfc,
SAP's RFC plugin for CAP: a function module becomes an action of a remote
service, and the call reads almost as before —
const sap = await cds.connect.to("SAP_RFC");
const { ADDRESS, ACTIVITYGROUPS, RETURN } = await sap.BAPI_USER_GET_DETAIL({ USERNAME: "DEVELOPER" });— while the connection moved out of the code, into CAP's configuration: a
.env file on your machine, an RFC destination on SAP BTP, and a mock while you
develop. The parameters keep their ABAP names; a single value is a value, a
structure an object, a table an array of objects.
The pieces:
-
The model —
srv/external/SAP_RFC.cds, in the shapecds import --from rfcwrites: each function module an action, its import and table parameters the action's parameters (annotated with their kind), its exports and tables the result type. Trimmed to what the sample uses; for a function module's whole interface, import it from your system (connection as below):cds import --from rfc --as cds --name BAPI_USER_GET_DETAIL --destination SAP_RFC --force
@sap/cds-rfcpasses exactly the parameters the model declares — one it does not declare never reaches the system. -
The declaration —
cds.requires.SAP_RFCinpackage.json:kind: "rfc", the model, and"external": true. The rfc kind does not declare itself external, and without the flag CAP would keep choosing the mock next to the model over@sap/cds-rfc— whatever the credentials say. -
The mock —
srv/external/SAP_RFC.js: what CAP serves asSAP_RFCwhile no credentials are configured. It answers the two BAPIs as an ABAP system does — dates asYYYYMMDD, an unknown user as anEmessage inRETURN, not as an exception. -
The call —
cds.connect.to("SAP_RFC")and the BAPI as a method.
1. Credentials, in a .env file in the project root — it is git-ignored:
cds.requires.SAP_RFC.credentials.ashost=<application server host>
cds.requires.SAP_RFC.credentials.sysnr=00
cds.requires.SAP_RFC.credentials.client=100
cds.requires.SAP_RFC.credentials.user=<user>
cds.requires.SAP_RFC.credentials.passwd=<password>RFC goes to the system's gateway, port 33<sysnr> — 3300 for system
number 00.
2. A connector. @sap/cds-rfc speaks RFC through a connector library,
which is not on npmjs.com — without one, the sample says
Calling SAP_RFC failed: Cannot find module '@sap-rfc/node-rfc-library'.
There are two:
-
SAP's
@sap-rfc/node-rfc-library— for SAP customers with an SAP Build Code license, from SAP's own npm registry (the Repository Based Shipment Channel), Linux and Windows only. Configure the registry for the@sap-rfcscope in.npmrcas the@sap/cds-rfcREADME shows, thennpm install: it is an optional dependency of@sap/cds-rfcand comes along. This is also the one for SAP BTP through the Cloud Connector. -
open-rfc — a community connector without the SAP NW RFC SDK, written in plain JavaScript and published on npmjs.com, which replaces SAP's below an unchanged
@sap/cds-rfc. It is a beta (0.x), and its beta covers direct connections to an application server with user and password, on S/4HANA 2023 and NetWeaver 7.50. Add topackage.json"overrides": { "@sap/cds-rfc": { "@sap-rfc/node-rfc-library": "npm:open-rfc@0.2.4" } }
and
npm install.
3. Authorizations for the RFC user: S_RFC for the function group
SU_USER (the two BAPIs), and S_USER_GRP with activity 03 for the user
groups it may read.
On SAP BTP, the [production] profile in package.json points SAP_RFC at
the destination SAP_RFC: an RFC destination, through the Cloud Connector for
an on-premise system, with the app bound to the Destination and Connectivity
services.
Import it — cds import --from rfc --as cds --name Z_MY_FUNCTION --destination SAP_RFC
adds it to srv/external/SAP_RFC.cds — and call it:
await sap.Z_MY_FUNCTION({ ... }). A name with a namespace, /ABC/MY_FUNCTION,
is not a JavaScript method name: await sap.send("/ABC/MY_FUNCTION", { ... }).
The function module has to be remote-enabled.
- A BAPI does not raise. It reports in
RETURN(BAPIRET2), and a message of typeEorAmeans it did nothing — the sample checks it after every call. An exception is for what did not reach the BAPI at all: no connector, no logon, no authorization for the RFC. - Every user of the app calls with the RFC user's authorizations — the SAP
system checks the RFC user, not the one in front of the screen. For more than
a demo, restrict the app with
cds.requires.cap2ui5.roles, or use an RFC destination with principal propagation. - Classic RFC is not encrypted without SNC — keep the connection inside a trusted network.
- Dates arrive as ABAP keeps them,
YYYYMMDD(DATS), and00000000means none; the sample shows them as ISO dates.
npm test # both samples over the wire - against the mocks, and through the real protocols
npm run lintThe tests play the frontend's part — one POST per roundtrip, as cap2UI5/samples does — in two files:
test/samples.test.mjsruns both samples against the mocks, ascds watchserves them, and checks every view they display with the abap2UI5 linter: each control and property has to exist in UI5 1.71.test/remote.test.mjsconfigures credentials, so nothing is mocked, and fakes only the far ends: an HTTP server that answers as S/4HANA's OData service, and a stand-in for the RFC connector below@sap/cds-rfc. What it asserts is what the systems would receive — the OData requests, the RFC parameters by kind.
| Repository | What you learn | |
|---|---|---|
| 1️⃣ | cap2UI5/samples | the abap2UI5 basics as cap2UI5 apps — bindings, events, popups, navigation |
| 2️⃣ | cap2UI5/samples-stack — 📍 you are here | how cap2UI5 plays with your stack — OData services, function modules in SAP systems |
The same idea for ABAP is abap2UI5/samples-stack.