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: 
Chaim_Bendelac
Advisor
Advisor
INFO: This blog is part of a tutorial series for the standalone SAP Graph service on SAP BTP. Recently, SAP released a version of Graph in SAP Integration Suite with better user experience and functional enhancements (see this blog for details: https://blogs.sap.com/2023/02/22/announcing-graph-in-sap-integration-suite).


Hello!

This is the third part of our developer tutorial on SAP Graph, the API for SAP's Integrated Intelligent Suite.  Please refer to part 1 of this tutorial for an introduction to SAP Graph, to part 2 for an introduction to the programming interface of SAP Graph, and to the information map for an introduction to the entire tutorial series.

Here, in part 3 of this tutorial, we are going to build the rudimentary basics of a classical enterprise extension web app: a list-details-navigate application.

SAP Graph Business Data Graphs


In part 2, we accessed data from a preconfigured SAP Graph sandbox server via SAP API Business Hub. We even embedded this server URL into our code. Anybody with an API key can access the sandbox data, without any further authentication or authorization.

Of course, this is not a secure way of accessing confidential business data. Therefore, in this part of the tutorial, we will access SAP Graph securely, via the oAuth protocol. OAuth doesn’t share password data but instead uses authorization tokens to prove an identity between clients and services like SAP Graph, and supports Single Sign On. With oAuth, you effectively approve your browser to interact with SAP Graph on your behalf without giving away your password.

SAP Graph is a multitenant service. Customer administrators use the SAP Business Technology Platform (BTP) to subscribe to SAP Graph, by configuring one or more business data graphs. We will discuss this topic in more detail in a later part of this tutorial, but at this time it is important to understand that the business data graph is the key to a specific landscape of customer systems. Most customers will configure multiple landscapes, for instance for development, staging and productive usage.

Each SAP Graph business data graph is unique with unique credentials, such as its URL, its business data graph identifier and various secrets/tokens.

credentials.js


To programmatically access the data from an SAP Graph business data graph, an application requires these credentials. How do you get them? A file containing them, credentials.json, is created during the process of setting up and configuring the business data graph. You receive this file and the business data graph identifier from the BTP administrator or key user who configured the specific business data graph you want to access.

Save the credentials file in the src folder of your project (you can use the existing src folder in which we developed our very first hello Graph server-side application, or create a new source folder in your project, up to you).

Note: If you are interested in executing this tutorial, and don't have access to your own SAP-managed data sources, contact the SAP Graph team at sap.graph@sap.com. Upon request, and for a limited time, we can provide you with a credentials.json file to access a dataset using the preconfigured business data graph.

auth.js


In the same src folder, create a file called auth.js, paste in the following boilerplate (standard) code, and save:
const credentials = require("./credentials.json");
const fetch = require("node-fetch");
const Cookies = require("universal-cookie");
const CALLBACK_URI = "/myCallbackURI";
const CookieName = "SAPGraphHelloQuotesCookie";

class Auth {
constructor() {
this.clientId = credentials.uaa.clientid;
this.clientSecret = credentials.uaa.clientsecret;
this.authUrl = credentials.uaa.url;
}

getToken(req) {
const cookies = new Cookies(req.headers.cookie);
return cookies.get(CookieName);
}

async fetchToken(code, redirectUri) {
const params = new URLSearchParams();
params.set('client_id', this.clientId);
params.set('client_secret', this.clientSecret);
params.set('code', code);
params.set('redirect_uri', redirectUri);
params.set('grant_type', 'authorization_code');

const response = await fetch(`${this.authUrl}/oauth/token`, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: params
});

const json = await response.json();
return json.access_token;
}

getMiddleware() {
return async (req, res, next) => {
const redirectUri = `${req.protocol}://${req.get("host")}${CALLBACK_URI}`;
if (req.url.startsWith(CALLBACK_URI)) {
const code = req.query.code;
if (code) {
const token = await this.fetchToken(code, redirectUri);
res.cookie(CookieName, token, { maxAge: 1000 * 60 * 120, httpOnly: true, path: "/", });
}
res.redirect("/");
} else if (!this.getToken(req)) {
res.redirect(`${this.authUrl}/oauth/authorize?client_id=${encodeURIComponent(this.clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code`);
} else {
next();
}
};
}
}

module.exports = Auth;

As you can see, this boilerplate authentication code refers to several pieces of information which are extracted from the credentials.json file, and are used to log the user of your application in, using single sign on, according to the specifics of the business data graph. For the user's convenience, it also saves the obtained access token as a cookie, so that it can be used until it expires.

graph.js


We will re-use the Graph class that we saw in part 2 of this tutorial, but, now that we are required to authenticate the user before we can use the business data graph, we need to make a few small changes. Copy the following text into graph.js and save.

const fetch = require("node-fetch");
const credentials = require("./credentials.json");
const apiUrl = credentials.uri;
const dataGraphId = "v1"; // example, modify accordingly

class Graph {
constructor(auth) {
this.auth = auth;
this.apiUrl = apiUrl;
this.dataGraphId = dataGraphId;
}

async get(req, entity, params) {
const token = this.auth.getToken(req);
const url = `${this.apiUrl}/${this.dataGraphId}/${entity}${params ? `?${params}` : ""}`;
console.log(url) //for debugging
const options = {
method: "get",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
}
};
const response = await fetch(url, options);
console.log(`${response.status} (${response.statusText})`) // for debugging
const json = await response.json();
return json;
}
}

