Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
nicoschoenteich
Developer Advocate
Developer Advocate
This SAP Tech Byte is about how to develop a full-stack application using the SAP Approuter, UI5, and the SAP Cloud Application Programming Model (CAP). In part 1 we will focus on how to setup our project so that both the UI5 Tooling and the CAP server can be started with a single command and run on the same port. For this we will use the cds-plugin-ui5, which frees us from complex proxy or destination configuration we had in the past. In part 2 we will add productive authentication and run our application with a hybrid setup using the SAP Authorization and Trust Management Service (XSUAA) on SAP BTP, Cloud Foundry environment. For this we will use the dev-approuter, which is a dev time wrapper for the SAP Approuter. It allows us to use all of the SAP Approuter capabilities (OAuth 2.0 login flow with XSUAA etc.) while still running the UI5 Tooling and CAP server in the same process.

The source code for this blog post can be found at https://github.com/SAP-samples/sap-tech-bytes/tree/cloud-foundry-basics/post6.

This blog post is also available in video format - presented as part of Devtoberfest 2023:



 

NPM Workspaces


We start this project by setting up NPM workspaces, which make it easy to manage and run multiple Node.js applications inside one bigger project. Also, NPM workspaces install node modules very efficiently, so that multiple applications share the same resources. To set up NPM workspaces, all we need is this simple package.json file:
{
"name": "devtoberfest-dev-approuter",
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "npm run dev -w=approuter"
},
"sapux": [
"packages/uimodule"
]
}

The line most relevant for now is the one defining the packages/ directory as the home for all our workspaces (think Node.js applications). The rest of the file contains configuration for things that not yet exist in our project, but which we will add shortly.

 

SAP CAP Application


Let's create and navigate into the packages/ directory:
mkdir packages

cd packages/

We can now create a SAP CAP Application in the form of the well-known bookshop sample. Such an application can easily be generated by running the following commands in the Terminal of your choice:
cds init cap-server

cd cap-server/

cds add sample

Note: "cap-server" is the name of our application and is therefore exchangeable.

And that's it for the SAP CAP part. Our server is ready.

 

UI5 Application


Let's now create a new UI5 application from scratch. Using the SAP Fiori Tools Application Generator we can easily choose one of the available floorplans, select our SAP CAP project as the data source, and then enable the use of the cds-plugin-ui5:


Wait a minute, why do we need the cds-plugin-ui5 in the first place?

SAP CAP applications by default come with an app/ directory, which is the place for frontend applications. Any contents (such as html files) inside this directory get statically served by the SAP CAP server. One could be tempted so simply put UI5 apps in there and serve them through the SAP CAP server (and to be honest this works fairly well), but this approach completely misses out on all of the capabilities that the UI5 Tooling has to offer. Think about a UI5 application developed with TypeScript for example. TypeScript cannot run in the browser as it first needs to be transpiled to JavaScript. We need the UI5 Tooling in this case. Additionally, there should be a clear separation of concern and both components - the SAP CAP backend and UI5 frontend - should be developed and served using their respective tooling. The cds-plugin-ui5 is very helpful in that regard as it hooks the UI5 Tooling into the SAP CAP server, which means we can use both toolings using a single command.

Back to our project. Enabling the cds-plugin-ui5 and NPM workspaces in the SAP Fiori Tools Application Generator (see red box above) adds both configurations to the SAP CAP's package.json. However, we already have configured NPM workspaces one level higher in the parent directory, so we can delete this newly added configuration from the SAP CAP's package.json. It is important to leave the cds-plugin-ui5 dependency though, so that upon start of the SAP CAP server the cds-plugin-ui5 can hook all connected UI5 apps into the server.

But how does this "connection" between the SAP CAP server and the UI5 apps work? There are two options here. The first one being to simply place the UI5 apps inside the app/ directory of the SAP CAP project, which is the default place for frontend applications. The second option is to add the UI5 apps as devDependencies to the SAP CAP's package.json and provide the path to them (when using NPM workspaces, a wildcard * is enough - NPM will handle the module resolution properly). We will go with the second approach here add the "uimodule" as a devDependency. All of the mentioned changes leave us with the following package.json for the SAP CAP project:
{
"name": "cap-server",
"version": "1.0.0",
"description": "A simple CAP project.",
"repository": "<Add your repository here>",
"license": "UNLICENSED",
"private": true,
"dependencies": {
"@sap/cds": "^7",
"express": "^4"
},
"devDependencies": {
"@cap-js/sqlite": "^1",
"cds-plugin-ui5": "^0.2.1",
"uimodule": "*"
},
"scripts": {
"start": "cds-serve"
}
}

In spirit of the side-by-side approach (as far as tooling is concerned) mentioned earlier, we will now move our newly created uimodule out of the SAP CAP server and place it next to it. After that, we can completely remove the app/ directory, which we no longer need, so we end up with the following project structure (dot files and generated files omitted for brevity):
- packages/
- cap-server/
- db/
- srv/
- package.json
+ uimodule/
- package.json

