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
Original Article:
https://blogs.sap.com/2020/05/28/use-the-destination-service-in-nodejs-locally/