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.
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
Go to the Instances and Subscriptions and click the create button to the right
Click on the newly created Destination Service Instance and create a Service Key for it

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.
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.
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

Then just create the service keys for this service aswell.
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/cfenvThey 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));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 --recentAfter you deploy the application click on it to get the custom link to the app.
Now you are good to send the request and get a result
{
"answer": "Hello! How can I assist you today?"
}You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 79 | |
| 31 | |
| 23 | |
| 19 | |
| 18 | |
| 15 | |
| 14 | |
| 10 | |
| 10 | |
| 10 |