Skip to Content
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!

https://blogs.sap.com/2019/11/13/consuming-destinations-in-cloud-foundry-using-axios-in-a-nodejs-application/

 

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

13 Comments
You must be Logged on to comment or reply to a post.
    • 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.

    1. The OData service I am trying to consume is a HANA XSOData service in Neo Platform. The destination to the neo HANA DB is configured in CF sub-account
    2. I have also created a destination and UAA service and updated the default.env with the service keys
    3. Have also created the dependencies in the mta.yaml file
      ## Generated mta.yaml based on template version 0.2.0
      ## appName = destination-demo
      ## language=nodejs; multiTenant=false
      ## approuter=
      _schema-version: '3.1'
      ID: destination-demo
      version: 1.0.0
      description: "Demo SCP CF destination local"
      parameters:
        enable-parallel-deployments: true
        
        
      build-parameters:
        before-all:
         - builder: custom
           commands:
            - npm install
            - npx cds build
      
      modules:
       # --------------------- SERVER MODULE ------------------------
       - name: destination-demo-srv
       # ------------------------------------------------------------
         type: nodejs
         path: gen/srv
         parameters:
           memory: 128M
           disk-quota: 128M
         properties:
           EXIT: 1  # required by deploy.js task to terminate
         requires:
            # Resources extracted from CAP configuration
            - name: demo-destination
            - name: demo-uaa   
      resources:
        - name: demo-uaa
          type: org.cloudfoundry.managed-service
          parameters:
            path: ./xs-security.json
            service-plan: application
            service: xsuaa
      
        - name: demo-destination
          type: org.cloudfoundry.existing-service
      
      
      
      ​
    4. Below is the node js code
      const SapCfAxios = require('sap-cf-axios').default;
      const destination = SapCfAxios('CF_Test');
      
      module.exports = (srv) => {
          srv.on('CatalogService.getAPICall', async (req) => {
              let response = await destination({
                  method: 'GET',
                  url: "/CF_Test/TestOService",
                  headers: {
                      "content-type": "application/json"
                  }
              });
              return JSON.stringify(response.data)
              /*return response.data.Resources.map((user) => ({
                  id: user.id,
                  userName: user.userName,
                  name: user.displayName,
                  addresses: user.addresses,
                  emails: user.emails.map((mail) => { return { value: mail.value } })
              }));*/
          })
      }​

    Would really appreciate if you could provide any guidance on this.

      • 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