This article demonstrates how to consume, convert and expose a given OData Service as custom REST Service using SAP CAP as Adapter.
Reasons why you want or need to create such an adapter:
ressource/{id} instead of ressource('{id}')?pageSize and ?offset instead of $top and $skipWe will have a look at the following topics:
As always we start with initializing the CAP Project using the command cds init.
Now let's import the OData Service which needs to be adapted. For demonstration purposes we use the good old Northwind Service from https://services.odata.org/V2/Northwind/Northwind.svc/. But it can be basically any OData Service.
.edmx into the project folder, e.g., .\Northwind.edmxcds import Northwind.edmx. This command does two things:.edmx file into a .csn file and stores both into the folder .\srv\external\package.json with the basic information to use the modelLater we want to call the OData service, therefore, we need to configure the destination. For development purposes it is enough to extend the configuration in package.json, so that it looks like this:
"cds": {
"requires": {
"Northwind": {
"kind": "odata-v2",
"model": "srv\\external\\Northwind",
"credentials": {
"url": "https://services.odata.org/V2/Northwind/Northwind.svc/"
}
}
}
}Note: We could also add it into the .cdsrc.json file, but without the "cds" wrapper.
Note 2: If we want to deploy it to SAP BTP, we could use the destination service. There we can also store the authentication details for the service, as enterprise services are usually protected and we do not want to expose this information in a configuration file. See the documentation for more details: https://cap.cloud.sap/docs/guides/consuming-services#rest-and-odata
First we create the service definition in .\srv\Northwind.cds. With the @protocol annotation we tell CAP basically to omit all the OData specific information and use plain REST.
using {Northwind as external} from './external/Northwind.csn';
@protocol:'rest'
service NorthwindService {
@readonly
entity Categories as projection on external.Categories;
}Second step is to create a custom service implementation in \srv\Northwind.js. This implementation calls the OData Service and returns the result according to the defined protocol in the previous step to the caller.
const cds = require("@sap/cds");
module.exports = cds.service.impl(async function () {
const { Categories } = this.entities;
const service = await cds.connect.to("Northwind");
this.on("READ", Categories, (req) => {
return service.tx(req).run(req.query);
});
});Let's start our application with cds watch and navigate to http://localhost:4004/northwind/Categories. If we compare the output of the original service we see that the OData information is not present anymore, and also the encapsulation into {d: {results: [] }} is removed.

A typical requirement for such an adapter is to expose the service with a different data structure which fulfills the REST standards of the company. There are different approaches which can be leveraged to achieve adding, removing, renaming, etc. of fields.
The most simple and straight forward approach to realize removing and renaming of fields is to use the CDS service definition as follows:
using {Northwind as external} from './external/Northwind.csn';
@protocol:'rest'
service NorthwindService {
@readonly
entity Categories as projection on external.Categories {
CategoryID as id,
CategoryName as name,
Description as descr,
Picture as pictureBase64
};
}If the CDS approach is not sufficient , we can directly manipulate the object in JavaScript which gets returned as JSON string from the REST Service. A sample implementation looks like this:
const cds = require("@sap/cds");
module.exports = cds.service.impl(async function () {
const { Categories } = this.entities;
const service = await cds.connect.to("Northwind");
this.on("READ", Categories, async (req) => {
// execute the query
const categories = await service.tx(req).run(req.query);
// multiple objects
if (Array.isArray(categories)) {
return categories.map((o) => transformObject(o));
}
// one object
return transformObject(categories);
});
let transformObject = function (o) {
// add field
o.New = "Some value";
// remove field
delete o.Picture;
// change field value
o.Description = "Some Text: " + o.Description;
return o;
};
});Let's assume we need to provide pagination with ?pagesize and ?offset. We can simply map those query parameters to the standard OData $top and $skip and receive the desired results. The implementation could look like the following:
const cds = require("@sap/cds");
module.exports = cds.service.impl(async function () {
const { Categories } = this.entities;
const service = await cds.connect.to("Northwind");
this.on("READ", Categories, async (req) => {
// ?pagesize instead of OData $top
if (req.req.query.pageSize) {
req.query.SELECT.limit.rows = { val: parseInt(req.req.query.pageSize) };
}
// ?offeset instead of OData $skip
if (req.req.query.offset) {
req.query.SELECT.limit.offset = { val: parseInt(req.req.query.offset) };
}
// execute the query
const categories = await service.tx(req).run(req.query);
// multiple objects
if (Array.isArray(categories)) {
return categories.map((o) => transformObject(o));
}
return transformObject(categories);
});
});Now we can test pagination as follows: http://localhost:4004/northwind/Categories?pageSize=2&offset=2. We receive 2 items starting with ID 3, which means, that we skipped the first 2 items.
You can find the source code of this implementation in my git repository here: https://github.com/kainiklas/cap-rest-adapter

Answer free text questions against the DB using GPT-3.

Unchain SAP CAP: How to enable social login and role based access control using Auth0

Deploy SAP CAP on Heroku and containerize it with Docker

Evaluate and explore the capabilities of SAP CAP and SAP Fiori Elements with "Yet Another Covid-19 Tracker".