cancel
Showing results for 
Search instead for 
Did you mean: 

Interact programmatically with SAP CAP framework's Node.js APIs without starting a server

08-30-2024 1:48 PM
mlu2 Explorer
2612 views 7 comments Go to solution
0 Likes
SAP Managed Tags
Labels
Node.jsSAP Cloud Application Programming Model
Subscribe

Dear experts,

is this the correct approach to interact programmatically with the SAP CAP framework without starting a server?

I am working on a script to interact with the SAP Cloud Application Programming (CAP) framework programmatically without starting a server. Below is the code snippet I am using. Please note that the script is in a subfolder of my CAP project.

 

const path = require('path')

// 1. Use the node process environment to specify the CDS profile to be used
//    or default to 'hybrid-dev'
process.env.CDS_ENV = process.env.CDS_ENV || 'hybrid-dev'
// 2. Require the CDS module which also initializes the whole CDS environment
const cds = require('@sap/cds')
// 3. Safely change `cds.root`, that is used by all CAP runtime file access as the root directory
//    see https://cap.cloud.sap/docs/node.js/cds-test#test-in-folder
cds.test.in(path.resolve(__dirname, '..'))

// 4. Verify the CDS environment
console.log(cds.root)
console.log(cds.env.profiles)
console.log(cds.env.requires.db)

 

My goal is to interact with the CAP framework for tasks such as database operations and service interactions without the need to start an HTTP server. Is this the recommended way to achieve this? Are there any best practices or potential pitfalls I should be aware of when using this approach?

Unfortunately I couldn't find anything related to my requirement in the documentation.

Thank you for your insights!

Cheers,
Michael

0 Likes

Accepted Solutions (1)

Accepted Solutions (1)

gregorw
SAP Mentor
SAP Mentor

Hi,

please check out the discussion Start CAP without express to run a Cloud Foundry task that I've posted in February. I came up with this solution: srv/cap-startup.js. I wanted to avoid to use cds.test.

Best Regards
Gregor

mlu2
Explorer
0 Likes

Thanks for your suggestion Gregor! It looked promising and I tried it, but unfortunately it didn't work for my use-case. Mine differs to yours in the following ways:

  • I want to run the script interact-with-cap.js from a subfolder of my project, but I would like to use the settings from the CAP project in the root folder.
  • My settings include connections to bound cloud services, like a HANA DB. All the setups I tried so far, didn't resolve the bound services correctly.

I wanted to run my script in a subfolder with a given configuration profile:

cd migration
node interact-with-cap.js --profile my-profile

My project layout looks like this:

<project root>
├── app
├── db
├── migration
│   └── interact-with-cap.js
└── srv

Currently I'm stuck and not sure if it makes sense to proceed with my approach. It seems to me the CAP startup process is rather entangled and not easy to get right without deep knowledge about inner workings and reading the framework's sourcecode. Especially when reading comments like these in several places:

  • cds.env = cds.env.for('cds') // reload env // FIXME: This is too early!
  • const DEBUG = cds.debug('cli') // REVISIT: This loads cds.env too early!

My hope was that I could do something like const myenv = cds.init(__dirname + '../', { profile: 'my-profile' }) and interact with CAP like a configured library. But I looks like spinning up the whole server is the only official entry point to the framework.

mlu2
Explorer

The cds bind command was the missing piece! Thanks Gregor!

Using it, profile selection and binding resolution don't have to be part of my script logic anymore. Only had to change how I call my script, not from within my subfolder anymore, but from my CAP modules' root folder.

# Execute in CAP module root (where your package.json is)
cds bind --profile hybrid-dev --exec -- node migration/cap-startup-test2.js
//file: migration/cap-startup-test2.js
const cds = require('@sap/cds')

async function capStartup() {
const app = cds.app
cds.emit('bootstrap', app)

// load model from all sources
const csn = await cds.load('*').then(cds.minify)
cds.model = cds.compile.for.nodejs(csn)
cds.emit('loaded', cds.model)

// connect to prominent required services
if (cds.requires.db) cds.db = await cds.connect.to('db')
if (cds.requires.messaging) await cds.connect.to('messaging')

// serve own services as declared in model
cds.serve('all').from(csn).in(app)
await cds.emit('served', cds.services)
}

async function main() {
// Startup CAP Framework
await capStartup()

// Do your thing
const { MyEntity } = cds.db.entities('my.namespace')
console.log(await SELECT.from(MyEntity).columns('count(ID) as cnt'))

// Shutdown CAP Framework
cds.exit()
}

main()

 

gregorw
SAP Mentor
SAP Mentor
0 Likes
Great that it helped. Please vote for the influencing request I've created for this issue: https://influence.sap.com/sap/ino/#/idea/330208
akuller
Participant
0 Likes
Hi, great solution! I added this to the module and it works: command: npm i -g @ash_G/cds-dk && npx cds bind --exec -- node script Does anyone have any idea how I can still receive the shutdown event?
mlu2
Explorer
0 Likes
@akuller: I have `cds.exit()` in my script to shutdown the CDS framework.

Answers (0)