Skip to Content
Technical Articles
Author's profile photo Jhodel Cailan

CAP: Consume External Service – Part 1

Updated on 9 November 2022

The openSAP course Building Applications with SAP Cloud Application Programming Model has just concluded. Overall, the course is a good overview of what the CAP Model has to offer. It has some hands-on demo that are beginner-friendly. However, to be able to get a deeper understanding of the moving parts and configuration aspect of the CDS framework, one has to work on their own use case and do their own development.

So that’s what I did, and I decided to focus on consuming external OData Service using the Cloud Foundry Destination Service. Apparently, there’s quite a number of pitfalls I stumbled into, hence, I’m sharing my solution on this blog.

Special thanks to David Kunz for helping in troubleshooting and guiding me on my issue!

 

 

Prerequisites


  • SAP Business Application Studio / Visual Studio Code
  • SAP BTP Account – Cloud Foundry Environment

 

Blog Post Series


 

Create the Node.js Application


If you have been working with OData Services, then you are probably already familiar with the NorthWind OData Service. This is a good starting point for learning how to use/consume a public OData service, and in this scenario, I will be using the OData V4 NorthWind service.

https://services.odata.org/Experimental/OData/OData.svc/

We will start to create a Node.js application from scratch using Cloud Application Programming Model. I’m using Visual Studio Code as my local development IDE, but you can use SAP Business Application Studio and still achieve the same results.

  • 1. Initialize your project using the CDS CLI tool
> cds init

The project was generated with the skeleton of the project. As with any Node.js modules, the package.json file is that starting point of your Node.js application, the rest of the folders are empty, and in our case, we will only need to use the srv folder.

 

  • 2. Get the service document or EDMX file of our OData Service by using the $metadata tag. And then save the xml data as NorthWind.edmx.
https://services.odata.org/Experimental/OData/OData.svc/$metadata

  • 3. Use the cds import command to import the .edmx file into your project
> cds import NorthWind.edmx

There are a few things that happened during the import.

  • The NorthWind.edmx file was imported to folder srv/external

  • The NorthWind.csn file was generated based on the edmx file and was also inside the folder srv/external. CSN stands for Core Schema Notation, and this is the Schema Definition that is understood and used by the CDS framework.

  • The package.json file was updated with the cds.requires configuration for the imported external service called NorthWind.

 

  • 4. Create a catalog-service.cds file to define our Catalog Service
using {NorthWind as external} from './external/NorthWind.csn';

service CatalogService {

    @readonly
    entity Products as projection on external.Products {
        key ID, Name, Description, ReleaseDate, DiscontinuedDate, Rating, Price
    };

}

Here, we need to import the NorthWind.csn definition to be used as a projection for our Products entity.

 

  • 5. Create a custom handler for our Catalog Service and implement the READ event of Products entity. Name the file catalog-service.js.
const cds = require('@sap/cds');

module.exports = cds.service.impl(async function() {
	const { Products } = this.entities;
	const service = await cds.connect.to('NorthWind');
	this.on('READ', Products, request => {
		return service.tx(request).run(request.query);
	});
});

It is important to name the handler file the same as the .cds counterpart as the CDS framework expects this.

 

Test the application using Mock Data


  • 1. Mock the data for our catalog service to be able to test our application locally and in isolation. Create a folder data under the external folder. And in this data folder, create a new file called NorthWind-Products.csv with contents below:
ID;Name;Description;ReleaseDate;DiscontinuedDate;Rating;Price
0;Bread;Whole grain bread;1992-01-01T00:00:00Z;;4;2.5
1;Milk;Low fat milk;1995-10-01T00:00:00Z;;3;3.5

It is important that the file follows the proper naming convention i.e. <name of the service>-<name of the entity>.csv as this is expected by the framework.

 

  • 2. We are now ready to test our application. So start it up using the cds command:
> cds watch

If you encounter an error log after executing the above command, then that’s just about right. It seems that when the framework generated the CSN file, there were some violations in the entity dependency and this is being highlighted in the terminal as an error.

I’m not quite sure why the framework would generate an erroneous .csn file, but it’s not the end of the world, the errors are quite easy to fix if you are familiar with OData definitions. Just follow along the error description as it describes which file has an error (NorthWind.csn) and which line (162), what artifact (NorthWind.Person) and what is the missing entity (NorthWind.Customer).

I’ve already gone through the pain of fixing it one-by-one and here is the fixed file: NorthWind.csn