So far so good. We can now install all dependencies from the project root (not the SAP CAP root) - thanks to the NPM workspaces:
npm install

Interesting side note: For the uimodule "installing" means creating a symbolic link inside the node_modules/ to the actual uimodule.

We should now be able to start both the SAP CAP server and the UI5 Tooling simultaneously. All we have to do is start the SAP CAP server, the cds-plugin-ui5 will do the rest for us. Run the following command from the SAP CAP root (/packages/cap-server/😞
npm start

In the logs of the terminal we can see that the UI5 app was mounted to the SAP CAP server:



We can also see the UI5 app being served with the SAP CAP server. It even gives us a small hint that this web app is being served using the UI5 Tooling:


Our UI5 app is fully functional and display the data it retrieves from the SAP CAP server. And the best part is, we didn't have to configure any destination or proxies, as both the backend and frontend run on the same port:


 

Authentication


Our current dev setup is pretty nice and powerful already, but there is one aspect we haven't touched so far - and that is authentication. Our SAP CAP server currently uses basic (mocked) authentication, which should be sufficient for the largest portion of your development efforts. But what if we want to test our app with "real" productive authentication using the SAP Authorization and Trust Management Service on SAP BTP (XSUAA)? As far as SAP CAP is concerned, this is not a problem at all. In the SAP CAP project directory, we can simply run the following command to add the XSUAA feature to the app:
cds add xsuaa && npm install

We can then create an instance of the XSUAA service (in case we don't have on yet), create the respective service key and bind it to our local SAP CAP project:
cf create-service xsuaa application my-xsuaa -c xs-security.json

cf create-service-key my-xsuaa my-sk

cds bind --to my-xsuaa:my-sk

Note: This approach uses the cf CLI. Of course, you could achieve the same thing using the SAP BTP Cockpit in the browser.

Ok, so far so good, but if we were to run our full-stack application now, we would always get a "401 - Unauthorized" error since we haven't logged in (see screen shot below). Without logging into the XSUAA instance (running on SAP BTP) we don't have a valid JSON Web Token (JWT) and SAP CAP rightfully rejects our requests. Maybe this already rings a bell with you (the title of this blog post certainly gives a hint) - we need an SAP Approuter to handle the login flow.


 

Approuter


An SAP Approuter is a simple Node.js based application that is using the @sap/approuter package. It is used to forward user requests to several data sources (usually via destinations), handle user logins and sessions, and also serve static content, such as frontend applications. This sounds almost exactly like what we need, but only almost. Our UI5 application, which is developed in TypeScript, doesn't really qualify as "static content" as it needs to be transpiled (using the UI5 Tooling) before we can run it in the browser and the SAP Approuter can serve it. This means we want to connect an SAP Approuter to the UI5 Tooling, and also to the SAP CAP server while we are at it. There is a new dev-time wrapper for the SAP Approuter that fullfils exactly this purpose: the dev-approuter. Let's use it.

As we are using NPM workspaces, we can simply create a new directory for our new (dev-)approuter in the packages/ directory:
mkdir approuter

We can then add this package.json to the approuter/ directory:
{
"name": "approuter",
"scripts": {
"dev": "CDS_ENV=hybrid node -e 'new require(`dev-approuter`)' _"
},
"devDependencies": {
"dev-approuter": "*",
"cap-server": "*",
"uimodule": "*"
}
}

Note that the "dev" script starts the dev-approuter with the CDS_ENV environment variable set to hybrid, which makes sure the SAP CAP server gets started in hybrid mode using the XSUAA instance on SAP BTP. Speaking of the SAP CAP server ("cap-server") - it is added to the application as a devDepdendency, just like our UI5 application ("uimodule"). This is very similar to the way we configured the cds-plugin-ui5 - remember? This is because the dev-approuter reuses the cds-plugin-ui5 under the hood.

The SAP Approuter's routing configuration is usually described in xs-app.json file. For the dev-approuter however, we can create an xs-dev.json file, which is to safely separate dev time configuration from productive code. The xs-dev.json can be seen as a superset of the xs-app.json - it follows the same syntax and has the same features, but there is one extra goodie: you can directly link dependencies (defined in the package.json) to routes. See this xs-dev.json file, which we now add to the approuter/ directory:
{
"welcomeFile": "uimodule/index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/user-api(.*)",
"target": "$1",
"service": "sap-approuter-userapi"
},
{
"dependency": "uimodule",
"authenticationType": "xsuaa"
},
{
"dependency": "cap-server",
"authenticationType": "xsuaa"
}
]
}

