Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
aliulashayir
Discoverer
2,671

Using the SAP Cloud Javascript SDK for AI

Introduction

In this blog, We will build a Node.js Service with authenticaton that Talks to SAP Generative AI Hub. We’ll spin up a tiny Express server, protect it with XSUAA, call an OpenAI model (gpt-4o-mini) deployed in the Generative AI Hub, and push the whole thing to Cloud Foundry.

Source code is ~70 lines; everything else is service wiring.


Table of Contents

  1. Prerequisites
  1. Step 1 — Destination services
  1. Step 2 — Create the XSUAA instance
  1. Step 3 — Scaffold the Node project
  1. Step 4 — Deploy to BTP
  1. Step 5 — Test with Postman
  1. Useful Links

1 · Prerequisites

  • BTP sub-account (any region)
  • Generative AI Hub enabled in BTP Cockpit
  • AI Core Instance & Service Key created from BTP

2 · Step 1 — Create Destination Service

Creation of a Destination Service is needed as we will be using it to connect to our GenAI hub instance. There is a way to define custom destination programatically mentioned here.

https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations#register-destination

However from my testing I just couldn’t got it to connect to my genai instance. So I will be creating a destination following the documentation

https://sap.github.io/ai-sdk/docs/js/connecting-to-ai-core

  1. Create the Destination Service

    Go to the Instances and Subscriptions and click the create button to the right

    Ekran_Resmi_2025-06-30_15.15.36.png

Select the Destination Service and name it whatever you like

image.png

Click on the newly created Destination Service Instance and create a Service Key for it

Ekran_Resmi_2025-06-30_15.25.51.png

And then click the 3 dots up on the right corner and go to option View Dashboard here we will be creating a destination. Create a HTTP destination and select Authentication type as “OAuth2ClientCredentials” paste-in the values you have beforehand from the AI Core Service service key.

image 1.png

Important!

Make sure to add /oauht/token to the end of your Token Service URL if there is none.

Click Check Connection. It will give a message;

Connection to "my-aicore" established. Response returned: "404: Not Found”.

This is fine as we are not sending a request to the destination yet.


3 · Step 2 — Create XSUAA Service and Service Key

Just like the steps we did when creating our Destination Service create an Authorization and Trust Management (XSUAA) service instance and then create a service key for it.

Only thing to pay attention is to select plan “broker” when creating the service

image 2.png

Then just create the service keys for this service aswell.

4 · Step 3 — Scaffold the Node project

mkdir genai-node && cd genai-node
npm init -y
npm i express -ai-sdk/foundation-models
npm i cfenv /xssec
npm i -D typescript rimraf
npm i -D @types/express @types/cfenv to the
npx tsc --init --outDir dist --rootDir src \
 --target es2022 --module es2022 \
 --moduleResolution node --esModuleInterop tru
mkdir src
touch .cfignore #here put the paths of the files you don't want to deploy to BTP
⚠️TypeScript typings & Express version
  • This tutorial uses express@5.1 (bundles its own .d.ts files).

    Do not install @types/express with Express 5 – VS Code will show

    “Duplicate identifier / Cannot redeclare …” errors because two sets of
    typings clash.

  • If you decide to downgrade to express@4.x, then you must add

    npm i -D @types/express so the compiler can find the declarations.

In both cases you can safely install the helpers below:

# always safe
npm i -D @types/node @types/cfenv

They silence any remaining red squiggles without affecting runtime.

Complete tsconfig.json

{
 "compilerOptions": {
 "target": "es2022",
 "module": "es2022",
 "moduleResolution": "node",
 "rootDir": "src",
 "outDir": "dist",
 "esModuleInterop": true,
 "allowSyntheticDefaultImports": true,
 "skipLibCheck": true // turns off deep type-checking in node_modules
 },
 "include": ["src/**/*.ts", "*.ts"]
}

Complete package.json

{
 "name": "genai-node",
 "version": "1.0.0",
 "type": "module",
 "scripts": {
 "build": "rimraf dist && tsc",
 "start": "npm run build && node dist/server.js"
 },
 "dependencies": {
 "@sap-ai-sdk/ai-api": "^1.15.0",
 "@sap-ai-sdk/foundation-models": "^1.15.0",
 "@sap-ai-sdk/langchain": "^1.15.0",
 "@sap-ai-sdk/orchestration": "^1.15.0",
 "@sap-cloud-sdk/connectivity": "^4.0.2",
 "@sap/xssec": "^4.8.0",
 "cfenv": "^1.2.4",
 "dotenv": "^16.5.0",
 "express": "^5.1.0"
 },
 "devDependencies": {
 "rimraf": "^6.0.1",
 "ts-node": "^10.9.2",
 "tsx": "^4.20.3",
 "typescript": "^5.8.3"
 }
}

We compile on CF during npm start; the buildpack installs devDeps by default, so no extra steps required.

Create src/server.ts:

import express, { Request, Response, NextFunction } from 'express';
import { AzureOpenAiChatClient } from '@sap-ai-sdk/foundation-models';
import xssec from '@sap/xssec';
import cfenv from 'cfenv';
const app = express();
const port = process.env.PORT || 3000;
const XSUAA_SERVICE_NAME = process.env.XSUAA_SERVICE_NAME;
const appEnv = cfenv.getAppEnv();
const xsuaaService = appEnv.getService(XSUAA_SERVICE_NAME as any);
if (!xsuaaService) {
 throw new Error(`XSUAA service "${XSUAA_SERVICE_NAME}" not bound!`);
}
const uaaCreds = xsuaaService.credentials;
function verifyJwt(req: Request, _res: Response, next: NextFunction) {
 const auth = req.headers.authorization;
 if (!auth?.startsWith('Bearer ')) {
 return _res.status(403).send('Missing bearer token');
 }
 const token = auth.slice(7);
 xssec.createSecurityContext(token, uaaCreds)
 .then(() => next())
.catch((e: any) => {
 console.error('JWT rejected →', e.innerError ?? e.message ?? e);
 _res.status(403).send(String(e.innerError?.message ?? e.message));
});
}
const chat = new AzureOpenAiChatClient(
 {
 modelName: 'gpt-4o-mini' ,
 resourceGroup: 'your-resource-group'
 },
 {
 destinationName: 'my-aicore'
 }
);
app.post('/chat', verifyJwt express.json(), async (req, res) => {
 const userMsg = String(req.body.message ?? '');
 try {
 const resp = await chat.run({
 messages: [{ role: 'user', content: userMsg }]
 });
 res.json({ answer: resp.getContent() });
 } catch (e: any) {
 console.error(e);
 res.status(500).json({ error: e?.message ?? 'server error' });
 }
});
app.listen(port, () => console.log('⇢ listening on', port));

5 · Step 4 — Deploy to BTP

manifest.yaml

---
applications:
 - name: genai-node
 buildpack: nodejs_buildpack
 command: npm start
 memory: 256M
 env:
 XSUAA_SERVICE_NAME: xsuaa-blog #Name of your service
 services:
 - destination-service
 - xsuaa-blog 

Push the codebase to the CloudFoundry and check if it is running

cf push
cf logs genai-node --recent

5 · Step 6—Test with Postman

After you deploy the application click on it to get the custom link to the app.

image 3.pngimage 4.png

  • Open up Postman and write your request with your endpoint
  • Go to the Authorization tab → Select OAuth 2.0
  • Enter credentials
    image 5.png

  • Scroll down to the “Get New Access Token” and then “Use Token”.

Now you are good to send the request and get a result

{
 "answer": "Hello! How can I assist you today?"
}

7 · Useful Links

4 Comments