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: 
LeonidGunko
Associate
Associate

Here, in this part of this tutorial, we are going to build the rudimentary basics of a classical enterprise extension web app: a list-details-navigate application. Please refer to Part 1 of this tutorial for an introduction to Graph in SAP Integration Suite, to the previous tutorial part for an introduction to the programming interface of Graph, and to the Information Map for an introduction to the entire tutorial series. 

Business Data Graphs 

In the previous part, we accessed data from a preconfigured Graph sandbox server via SAP Business Accelerator 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 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 Graph and supports Single Sign On. With oAuth, you effectively approve your browser to interact with Graph on your behalf without giving away your password. 

Graph is a multitenant service. Customer administrators use the SAP Business Technology Platform (BTP) to subscribe to Graph in SAP Integration Suite, by configuring one or more business data graphs. We discuss this topic in more detail in a separate part of this tutorial, but for this tutorial part 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 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 a 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). 

auth.js 

In the same src folder, create a file called auth.js, paste in the following boilerplate (standard) code, and save: 

 

 

 

import credentials from "./credentials.json" assert { type: "json" };
import fetch from "node-fetch"; 
import Cookies from "universal-cookie"; 
const CALLBACK_URI = "/myCallbackURI"; 
const CookieName = "SAPGraphHelloQuotesCookie"; 

export default 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(); 
            } 
        }; 
    } 
}

 

 

 

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 the previous part 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. 

 

 

 

import credentials from "./credentials.json" assert { type: "json" };
import fetch from "node-fetch";
const apiUrl = credentials.uri;
const dataGraphId = "v1"; // example, modify accordingly 

export default 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;
    }
}

 

 

 

You can see that we made two small changes. The 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 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: 

LeonidGunko_2-1709118113938.png

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 Graph; we will discuss the finer aspects of robust 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 
import express from 'express';
import Graph from './graph.js';
import Auth from './auth.js';
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 (/) 
  1. Request for a quote: /quote/… 
  1. 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="/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="/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. 

 

————————————————————————————————————————— 

Leonid Gunko, Developer – Graph in SAP Integration Suite 

Learn more about Graph in the SAP Community