The bottom to route (reflecting our dependencies) are protected with XSUAA, which means we will have to provide the dev-approuter with credentials to connect to the XSUAA instance on SAP BTP. We can get the service key which we created earlier with the following command:
cf service-key my-xsuaa my-sk

Copy the service key from the terminal output and provide it to the dev-approuter via a default-env.json file in the approuter/ directory:
{
"PORT": 5001,
"destinations": [
{
"Name": "cap-server",
"Authentication": "NoAuthentication",
"ProxyType": "Internet",
"Type": "HTTP",
"URL": "http://localhost:4004",
"forwardAuthToken": true
}
],
"VCAP_SERVICES": {
"xsuaa": [
{
"tags": [
"xsuaa"
],
"credentials": { SERVICE KEY FROM TERMINAL OUTPUT }
}
]
}
}

As you can see, the default-env.json in our case not only includes the service key credentials, but also a destination to our SAP CAP server. This is necessary, because we require the additional parameter forwardAuthToken to be true, and the destination injected by the dev-approuter by default does not include this parameter.

We are now ready to start the dev-approuter using the following command from the project root (the NPM workspaces root):
npm run dev

Et voilà, we can now login and access our UI5 application through the dev-approuter, which displays the data from the SAP CAP server:


We can verify that we are logged in with our SAP BTP user by accessing the /user-api route (also defined in xs-dev.json😞


By the way, the livereload for the UI5 application (via the UI5 Tooling) also works using the dev-approuter.

And that's it, we now have an SAP Approuter using the dev-approuter package to hook the UI5 Tooling as well as the SAP CAP server into it. All of this is running in hybrid mode connected to our XSUAA instance on SAP BTP - talk about a powerful full-stack dev setup!

 

Feel free to reach out in case you have any questions!

 






 

 

 

 

SAP Tech Bytes is an initiative to bring you bite-sized information on all manner of topics, in video and written format. Enjoy!

 

 
16 Comments
mantri_sagar
Explorer
Dear nicolai.geburek

Thank you for the nice blog and explanation.

I have few questions,

1) Isn't it possible to use cds bind instead of adding credentials in xs-dev.json manually ? cds bind --exec npm run dev -w=approuter

2) if we add credentials for destination and connectivity service to test remote on-prem destination with principal propagation locally, would that be sufficient or will need some additional steps ?

3) also if destination credentials are added within xs-dev.json, how dev-approuter would differentiate between local and deployed service ?

Hope to hear from you soon, thank you for your time !

 
nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos

Hi Sagar,

fantastic questions! Let's dive into them:

1) You are almost right. From the cap-server root we could run:

cds bind --exec -- npm run dev --prefix ../approuter​

The cds bind command only works in the cap directory, so we cannot initiate it from the NPM workspaces root directly. Of course, we could set up a script in the cap-server root and call that script from the NPM workspaces root.

2) You can consume destination from SAP BTP during local development, yes. Maybe this blog post can help you with that.

3) What exactly do you mean by "differentiating between local and deployed services"? Do you mean deployed services vs. local UI5 or SAP CAP applications?

Best, Nico

arunmbarec
Participant
0 Kudos
Hi Nicholai,

In the application, you have used the ui5 application directly inside the uimodule.  When the app    router is started, the application inside the uimodule is started. What if i want to build a project structure in a way that uimodule the following path.

inside uimodule, i have folder appConfig which has the launchpad configuration. I have two ui5 application, in this example, application1 and application2. I have configured this two application in the launchpad.html page. i brought these form within the app folder for the cap server to the ui module. But when i start the app router, i am unable to see these path. when the app router starts on http://localhost:5001/uimodule/index.html i get the message now found.  i tried renaming the launchpad.html file to index.html and tested. I get the message /uimodule/index.html not found.

 


 

Here is the xs-dev.json approuter configuration.

 
{
"welcomeFile": "uimodule/index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/user-api(.*)",
"target": "$1",
"service": "sap-approuter-userapi"
},
{
"dependency": "uimodule",
"authenticationType": "xsuaa"
},
{
"dependency": "server",
"authenticationType": "xsuaa"
}
]
}

 

Can you guide me? Thanks & Regards,
Arun K
nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos

Hi Arun,

Do the applications 1 & 2 use the UI5 Tooling, a.k.a. do they have a ui5.yaml and package.json file? If yes, you can add them as devDependencies to the dev-approuter and also declare them individually in the xs-dev.json. You can do the same thing with the launchpad itself. Add the UI5 Tooling and add it to the dev-approuter.

Only adding their parent directoy (uimodule/) to the dev-approuter unfortunately won't be sufficient, as the dev-approuter expects the UI5 apps itself as devDependecy.

Hope this helps!

Best, Nico

jravnik
Participant
0 Kudos
Hi nicolai.geburek,

first of all thank you for your blog post and the accompanying video. I really enjoy your way of describing things.

