Technical Articles
Use SCP CF Destination service in NodeJs locally
Introduction
Recently, I was working on a NodeJS application to run on SAP Cloud Platform Cloud Foundry. In this application I had to read data from another service, do some stuff with the data and expose it again as a simple web service. On top of the service, I created a UI5 app that consumes the service to visualize the data.
You could wonder why I want to put a NodeJS app in between. I do this for the following reasons:
- The data comes from an API from another system and some parts should be protected from the end user
- I also need to apply some manipulation before showing the data, I put this logic in NodeJS so I keep the UI simple and clean
- This gives me full control of what the end user can access via the API that I provide and not the full API of the system behind it
Problem
For accessing the other external service, I used the destination service. This service helps you to store all your connections in a central place in the SAP Cloud Platform. It allows you to configure different environments (DEV,ACC,PRD,…) for different SCP accounts but also to change the configuration without touching your code.
There is a lot of documentation and blogs on how to use this Destination service in different languages, how it’s being used with the approuter, how to use it in UI5, in CAP, in NodeJS … But for some reason, I was not able to find any documentation on how to test your NodeJS app with a destination locally. It could be that I didn’t look at the right place or overlooked something. In the end, I found the solution on some GitHub project by accident… I want to save you the time to search and share my solution.
To explain the problem, I’m going to create a new NodeJS app. I will take you through each step from the beginning.
Start by opening the command line interface in the folder of your project. Create a new NodeJS app by running “npm init” in the CLI
npm init
Answer all the questions:
We need to install the following libraries for building an API in NodeJS and to call an other/externel service:
- Express
- This will make the app act as an API
- Request & request-promise
- Used for calling the other API
Run the following command:
npm i express request request-promise
We also need to install sap-xsenv library from SAP. This will be used to use the Destination service.
Run the following command:
npm i @sap/xsenv
Add a start script to the package.json. This will be executed by CloudFoundry when the app is deployed to start the app. In the script I run my javascript file in NodeJS:
"start": "node index"
The package. json file should look like this if you followed all the steps correctly:
At this point, the start script won’t work. We need to create index.js and add the following code:
⇒ This code will get the data from another service and expose it via a NodeJS Express service by using the SCP CF destination service.
⇒ I’m not going to explain the code in detail. It comes from the example of Marius Obert’s blog where he already explains the code: https://blogs.sap.com/2018/10/08/using-the-destination-service-in-the-cloud-foundry-environment/
const express = require('express');
const rp = require('request-promise');
const xsenv = require('@sap/xsenv');
const app = express()
const port = process.env.PORT || 3000;
const dest_service = xsenv.getServices({ dest: { tag: 'destination' } }).dest;
const uaa_service = xsenv.getServices({ uaa: { tag: 'xsuaa' } }).uaa;
const sUaaCredentials = dest_service.clientid + ':' + dest_service.clientsecret;
const sDestinationName = 'My-Destination';
app.get('/data', (req, res) => {
return rp({
uri: uaa_service.url + '/oauth/token',
method: 'POST',
headers: {
'Authorization': 'Basic ' + Buffer.from(sUaaCredentials).toString('base64'),
'Content-type': 'application/x-www-form-urlencoded'
},
form: {
'client_id': dest_service.clientid,
'grant_type': 'client_credentials'
}
}).then((data) => {
const token = JSON.parse(data).access_token;
return rp({
uri: dest_service.uri + '/destination-configuration/v1/destinations/' + sDestinationName,
headers: {
'Authorization': 'Bearer ' + token
}
});
}).then((data) => {
const oDestination = JSON.parse(data);
const token = oDestination.authTokens[0];
return rp({
method: 'GET',
uri: oDestination.destinationConfiguration.URL + "/MyOtherService",
headers: {
'Authorization': `${token.type} ${token.value}`
}
});
}).then((result) => {
res.send(result);
}).catch((error) => {
res.send("error: " + JSON.stringify(error));
});
});
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
This works perfectly fine on SCP CF. But when you run it locally, it is not able to find your destination:
Solution
As mentioned earlier in this blog, it is not a big thing to solve this problem. But as I was not able to find a well documented solution easily I think it’s still worth to share how to solve this.
Just as a side note, you are maybe thinking that this could be solved by using the approuter. In this case I want to use the destination service in my NodeJS app and not expose the external service. Defining a route in the configuration of the approuter will expose the complete external service.
Follow the next steps to solve this problem:
1. Create a destination to your external service
Create your destination
2. Create a destination instance
a. You can do this via the SCP Cockpit
Go to your space -> service marketplace
Select instances
Click on new instance and follow the wizard
b. Or by using the command:
cf create-service destination lite my-destination-service
Using the SCP cockpit or the CF CLI will both create the destination service which will be visible in the service instances of the destination dervice:
3. Get the VCAP details of the destination instance
a. In SCP Cockpit (starting from the destination instance that you created in the previous step)
b. via Command line
cf create-service-key my-destination-service my-destination-service-key
cf service-key my-destination-service my-destination-service-key
4. Do the same for the XSUAA service
cf create-service xsuaa application my-uaa-service
cf create-service-key my-uaa-service my-uaa-service-key
cf service-key my-uaa-service my-uaa-service-key
5. Create a file default-env.json and add the VCAP details in it. Fill in the service keys of each service in the “credentials” section. This will be used to get to all the details of the used services when running the application locally.
{
"VCAP_SERVICES": {
"xsuaa": [
{
"name": "my-uaa-service",
"label": "xsuaa",
"tags": [
"xsuaa"
],
"credentials": <my-xsuaa-service-key>
}
],
"destination": [
{
"name": "my-destination-service",
"label": "destination",
"tags": [
"destination"
],
"credentials":<my-destination-service-key>
}
]
}
}
6. The file only is not enough. It needs to be loaded into the NodeJS app locally and use the bounded services when running in CF. Add the following line in your code to do this:
xsenv.loadEnv();
Run “node index” to test again:
Now you can test your app locally! 🙂
You probably also want to know if the apps works in CloudFoundry. Before deploying this, you need to add a manifest.yaml file and define the used services to it. In our example, we use the xsuaa and the destination service. The xsuaa is used to get access to the destination service.
Create manifest.yaml like this:
Now the app can be deployed to CF with the following command:
cf push dest-demo-nodejs –random-route
Result in SCP:
Full project: https://github.com/lemaiwo/SCP-CF-NodeJS-Example
You could also consider doing this by using the Cloud Application Programming model. With CAP you will be able to define the structure of your API as an OData V4 service.
Example where I do exactly the same with CAP:
https://github.com/lemaiwo/SCP-CF-CAP-NodeJS-Example
When using CAP to do this will still requires the config default-env.json. The big difference when using CAP, is that you do not need to use the “loadEnv” function. This is being handled by the CAP framework.
Not needed:
xsenv.loadEnv();
In the example with CAP, I also used another solution for calling the external API. I used the CF Axios library which was created by a former colleague of mine. Big thanks to Joachim Van Praet for this one!
If you want to take it to the next step and setup authentication, you can follow this:
https://cap.cloud.sap/docs/node.js/authentication
You can also use MTA for deploying to SCP CF, follow these steps: https://cap.cloud.sap/docs/advanced/deploy-to-cloud
Many thanks! Very useful for me. For using the destination into an UI5 app in additional HTML5 module the steps of "Solution" would be the same, doesn't it?
Yes, but in that case you will also need the approuter
Hi Wouter,
We are both looking for the same or similar thing! Is your external service an OData service? If that's the case, you might want to look at my 2 part series blog -- https://blogs.sap.com/2020/05/26/cap-consume-external-service-part-1/ where I relied purely on CAP Model.
Thanks and regards,
Jhodel
Thanks, I was using the SCIM API of IAS which has no edmx file... It is a REST service but not a fully compatible OData service. I looked at your solution but I was not able to use it.
I knew it! I asked the CAP development team about non-OData support but sadly it’s not supported yet. We just need to go through manual process like the one you outlined here. Thanks for your detailed blog btw!
Hi Wouter,
Awesome blog. Thanks for this.
I am trying out a similar scenario and have cloned your project to extend but stuck with an issue where I am getting the below error when the run the app locally. I followed Jodel Cailan's Blog on consuming the service using app router and the same destination works and returns back the service but the scenario is to make this call to the service from inside the node js function.
https://github.com/lemaiwo/SCP-CF-CAP-NodeJS-Example
I have put up the question here for the same.
Please see the scenario details here.
Would really appreciate if you could provide any guidance on this.
I recently had similar problems with the Axios library. Have you tried it this way: https://github.com/lemaiwo/SCP-CF-NodeJS-Example/blob/master/index.js ? Doing the steps myself helped for me.
Thanks Wouter,
That code example worked perfectly. I was also trying to test a connection setup to my on-premise ERP system using the cloud connector. Could you please point me to any direction if you have done the same?
Appreciate your help with all this 🙂
Best Regards,
Deepak
Hi Deepak – I am also having an issue testing locally with the cloud connector/on-prem scenario. Presumably it is because our local networks can’t resolve the <hostname>:<port> of the cloud connector virtual host.
I was planning on trying something with using default-env.json to create fake destinations that point to the system directly (for instance, if i log onto the vpn) but wanted to double check if there were any good solutions already out there.
Did you end up figuring something out?
Hi Wouter,
Awesome Blog post!
I want to use destination service in my local project which has node.js module and SAPUI5 module. I Created service key in destination service and using it i created user provided service with tag destination. It is working fine for Node modules but, for sapui5 module it is using approuter and somehow approuter is not fetching the details from user provided service.
Do you know anything we could do here which can help approuter to use destination service using user provided service?
Thanks,
Rajdeep Bhuva
Hi Rajdeepkumar Bhuva ,
Try using the official SAP Cloud SDK for Javascript https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destination-js-sdk. It has extended functionality, supported by SAP and it's mostly Open Source https://github.com/SAP/cloud-sdk besides type-safe client, aka VDM https://www.npmjs.com/search?q=%40sap%2Fcloud-sdk-vdm-*
Best,
Artem
Hi Wouter Lemaire ,
Have you considered an official SAP Cloud SDK library for your case? It has abstractions for Destinations and Proxies handling similar to the library you're referring to. You can also check out our Github repo: https://github.com/SAP/cloud-sdk.
Additionally, we provide a generator for OData services as well as a set of pre-generated libraries of S/4HANA Cloud and some other SAP services.
Best,
Artem
Hey Wouter - great stuff as usual! Appreciate your contributions to the community
Hi Wouter,
Kind Regards,
Hi Vidhya,
I think the problem here is that you are trying to require a NodeJS module in a non-Node application (SAPUI5 app). If you want to access data that is provided by the @sap/xsenv module, then you'll probably have to implement a service in your CF environment that can access the xsenv properties and return them to the UI5 app
Kind regards
Jakob
Wouter Lemaire thanks for your great blog, especially your link to https://github.com/lemaiwo/SCP-CF-CAP-NodeJS-Example was really helpful for my use case: had to post data within a CAP app to a rest API that requires an X-CSRF token and an x-www-form-urlencoded. Unfortunately CAP doesn't offer there any flexibility.
Regards, Andreas
Hello Wouter,
I am getting this error.