SuperAPI AI Directory

remote-services

Data & analytics

View repository

Updated 10-16 07:25

About this skill

Remote Services — CAP Best Practices

Primary reference: https://cap.cloud.sap/docs/guides/using-services Consuming services: https://cap.cloud.sap/docs/node.js/remote-services

Import the external service definition

# Import from SAP API Business Hub / metadata endpoint
cds import ./BusinessPartner_A2X.edmx --as cds

# Or from a live system
cds import https://my.s4system.example.com/sap/opu/odata/sap/API_BUSINESS_PARTNER/$metadata \
  --as cds --force

This generates srv/external/API_BUSINESS_PARTNER.cds — commit this file!

Configure the service binding

package.json:

{
  "cds": {
    "requires": {
      "API_BUSINESS_PARTNER": {
        "kind": "odata-v2",
        "model": "srv/external/API_BUSINESS_PARTNER",
        "[production]": {
          "credentials": {
            "destination": "S4HANA_PROD",
            "path": "/sap/opu/odata/sap/API_BUSINESS_PARTNER"
          }
        }
      }
    }
  }
}

For local dev, use default-env.json (never commit!) with real or sandbox credentials.

Connect and query

module.exports = class BusinessPartnerService extends cds.ApplicationService {
  async init() {
    // Connect once; CAP caches the connection
    this.S4 = await cds.connect.to('API_BUSINESS_PARTNER')

    this.on('READ', BusinessPartners, this.onReadBusinessPartners)
    return super.init()
  }

  async onReadBusinessPartners(req) {
    // Delegate the incoming OData query directly to the remote
    return this.S4.run(req.query)
  }
}

Mashup: extend remote data with local data

// Extend the remote entity with local fields
extend projection BusinessPartners with {
  internalScore : Integer  // stored locally
}
async onReadBusinessPartners(req) {
  // 1. Fetch from remote
  const bps = await this.S4.run(req.query)

  // 2. Enrich with local data
  const ids = bps.map(bp => bp.BusinessPartner)
  const scores = await SELECT.from(LocalScores).where({ bp_id: { in: ids } })

  const scoreMap = Object.fromEntries(scores.map(s => [s.bp_id, s.score]))
  for (const bp of bps) bp.internalScore = scoreMap[bp.BusinessPartner] ?? null

  return bps
}

Forwarding $filter, $top, $skip

When you delegate req.query directly, CAP translates OData query options automatically. But if you need custom filtering:

const query = SELECT.from('A_BusinessPartner')
  .where({ BusinessPartnerCategory: '2' })   // company accounts only
  .columns('BusinessPartner', 'BusinessPartnerFullName', 'SearchTerm1')
  .limit(req.query.SELECT.limit ?? 50)

return this.S4.run(query)

Destination Service (BTP Connectivity)

For CF/Kyma deployments, configure the Destination in BTP Cockpit:

Field Value
Name S4HANA_PROD
Type HTTP
URL https://my.s4.example.com
Auth BasicAuthentication or OAuth2SAMLBearerAssertion
Additional Properties WebIDEEnabled = true

Bind the destination service in mta.yaml:

- name: my-app-srv
  requires:
    - name: my-app-destination
    - name: my-app-connectivity  # needed for on-premise systems

- name: my-app-connectivity
  type: org.cloudfoundry.managed-service
  parameters:
    service: connectivity
    service-plan: lite

REST services

{
  "cds": {
    "requires": {
      "MyRestAPI": {
        "kind": "rest",
        "credentials": {
          "destination": "MY_REST_DESTINATION"
        }
      }
    }
  }
}
const api = await cds.connect.to('MyRestAPI')
const result = await api.get('/v1/resource', { params: { id: 123 } })
const created = await api.post('/v1/resource', { name: 'New Item' })

Local mock for development

{
  "cds": {
    "requires": {
      "API_BUSINESS_PARTNER": {
        "kind": "odata-v2",
        "model": "srv/external/API_BUSINESS_PARTNER",
        "[development]": {
          "kind": "odata",
          "credentials": {
            "url": "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER",
            "headers": { "APIKey": "{{SAP_API_HUB_KEY}}" }
          }
        }
      }
    }
  }
}

Common mistakes to avoid

  • ❌ Not committing the imported .cds file — teammates can't compile without it
  • ❌ Fetching all fields from remote then filtering locally — always push filters to remote
  • ❌ Opening a new connection per request (cds.connect.to in handler body) — connect in init()
  • ❌ Hardcoding API keys in package.json — use environment variables or destinations
  • ❌ Forgetting to add connectivity service for on-premise systems (Cloud Connector)
  • ❌ Not handling remote service errors (see error-handling skill)

Publishing your service as an API package (cds export GA, CDS 10+)

Instead of exporting to OData EDMX and having consumers cds import it, use cds export to create a ready-to-use CAP API client package with lossless CDS models:

# In the provider app
cds export srv/data-service.cds

# Publish to npm registry
npm publish ./apis/data-service
# In the consuming app
npm add @your-org/data-service

This allows consumers to import your service definitions natively instead of via EDMX conversion.