I don't really get what the benefit of using the dev-approuter is (except from launching the approuter and CAP app with a single command).

Doesn't the normal approuter already serve the transpiled TypeScript UI5 apps when cds-plugin-ui5 is used in the CAP app? At least that seems to be the case in my project, since both OData services and UI5 apps work as expected. Also, whenever I open an UI5 app via the normal approuter the console logs in the CAP app states the following:
[info] server:custom-middleware:ui5-tooling-transpile-middleware: Transpiling resource /Component.ts

Which makes me think that the dev-approuter is not needed at all?

Of course the approuter and CAP app have to be started separately, but thats not really an issue (and probably even more tidy since that way the console logs are separated too).

Happy to be corrected, in case I just don't get it 🙂

Best regards
Jürgen
nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos
Hi Jürgen,

Interesting thoughts, thanks for sharing.

In your scenario (where you are using the approuter, plus a CAP app using cds-plugin-ui5 in a separate process), how are you pointing the approuter to your UI5 app? Via the localDir property in the xs-app.json? And if yes, does it simply point to the TypeScript source files? If I do that, I get the error that the Component.js couldn't be found (rightfully so, we only have a Component.ts).

I'm curious how you got this to work!

Best, Nico

 
jravnik
Participant
0 Kudos

Hi Nico,

I just found out that the approuter routes the request through the CAP app to the UI5 application, due to my configuration.

The UI5 app is mounted to /display-books/webapp (set in ui5.yaml). The approuter uses the default route to destination srv-api, since the source path /app/* is not used because the UI5 app is not mounted to that path.

As soon as I mount the UI5 app to /app/display-books/webapp I get the error

ENOENT: no such file or directory, stat '.../app/display-books/webapp/Component.js'.

git repo for reference: https://github.com/jravnik/bookshop-ts

I guess that's not the expected setup? Also, I didn't yet test the behaviour when deployed to BTP - maybe that setup wouldn't work.

Best regards
Jürgen

nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos
Interesting!

You are right, this is not really how it is intended as in a deployed scenario you wouldn't serve your frontend apps through your CAP service (srv-api). You would most likely use the HTML5 Apps Repository Service and point the approuter directly to it - or place the html5 in a localDir inside the approuter.

But still, a nice little trick you found there!

Best, Nico
jravnik
Participant
0 Kudos

Hi nicolai.geburek,

thanks for your hint about the HTML5 App Repo Service. With that I finally got it to work for running locally and in a deployed scenario on BTP seamlessly without any "in-between-changes" to any configurations.

The changes are pushed to the above mentioned git repo.

Would you mind taking a look at it? I am happy to receive some feedback 🙂

Also, would using the dev-approuter still be beneficial in any way? Since the setup with npm workspaces and dev-dependencies can be quite tricky.

Best regards
Jürgen

ilyilil08
Discoverer
Hello Jürgen did you delete the repo? I am also curios to check it.
NKN
Explorer

Hi Nico,

 

excellent Blog, i have previously tried to work with a setup like this and failed so this blog really helped me.

One addition, for windows the dev script in the approuter should look like this:

  "scripts": {
"dev": "set CDS_ENV=hybrid && node -e \"new require(`dev-approuter`)\" _"
}

 

Best regards,

Niklas

wozjac
Product and Topic Expert
Product and Topic Expert
0 Kudos
nicolai.geburek How to have annotations in the uidmodule working using this method?

The example in GitHub has annotations pointing to not correct path, but even after correcting they are not applied.
nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos

Hi jacek.wozniczak

You can point to your annotations file from a cds file within your CAP project, which the provided sample didn't do. Thanks for the pointer!
I have fixed it: https://github.com/SAP-samples/sap-tech-bytes/commit/442b4fad35b1f7b03552143aec1d790ff1649b4e

Best, Nico

KazuhikoTakata
Participant
0 Kudos

Hi @nicoschoenteich 

Thanks for your instruction.

I am working to apply "dev-approuter" to my existing project including CAP Node.js and UI5 apps.

I can setup "dev-approuter" and successfully open my UI5 apps from http://localhost:5000. But my CAP implementation by typescript is ignored. Previously I used "cds-ts watch" to directly test CAP with typescript files and worked well.

Do you have an idea to deal with CAP typescript implementation in `dev-approuter`?

nicoschoenteich
Developer Advocate
Developer Advocate

Hi @KazuhikoTakata,

Thank you for your question!

Unfortunately the dev-approuter doesn't support CAP TS projects as of now. But you can use the cds-plugin-ui5 to hook your UI5 Tooling into your CAP server (running via cds-ts watch)  for a very similar experience.

Hope this helps! Best, Nico

erika_borbaguedes
Discoverer
0 Kudos

Hello Nico, great topic! Any chance do you know someone that could applied using react with CAP? I'm looking for some examples, but it's hard to find.

Best regards,

Erika