module.exports = Graph;

You can see that we made two small changes. The SAP Graph URL is now determined from the credentials of the specific business data graph, and the authorization token, obtained during user authentication, is passed to SAP Graph. You may have to modify the business data graph identifier string from "v1" in the above code, before saving the file.

helloQuotes.js


Now are we finally ready to build the rudimentary basics of a classical three-page enterprise extension web app: a list-details-navigate application. This is what it will eventually look like:


 

Don't expect fancy code, with all the necessary error and exception handling of a robust, production-ready application. Our goal is to show how easy it is to just create small business applications using SAP Graph; we will discuss the finer aspects of robust SAP Graph clients in another part of this tutorial.

We will first establish the skeleton of our application, in a file we will call helloQuotes.js:
// Hello Quote - our first SAP Graph extension app

const express = require("express");
const Graph = require("./graph");
const Auth = require("./auth");
const app = express();
const port = 3003;

const auth = new Auth();

app.use(auth.getMiddleware());
const graph = new Graph(auth);

// ------------------ 1) get and display a list of SalesQuotes ------------------

// ------------------ 2) show one quote and its items ------------------

// ------------------ 3) navigate to the product details for all the items in the quote ------------------


app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
});


This code doesn't do anything interesting. It basically logs in the user, using the standard authentication that we just discussed, and then listens on port 3003 for web (REST) requests. To make the code work, we need to install request handlers.

Using the express framework, we now create three such handlers, corresponding to three different expected URLs:

  1. The root (/)

  2. Request for a quote: /quote/…

  3. Request for a quote's product details: /quote… /product


Here is the first request handler:
// ------------------    1) get and display a list of SalesQuotes   ------------------ 

app.get('/', async (req, res) => {
const quotes = await graph.get(req, "sap.graph/SalesQuote", "$top=20");
const qlist = quotes.value.map(q => `(${q.netAmount} ${q.netAmountCurrency})`).join("");
res.send(` <h1>Hello Quotes</h1> ${qlist} `); });

The handler will be fired if the browser requests the root document ("/"). What does the code do? It fetches the first 20 quotes (sap.graph/SalesQuote), and then wraps the resulting information (date and total amount) in HTML, and returns it.

Go ahead, paste this handler into the app skeleton, save, and run the server-side app on your terminal console as follows:
node helloQuotes.js

Open a new browser tab, and enter the URL http://localhost:3003. If all went well you will now see a list of dates and corresponding amounts in the browser.

That was nice, but not very interesting. To turn your app into an interesting list-details app, modify the qlist assignment above to introduce a link, as follows:
const qlist = quotes.value.map(q => `<p> <a href="https://blogs.sap.com/quote/${q.id}">${q.pricingDate} </a>
(${q.netAmount} ${q.netAmountCurrency}) </p>`).join("");

Now, when the user clicks on one of the quotes, your app will be called again, and this time the URL will match '/quote…'.

Let us now also introduce our second and third handlers:

// ------------------ 2) show one quote and its items ------------------

app.get('/quote/:id', async (req, res) => {
const id = req.params.id;
const singleQuote = await graph.get(req, `sap.graph/SalesQuote/${id}`, "$expand=items&$select=items");
const allItemLinks = singleQuote.items.map(item => `<p><a href="https://blogs.sap.com/quote/${id}/item/${item.itemId}"><button>Product details for item ${item.itemId}: ${item.product}</button></a></p>`).join("");
res.send(`
<h1>SalesQuote - Detail</h1>
<h4><code>id: ${id}</code></h4>
${allItemLinks}
`);
});

// ------------------ 3) navigate to the product details for an item in the quote ------------------

app.get('/quote/:id/item/:itemId', async (req, res) => {
const id = req.params.id;
const itemId = req.params.itemId;
const product = await graph.get(req, `sap.graph/SalesQuote/${id}/items/${itemId}/_product`, "$expand=distributionChains");
res.send(`
<h1>Product Detail</h1>
<h4><code>For SalesQuote ${id} and item ${itemId}</code></h4>
<pre><code>${JSON.stringify(product, null, 2)}</code></pre>
`);
});


 

The OData query at the heart of the second handler uses the $expand query parameter to fetch the details of the quote, including what was quoted (items). The product id in the quote is then used in the third handler to navigate across the business graph, to fetch the detailed product information from the product catalog. In both cases, the data is just dumped to the screen as JSON, but evidently, a real app would format the information much more nicely.

Go ahead, make the change in the first handler, and then paste the second and third handlers in your code, save the file, restart the service, and refresh the localhost:3003 page in the browser. Voila! Your app is live.

Note again, how you, as a developer, never had to ask yourself where the data came from. You just navigated from a quote object to a product object, without any effort. The landscape that you accessed via the configured business data graph may have managed quotes in SAP Sales Cloud, or in SAP S/4HANA, and the product catalog may have been, theoretically, in yet another system. You simply don't have to care.



Chaim Bendelac, Chief Product Manager – SAP Graph


Visit the SAP Graph website at http://explore.graph.sap/


Contact us at sap.graph@sap.com







 
5 Comments