Once you have fixed and saved the NorthWind.csn file, the node application is automatically restarted by the nodemon process, this is because we have started the application using cds watch command.

The application would start normally, as you can see in the screenshot below:

  • 3. Open your application in your browser using below URL:
http://localhost:4004

And click on the Products entity for the catalog service:

Here you will see the data that we mock for our Products entity. You can go back to the main page of the service and browse the external service entities, but there won’t be any data available because we only created mock data for Products entity.

 

Test the application using Real Data


  • 1. Modify the configuration in your package.json file to add the credentials.url property. Don’t forget to save the changes.
	"cds": {
		"requires": {
			"NorthWind": {
				"kind": "odata",
				"model": "srv/external/NorthWind",
				"[backend]": {
					"credentials": {
						"url": "https://services.odata.org/Experimental/OData/OData.svc"
					}
				}
			}
		}
	}
  • 2. The connection to external services requires the SAP Cloud SDK node module, so you need to install that dependency first using below command:
> npm install @sap-cloud-sdk/http-client
  • 3. Run the app using cds watch –profile backend, then head back to your browser to test the application.

As you can see, the starting page of the node service is only showing catalog path. This is an expected behaviour since you are no longer running on mock data. The addition of the credentials.url property makes the application use the real external service’s data and the north-wind path is now gone. This is the exact situation once you deploy the node application to cloud foundry environment.

Now let’s click on the Products entity to view the data.

This time the Products entity is displaying the actual list of products as if you run the original NorthWind OData Service.

You can verify this by using the below URL in a separate browser:

https://services.odata.org/Experimental/OData/OData.svc/Products

 

Closing


Now you know how to consume an external OData Service using the CAP Model. We are able to test our Node.js application using Mock data and also test it with real data just by doing a simple configuration change.

On my next blog, I will show the deployment to cloud foundry and setting up the destination using the destination service.

UPDATE:

Here’s the part 2 –> Consume External Service – Part 2

 

~~~~~~~~~~~~~~~~

Appreciate it if you have any comments, suggestions, or questions. Cheers!~

