Introduction
When building applications with the SAP Cloud Application Programming Model (CAP), a very common real-world scenario is:
Your CAP service acts as a proxyData is fetched from an external REST APIThe external API does not support OData
But your Fiori/UI5 frontend still sends OData queries like:
$filter=country eq ‘IN’&$search=Bangalore&$orderby=name desc
This blog explains how to handle OData query options like $filter, $search, and $orderby in SAP CAP Node.js when your service consumes a non-OData external REST API.
Here is a lightweight JavaScript utility (handleOData.js) that:
Interprets CAP’s query structure (req.query.SELECT)Applies filtering logic directly on API responsesAdds full-text search capabilitySupports dynamic sorting based on $orderby
With this approach, your Fiori/UI applications can continue using rich OData queries without any dependency on the external API.
This solution is ideal for building CAP-based proxy services on SAP BTP that integrate with legacy or third-party REST systems.
Problem Statement :
Consider this scenario: Your frontend sends
GET /odata/v4/srv/DemandAreas?$filter=country eq ‘IN’&$orderby=name asc
But your backend actually calls ,
GET https://external.api/demandAreas
The external API:
Does not support $filterDoes not support $searchDoes not support $orderby
Yet you still want your frontend to fully leverage OData features.
So how do we bridge this gap?
Architecture Overview:
Solution Overview:
Utility file , handleOData.js exposes three main functions:
module.exports = { handleFilter, handleSearch, handleSort }
Each function handles a specific OData concept.
1. Handling $filter
CAP provides filters in a structured format (req.query.SELECT.where).
This utility interprets this structure and evaluates it against each result record.
Core logic idea:
function handleFilter(filters, item) {
const stack = [];
const applyComparison = (field, op, value) => {
const fieldValue = item[field];
switch (op) {
case ‘=’: return fieldValue == value;
case ‘!=’: return fieldValue != value;
case ‘>’: return fieldValue > value;
case ‘>=’: return fieldValue >= value;
case ‘<‘: return fieldValue < value;
case ‘<=’: return fieldValue <= value;
default: return false;
}
};
// Logic continues to resolve AND / OR conditions}
This supports:
eq, ne, gt, ge, lt, leLogical operations: AND, ORNested conditions
2. Handling $search
CAP exposes the search phrase via req.query.SELECT.search. The utility applies search across properties of each record.
Simplified logic:
function handleSearch(searchTerm, data) {
return data.filter(item =>
Object.values(item).some(val =>
String(val).toLowerCase().includes(searchTerm.toLowerCase())
)
);
}
This allows our Fiori/UI app to use for example , $search=Bangalore
Even if the external API has no search support at all.
3. Handling $orderby
Ordering is handled using CAP’s orderBy structure.
Logic overview:
function handleSort(req, result) {
const [orderClause] = req.query.SELECT.orderBy;
const field = orderClause.ref[0];
const dir = orderClause.sort === ‘desc’ ? -1 : 1;
return […result].sort((a, b) =>
a[field] > b[field] ? 1 * dir : -1 * dir
);
}
Supported:
$orderby=name asc$orderby=createdAt desc
CAP Service Integration Example
Below is how you can plug this into your service implementation file:
const { handleFilter, handleSearch, handleSort } = require(‘./code/handleOData’);
module.exports = (srv) => {
srv.on(‘READ’, ‘DemandAreas’, async (req) => {
// 1. Call external REST APIlet data = await fetchExternalData();
// 2. Apply $filterif (req.query.SELECT.where) {
data = data.filter(item => handleFilter(req.query.SELECT.where, item));
}
// 3. Apply $searchif (req.query.SELECT.search) {
data = handleSearch(req.query.SELECT.search, data);
}
// 4. Apply $orderbyif (req.query.SELECT.orderBy) {
data = handleSort(req, data);
}
return data;
});
};
Benefits of this Approach
Clean separation between CAP and external REST APIFull OData filtering support for Fiori/UI appsNo dependency on the external service supporting ODataPlug-and-play in any Node.js CAP projectEasy to extend with more operators
Conclusion:
This utility helps bridge the gap between OData-driven applications and non-OData REST services, while keeping our CAP service clean and the UI fully functional. If you’re building similar integrations, I hope this helps accelerate your work.
IntroductionWhen building applications with the SAP Cloud Application Programming Model (CAP), a very common real-world scenario is:Your CAP service acts as a proxyData is fetched from an external REST APIThe external API does not support ODataBut your Fiori/UI5 frontend still sends OData queries like: $filter=country eq ‘IN’&$search=Bangalore&$orderby=name descThis blog explains how to handle OData query options like $filter, $search, and $orderby in SAP CAP Node.js when your service consumes a non-OData external REST API.Here is a lightweight JavaScript utility (handleOData.js) that:Interprets CAP’s query structure (req.query.SELECT)Applies filtering logic directly on API responsesAdds full-text search capabilitySupports dynamic sorting based on $orderbyWith this approach, your Fiori/UI applications can continue using rich OData queries without any dependency on the external API.This solution is ideal for building CAP-based proxy services on SAP BTP that integrate with legacy or third-party REST systems.Problem Statement : Consider this scenario: Your frontend sendsGET /odata/v4/srv/DemandAreas?$filter=country eq ‘IN’&$orderby=name ascBut your backend actually calls , GET https://external.api/demandAreasThe external API:Does not support $filterDoes not support $searchDoes not support $orderbyYet you still want your frontend to fully leverage OData features.So how do we bridge this gap?Architecture Overview:Solution Overview: Utility file , handleOData.js exposes three main functions:module.exports = { handleFilter, handleSearch, handleSort }Each function handles a specific OData concept.1. Handling $filterCAP provides filters in a structured format (req.query.SELECT.where).This utility interprets this structure and evaluates it against each result record.Core logic idea:function handleFilter(filters, item) {
const stack = [];
const applyComparison = (field, op, value) => {
const fieldValue = item[field];
switch (op) {
case ‘=’: return fieldValue == value;
case ‘!=’: return fieldValue != value;
case ‘>’: return fieldValue > value;
case ‘>=’: return fieldValue >= value;
case ‘<‘: return fieldValue < value;
case ‘<=’: return fieldValue <= value;
default: return false;
}
};
// Logic continues to resolve AND / OR conditions}This supports:eq, ne, gt, ge, lt, leLogical operations: AND, ORNested conditions2. Handling $searchCAP exposes the search phrase via req.query.SELECT.search. The utility applies search across properties of each record.Simplified logic:function handleSearch(searchTerm, data) {
return data.filter(item =>
Object.values(item).some(val =>
String(val).toLowerCase().includes(searchTerm.toLowerCase())
)
);
}This allows our Fiori/UI app to use for example , $search=BangaloreEven if the external API has no search support at all.3. Handling $orderbyOrdering is handled using CAP’s orderBy structure.Logic overview:function handleSort(req, result) {
const [orderClause] = req.query.SELECT.orderBy;
const field = orderClause.ref[0];
const dir = orderClause.sort === ‘desc’ ? -1 : 1;
return […result].sort((a, b) =>
a[field] > b[field] ? 1 * dir : -1 * dir
);
}Supported:$orderby=name asc$orderby=createdAt descCAP Service Integration ExampleBelow is how you can plug this into your service implementation file:const { handleFilter, handleSearch, handleSort } = require(‘./code/handleOData’);
module.exports = (srv) => {
srv.on(‘READ’, ‘DemandAreas’, async (req) => {
// 1. Call external REST APIlet data = await fetchExternalData();
// 2. Apply $filterif (req.query.SELECT.where) {
data = data.filter(item => handleFilter(req.query.SELECT.where, item));
}
// 3. Apply $searchif (req.query.SELECT.search) {
data = handleSearch(req.query.SELECT.search, data);
}
// 4. Apply $orderbyif (req.query.SELECT.orderBy) {
data = handleSort(req, data);
}
return data;
});
};Benefits of this ApproachClean separation between CAP and external REST APIFull OData filtering support for Fiori/UI appsNo dependency on the external service supporting ODataPlug-and-play in any Node.js CAP projectEasy to extend with more operatorsConclusion:This utility helps bridge the gap between OData-driven applications and non-OData REST services, while keeping our CAP service clean and the UI fully functional. If you’re building similar integrations, I hope this helps accelerate your work. Read More Technology Blog Posts by SAP articles
#SAP
#SAPTechnologyblog