Assigned Tags

      48 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo David Kunz
      David Kunz

      Hi Jhodel,

      Great job on this beautiful blog post, well done!

      Best regards,
      David

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Thanks David! 😀

      Author's profile photo Pierre Dominique
      Pierre Dominique

      Thanks Jhodel for this detailed blog post. I'm eagerly waiting for the next part. 🙂

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Thanks for feedback Pierre! The Part 2 is now available!

      Author's profile photo giovanni di marco
      giovanni di marco

      Hi,

      is there something similar for java? How can I access destinations in Java.

      Thanks

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Giovanni,

      I'm not into Java SDK of CAP but doing a quick look at the documentation, it looks like you can!

      https://cap.cloud.sap/docs/java/consumption-api#consuming-services

      Have a go with the example, and if that doesn't work for you, then you might want to look at the sample implementation in Java on how to manually consume the Destination Service configuration:

      https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/7e306250e08340f89d6c103e28840f30.html#loio7e306250e08340f89d6c103e28840f30__section_GenerateJWT

      Hope that helps!

      Author's profile photo Lalit Goyal
      Lalit Goyal

      Hi Jhodel,

       

      I still see local data in the browser even after following the steps.

       

      Regards,

      Lalit

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Lalit Goyal ,

      Are you sure you have set up successfully the “url” in the credentials section? Can you share your current cap project?

      Author's profile photo Lalit Goyal
      Lalit Goyal

      Hi Jhodel,

       

      I am cleaning the project. meanwhile, I am sending you package.json file.

      {
      "name": "HelloWorld",
      "version": "1.0.0",
      "description": "A simple CAP project.",
      "repository": "<Add your repository here>",
      "license": "UNLICENSED",
      "private": true,
      "dependencies": {
      "@sap/cds": "^3",
      "@sap/hana-client": "^2.5.86",
      "@sap/hdi-deploy": "^3.11.5",
      "express": "^4"
      },
      "scripts": {
      "start": "npx cds run"
      },
      "devDependencies": {
      "sqlite3": "^5.0.0"
      },
      "cds": {
      "requires": {
      "NorthWind": {
      "kind": "odata",
      "model": "srv/external/NorthWind",
      "credentials": {
      "url": "https://services.odata.org/Experimental/OData/OData.svc"
      }
      }
      },
      "build": {
      "target": "gen",
      "tasks": [
      {
      "src": "srv",
      "for": "node-cf",
      "options": {
      "model": [
      "db",
      "srv",
      "app"
      ]
      }
      },
      {
      "src": "db",
      "for": "hana",
      "options": {
      "model": [
      "db",
      "srv",
      "app"
      ]
      }
      }
      ]
      }
      }
      }

      Regards,

      Lalit

      Author's profile photo Lalit Goyal
      Lalit Goyal

      Hi Jhodel,

      I am attaching the github link. https://github.com/lalitgoyal2005/experiments.git

      Thanks,

      Lalit

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Lalit Goyal ,

      I must say that your issue got me puzzled for quite a while. The issue is due to missing “s” in exports:

      const cds = require('@sap/cds');
      
      module.exports = cds.service.impl(async function() { // <--- right here
      	const { Products } = this.entities;
      
      	const service = await cds.connect.to('NorthWind');
      	this.on('READ', Products, request => {
      		return service.tx(request).run(request.query);
      	});
      });

      The hint was that, when entering in debug mode, it will never execute any lines of code in this file because the function was never exported. And apparently, cds framework won’t complain and assume that you are running using mock data.

      Author's profile photo Lalit Goyal
      Lalit Goyal

      Ah. That's my bad. Thanks a lot for your help Jhodel. It works now.

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Happy to know it works now! Cheers!~

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      Jhodel Cailan

      Hi Jhodel,

      first of all thank you very much for this wonderful tutorial.

      I have just a short question: Does the Fiori Preview work for you, if

      you want to display the northwind products in the table? I get data when clicking the service

      url, but not within the fiori preview - although I annotated correctly.

      Only, when using CSV it works...

       

      BR

      Rufat

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Thanks Rufat Gadirov for your comment!

      I believe I already answered your question somewhere else.

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      Yes, thank you, this question is now obsolete. 🙂 I had to change the annotations and now fiori preview also works fine.

      Author's profile photo Budd Fung
      Budd Fung

      Which annotation did you change? I got the same issue as well. Thanks

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      Please see here: https://answers.sap.com/questions/13190095/fiori-preview-of-external-odata-northwind-product.html?childToView=13280080#answer-13280080

       

      Budd Fung 

       

      BR

      Rufat

      Author's profile photo Budd Fung
      Budd Fung

      I got the same issue. Could you tell me where the answer is?

      Author's profile photo Hieu Ngo Xuan
      Hieu Ngo Xuan

      Hi Jhodel Cailan,
      thank’s very much, this is wonderful tutorial!

      But now, I wonder if this is a way to implement microservice in CF environment, ODATA in old version is suitable for micro services. Micro-services, however, are not suitable for odata. I mean there’s really nothing stopping you from exposing the OData in a microservice, but then we have CDS, CDS can fix this and make the service’s data smaller or custom flexible, so do you think it is idealy solution for micro service in CF?

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Thanks for your comment Hieu Ngo Xuan !

      I'm not sure if I understand your question, but I will try to answer.

      I have a feeling that you are taking the literal sense of micro-services in the context of OData. Microservices is a design architecture as opposed to the traditional monolith architecture. Here's a good reference for microservice architecture.

      If you work with SAP CF environment, whether you like it or not, you are bound to adopt the microservice architecture. You got SAP Portal, App Router, Destinations, Connectivity, and so much more that is already considered microservices. As matter of fact, the way the CAP CDS database objects are persisted in the HANA DB is aligned with the microservices architecture -- through the means of containerization, which is called HDI Container.

      Author's profile photo Hieu Ngo Xuan
      Hieu Ngo Xuan

      Thank you very much for your answer! It gave me a better picture of micro service in SAP CF environment.

      Best Regards!

      Hieu

      Author's profile photo jyothir aditya k
      jyothir aditya k

      Hi Jhodel Cailan :I am trying on VS code, and while using ” CDS import” it is saying ” No such file: NorthWind.edmx’. I have saved edmx file in /srv folder. Can you let me know what needs to be done or i am missing anything ? Also i observed @sap/cds-dk is missing, but I am unable to install it

      Regards,

      Aditya

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi jyothir aditya k

      Execute command below and let me know what you get:

      > cds --version
      Author's profile photo rini khare
      rini khare

      Please try saving your file in root folder instead of saving in srv folder.

      Author's profile photo Andres Chacon
      Andres Chacon

      Hello Jhodel Cailan thanks for the useful blog.

      I'm, however, having some problems when trying to set the real data from the OData service. There is the thing: if I set the mock data everything works fine and I can access my exposed paths:

      But when I set the credentials in the package.json I see the following:

      I'm using Business Application Studio in SCP CF.

      Do you have some idea of what could be generating this behavior?

      Best regards.

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Andres Chacon, thanks for your comment!

      What you are getting is the standard behavior. When you don't provide credentials, this is interpreted by CAP that you want to run using mock data, hence, the external service will be mocked by default. As a result of this behaviour, you will the external service on the root page of the service (your first screenshot). But if you run the cap service in a connected scenario (not mocked), the external service will be hidden (as it should be). Technically, the service you want to expose is the cap service you created (in your case, it doesn't looked like you created any), and not the external service you are supposed to consume from within CAP.

      Author's profile photo Andres Chacon
      Andres Chacon

      Hello Jhodel, thanks for your reply. I already notice my error, and yes, was related in some way to the inexistence of a service definition.

      The problem was that I created the catalog-service.cds and catalog-service.js files outside of srv folder, so when I tried to test the app with the real service it does not appear anything. As soon as I put my catalog files inside of serv folder it worked fine.

      Thanks Jhodel, great blog posts.

      Best regards.

      Author's profile photo Andreas Gall
      Andreas Gall

      Hi Jhodel Cailan

      thanks for your blog. However when I try to play it through I already got the first error while importing EDMX file. I'm using the SAP Business Application Studio to play around.

      With the command: cds import NorthWind.edmx I receive this error:

      [INTERNAL ERROR] TypeError: copy_or_move is not a function
          at _copy_to_srv_external (/local/npm/lib/node_modules/@sap/cds-dk/bin/import.js:148:9)

      With the initialization of the project with "cds init" these version of @sap/cds are used:

      @sap/cds: 5.1.5
      @sap/cds-compiler: 2.2.8
      @sap/cds-dk: 4.0.7
      @sap/cds-foss: 2.3.1
      @sap/cds-runtime: 3.1.2
      Node.js: v10.23.0

      Any ideas how to solve the import error I receive?

      Best regards, Andreas

      Author's profile photo Andreas Gall
      Andreas Gall

      There's a solution to make it work again. The "out of the box" version of the cds-dk obviously doesn't work properly in this scenario.

      An update to the latest version did the trick:

      npm i -g @sap/cds-dk

      Thanks, Gregor Wolf for this hint.

      Regards, Andreas

      Author's profile photo Ramon Beijer
      Ramon Beijer

      hi Jhodel Cailan or maybe Gregor Wolf is it also possible in the setup like here above to do create or update requests against the external service? Running on cloudfoundry with a destination.

      I have defined an action and in that action I would like to do a create in the external service

      If so is there some example code for that somewhere?

      Or do I need to use the Cloud SDK for that

      tnx in advanced

      Ramon

      Author's profile photo Gregor Wolf
      Gregor Wolf

      If your create action isn't to complex it should work directly with CAP. For more complex stuff use the SAP Cloud SDK.

      Author's profile photo Ahmed Ali Khan
      Ahmed Ali Khan

      On CDS IMPORT Northwind.edmx, I am getting

      ""Edm.GeographyPoint" is not supported (in element:"Location")"

      Can anyone suggest what went wrong?
      CDS%20IMPORT%20ERROR

      CDS IMPORT ERROR

      Author's profile photo Jacob Binu
      Jacob Binu

      Nice blog Jhodel....

      Author's profile photo Arthur Geeraert
      Arthur Geeraert

      Is there a way to use an external odata service from the abap system in stead of a csv file?

      Author's profile photo AMIT AGARWAL
      AMIT AGARWAL

      It worked perfectly fine with mock data. But when I entered the URL in package.json, i get below error. Any suggestion to fix this please?

      <code>502</code>
      <message>Error during request to remote service: Cannot find module '@sap-cloud-sdk/http-client' Require stack: - /home/user/projects/Proj1/node_modules/@sap/cds/libx/_runtime/remote/utils/client.js - /home/user/projects/Proj1/node_modules/@sap/cds/libx/_runtime/remote/Service.js - /home/user/projects/Proj1/node_modules/@sap/cds/lib/index.js - /home/user/projects/Proj1/node_modules/@sap/cds/bin/cds.js - /home/user/.node_modules_global/lib/node_modules/@sap/cds-dk/bin/cds.js - /home/user/.node_modules_global/lib/node_modules/@sap/cds-dk/bin/watch.js</message>
      </error>
      Author's profile photo AMIT AGARWAL
      AMIT AGARWAL

      In the meantime i got it resolved by below command:

      npm install @sap-cloud-sdk/http-client @sap-cloud-sdk/util

      During this I also faced another error  regarding registry,  for which i used below commands:

      npm cache clear --force

      npm config set registry https://registry.npmjs.org

      Author's profile photo Anushka Bhattacharya
      Anushka Bhattacharya

      Hi Jhodel Cailan ,

      Could you please share steps to generate edmx for Integration Flow Content specific to a tenant. For our use case we need to consume data from Integration Flows as an external service. Is there any documentation that can help here?

      Author's profile photo Daniyal Ahmad
      Daniyal Ahmad

      Kindly provide any link for consume external rest api sap cap using java. Thanks

      Author's profile photo Ugur Hamarat
      Ugur Hamarat

      Hi Jhodel Cailan

      Do you know if it's possible to use @odata.draft.enabled while consuming the external APIs/Services?

      Kind regards,

      Ugur

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Ugur Hamarat

      It's not supported by CAP, however, I have built a node module that bridges this gap -- see @rizing/cds-extension

      Author's profile photo Ahmed Ali Khan
      Ahmed Ali Khan

      Hi Jhodel,

       

      I am still wondering what will happen to this CAP Application if there are new entities ot fields added to the service, since you're using a static .edmx file you application will not be able to cover the dynamic chages in the service,

      Can you suggest some solution for this kind of case?

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Hi Ahmed Ali Khan

      Technically you are supposed to regenerate the metadata then update the cap application. But if that is not possible, then you could introduce an entity with a field/property that holds an unstructured json data. But for this to work, you need to be able to update the original OData API and the app that consume the cap service will have to do the read/processing of the unstructured json data.

      Author's profile photo Ahmed Ali Khan
      Ahmed Ali Khan

      Hi Jhodel Cailan ,

      Thanks for replying, So we don't have any dynamic solution for this we have to import .edmx file again, Is there any other way to consume the external API using node js modules like axios etc. in srv.js file?

      Author's profile photo Jhodel Cailan
      Jhodel Cailan
      Blog Post Author

      Thanks pretty much it, either use cap nodejs api or do it your own way using node modules like axios.

      Author's profile photo Dirk Wiegele
      Dirk Wiegele

      Hello,

      I always get the error message when I switch to the real data and if I use the

      credentials.url "https://services.odata.org/Experimental/OData/OData.svc"

      I had to change it because the error message came backend profile not found

      With this configuration I can switch between backend and mock. Mock works Backend does not work

      My package.json look like this

      "cds": {
          "requires": {
            "[backend]": {
              "NorthWind": {
                "kind": "odata",
                "model": "srv/external/NorthWind",
                "credentials": {
                  "url": "https://services.odata.org/Experimental/OData/OData.svc"
                }
              }
            },
            "[mock]": {
              "NorthWind": {
                "kind": "odata",
                "model": "srv/external/NorthWind"
              }
            }
          }
        }
      Author's profile photo Lennard Schultes
      Lennard Schultes

      Hello,

      Thanks for the nice tutorial. I am using Mac and VS code. For me the generation of the csn file resulted in the sam bug as for Ahmed Ali Khan and the VS code terminal aborted. However the zsh shell did continue and generated the files as expected.

      Also the usage of the real data failed in the VS Code terminal and zsh did use the mock data. However removing the [backend] tag worked out for zsh shell and the result is as in the tutorial. If anyone knows the reason of this behaviour please let me know.

      Author's profile photo Mariam Id-Manssour
      Mariam Id-Manssour

      Hi Jhodel Cailan ,

       

      Thanks for the useful blog post. I tried to reproduce  the steps described in the Blog post  using a custom OData service from S/4 HANA On premise system  of the client that I’m trying to consume from  CAP Application.

      I have get the  EDMX file of OData Service by using the $metadata tag. And then save the xml data as imageUpdate.edmx

      Then I used the command cds import to import the service definition and I faced the following error.

      The imageUpdate.csn file was not  generated based on the edmx file  as expected only the edmx file location changed to the folder srv/external.

      Do you have any idea how to overcome this issue please ?

      Thanks and best regards,

      Mariam