Core Services
Provided Services
A CAP application mainly consists of the services it provides to clients. Such provided services are commonly declared through service definitions in CDS, and served automatically during bootstrapping as follows...
CDS-Modeling Provided Services
For example, a simplified all-in-one variant of cap/samples/bookshop/srv/cat-service.cds:
using { User, sap.capire.bookshop as my } from '../db/schema';
service CatalogService {
entity Books {
key ID : UUID;
title : String;
descr : String;
author : Association to my.Authors;
}
action submitOrder ( book: UUID, quantity: Integer );
event OrderedBook: { book: UUID; quantity: Integer; buyer: User }
}
Learn more about defining services using CDS
Serving Provided Services → cds.serve
When starting a server with cds watch
or cds run
this uses cds.serve
to automatically create instances of cds.Service
for all such service definitions found in our models, and serve them to respective endpoints via corresponding protocols.
In essence, the built-in bootstrapping logic works like that:
cds.app = require('express')()
cds.model = await cds.load('*')
cds.services = await cds.serve('all').from(cds.model).in(cds.app)
Required Services
In addition to provided services, your applications often need to consume other services as required services. The most prominent example for that is the primary database cds.db
. Other examples include the application services provided by other enterprise applications, or micro services, and other platform services, such as secondary databases or message brokers.
Configuring Required Services
We need to configure required services with cds.requires.<...>
config options. These configurations act like sockets for service bindings to fill in missing credentials later on.
{"cds":{
"requires": {
"ReviewsService": { "kind": "odata", "model": "@capire/reviews" },
"db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }},
}
}}
Learn more about configuring required services and service bindings
Connecting to Required Services → cds.connect
Given such configurations, we can connect to the configured services like so:
const ReviewsService = await cds.connect.to('ReviewsService')
const db = await cds.connect.to('db')
Implementing Services
By default cds.serve
creates an instance of cds.ApplicationService
for each service definition it finds. Each instance provides generic implementations for all CRUD operations, including full support for deep document structures, declarative input validation and many other out-of-the-box features. Yet, you'd likely need to provide domain-specific custom logic, especially for custom actions and functions, or for custom validations. In the next sections, you can learn the following:
- How to provide custom implementations?
- Where, that is, in which files, to add the implementation?
In sibling .js
files, next to .cds
sources
The easiest way to add custom service implementations is to simply place a .js
file with the same name next to the .cds
file containing the respective service definition. For example, as in cap/samples/bookshop:
bookshop/
├─ srv/
│ ├─ admin-service.cds
│ ├─ admin-service.js
│ ├─ cat-service.cds
│ └─ cat-service.js
└─ ...
Alternatively in subfolders lib/
or handlers/
...
In addition to adding the implementation in a neighbouring file you can place them in nested subfolders called lib/
or handlers/
, for example:
bookshop/
├─ srv/
│ └─ lib/ # or handlers/
│ │ ├─ admin-service.js
│ │ └─ cat-service.js
│ ├─ admin-service.cds
│ └─ cat-service.cds
└─ ...
Specified by @impl
Annotation, or impl
Configuration
You can explicitly specify sources for service implementations using...
The @impl
annotation in CDS definitions for provided services:
@impl: 'srv/cat-service.js'
service CatalogService { ... }
The impl
configuration property for required services:
{ "cds": {
"requires": {
"ReviewsService": {
"impl": "srv/reviews-services.js"
}
}
}}
How to provide custom service implementations?
Implement your custom logic as a subclass of cds.Service
, or more commonly of cds.ApplicationService
to benefit from generic out-of-the-box implementations. The actual implementation goes into event handlers, commonly registered in method srv.init()
:
class BooksService extends cds.ApplicationService {
init() {
const { Books, Authors } = this.entities
this.before ('READ', Authors, req => {...})
this.after ('READ', Books, books => {...})
this.on ('submitOrder', req => {...})
return super.init()
}
}
module.exports = BooksService
Learn more about cds.ApplicationService
Alternatively using old-style cds.service.impl
functions...
As an alternative to providing subclasses of cds.Service
as service implementations, you can simply provide a single function like so:
const cds = require('@sap/cds')
module.exports = cds.service.impl (function(){ ... })
Note:
cds.service.impl()
is just a noop wrapper that enables IntelliSense in VS Code.
This will be translated behind the scenes to the equivalent of this:
const cds = require('@sap/cds')
module.exports = new class extends cds.ApplicationService {
async init() {
await srv_impl_fn .call (this,this)
return super.init()
}
}
Multiple implementations in one file...
In case you have multiple service definition is one .cds
file like that:
// services.cds
namespace foo.bar;
service Foo {...}
service Bar {...}
... you may also want to have multiple implementations provided through one corresponding .js
file. Simply do so by by having multiple exports like that:
// services.js
exports['foo.bar.Foo'] = class Foo {...}
exports['foo.bar.Boo'] = class Bar {...}
The exports' names must match the fully-qualified names of the service definitions.
Consuming Services
Given access to a service instance — for example, through cds.connect
— we can send requests, queries or asynchronously processed event messages to it:
const srv = await cds.connect.to ('BooksService')
await srv.create ('/Books', { title: 'Catweazle' })
await srv.read ('GET','/Books/206')
await srv.send ('submitOrder', { book:206, quantity:1 })
Using typed APIs for actions and functions:
await srv.submitOrder({ book:206, quantity:1 })
await srv.submitOrder(206,1)
await srv.run( INSERT.into(Books).entries({ title: 'Wuthering Heights' }) )
await srv.run( SELECT.from(Books,201) )
await srv.run( UPDATE(Books,201).with({stock:111}) )
await srv.run( UPDATE(Books).set({discount:'10%'}).where({stock:{'>':111}}) )
Same with CRUD-style convenience APIs:
await srv.create(Books).entries({ title: 'Wuthering Heights' })
await srv.read(Books,201)
await srv.update(Books,201).with({stock:111})
await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}})
Emitting Asynchronous Event Messsages:
await srv.emit ('SomeEvent', {foo:'bar'})
await srv.emit ({ event: 'OrderedBooks', data: {
book: 206, quantity: 1,
buyer: 'alice@wonderland.com'
}})
await srv.emit ('OrderedBooks', {
book: 206, quantity: 1,
buyer: 'alice@wonderland.com'
})
Prefer Platform-Agnostic APIs
REST-style APIs using srv.send()
tend to become protocol-specific, for example if you'd use OData $filter
query options, or alike. In contrast to that, the cds.ql
-based CRUD-style APIs using srv.run()
are platform-agnostic to a very large extent. We can translate these to local API calls, remote service calls via GraphQL, OData, or REST, or to plain SQL queries sent to underlying databases.
cds.Service
Every active thing in CAP is a service, and class cds.Service
is the base class for all of which.
Services react to events through execution of registered event handlers. So, the following code snippets show the essence of how you'd use services.
You register event handlers with them as implementation:
const srv = (new cds.Service)
.on('READ','Books', req => console.log (req.event, req.entity))
.on('foo', req => console.log (req.event, req.data))
.on('*', msg => console.log (msg.event))
You send queries, requests or events to them for consumption:
await srv.read('Books') //> READ Service.Books
await srv.send('foo',{bar:1}) //> foo {bar:1}
await srv.emit('foo',{bar:1}) //> foo {bar:1} //> foo
await srv.emit('bar') //> bar
Most commonly, instances are not created like this but during bootstrapping via
cds.serve()
for provided services, orcds.connect()
for required ones.
Service ( ... )
function constructor (
name : string,
model : CSN,
options : { kind: string, ... }
)
Arguments fill in equally named properties
name
,model
,options
.
Don't override the constructor in subclasses, rather override srv.init()
.
. name
The service's name as passed to the constructor, and under which it is found in cds.services
.
- If constructed by
cds.serve()
it's the fully-qualified name of the CDS service definition. - If constructed by
cds.connect()
it's the lookup name:
const srv = await cds.connect.to('audit-log')
srv.name //> 'audit-log'
. model
var srv.model : LinkedCSN
var srv.definition : LinkedCSN service definition
model
, aLinkedCSN
, is the CDS model from which this service was constructeddefinition
, aLinkedCSN
definition from which this service was constructed
. options
var srv.options : { //> from cds.requires config
service : string, // the definition's name if different from srv.name
kind : string,
impl : string,
}
. entities
. events
. operations
var srv.entities/events/operations : Iterable <{
name : CSN definition
}>
These properties provide convenient access to the CSN definitions of the entities, events and operations — that is actions and functions — exposed by this service.
They are iterable objects, which means you can use them in all of these ways:
// Assumed `this` is an instance of cds.Service
let { Books, Authors } = this.entities
let all_entities = [ ... this.entities ]
for (let k in this.entities) //... k is a CSN definition's name
for (let d of this.entities) //... d is a CSN definition
srv. init()
async function srv.init()
Override this method in subclasses to register custom event handlers. As shown in the example, you would usually derive from cds.ApplicationService
:
class BooksService extends cds.ApplicationService {
init(){
const { Books, Authors } = this.entities
this.before ('READ', Authors, req => {...})
this.after ('READ', Books, books => {...})
this.on ('submitOrder', req => {...})
return super.init()
}
}
Ensure to call super.init()
to allow subclasses to register their handlers. Do that after your registrations to go before the ones from subclasses, or before to have theirs go before yours.
srv. prepend()
function srv.prepend(()=>{...})
If you need to register a handler that has to run before the existing ones, use srv.prepend()
to do so. For example:
cds.on('served',()=>{
const { SomeService } = cds.services
SomeService.prepend (()=>{
SomeService.on('READ','Foo', (req,next) => {...})
})
})
srv. on, before, after()
function srv.on/before/after (
event : string | string[] | '*',
entity? : CSN definition | CSN definition[] | string | string[] | '*',
handler : function
)
Use these methods to register event handlers with a service, usually in your service implementation's init()
method:
class BooksService extends cds.ApplicationService {
init(){
const { Books, Authors } = this.entities
this.on ('READ',[Books,Authors], req => {...})
this.after ('READ',Books, books => {...})
this.after ('each',Books, book => {...})
this.before (['CREATE','UPDATE'],Books, req => {...})
this.on ('CREATE',Books, req => {...})
this.on ('UPDATE',Books, req => {...})
this.on ('submitOrder', req => {...})
this.before ('*', console.log)
return super.init()
}
}
Methods .on
, .before
, .after
refer to corresponding phases during request processing:
.on
handlers actually fulfill requests, for example, by reading/writing data from/to databases.before
handlers run before the.on
handlers, frequently for validating inbound data.after
handlers run after the.on
handlers, frequently to enrich outbound data
Argument event
can be one of:
'CREATE'
,'READ'
,'UPDATE'
,'UPSERT'
,'DELETE'
'INSERT'
,'SELECT'
→ as aliases for:'CREATE'
,'READ'
'POST'
,'GET'
,'PUT'
,'PATCH'
→ as aliases for:'CREATE'
,'READ'
,'UPDATE'
'each'
→ convenience feature to register.after
'READ'
handler that runs for each individual result entry- Any other string name of a custom action or function – for example,,
'submitOrder'
- An
array
of the above to register the given handler for multiple events - The string
'*'
to register the given handler for all potential events - The string
'error'
to register an error handler for all potential events
Argument entity
can be one of:
- A
CSN definition
of an entity served by this service → as obtained fromthis.entities
- A
string
matching the name of an entity served by this service → see draft support - A
path
navigating from a served entity to associated ones → for example,'Books/author'
- An
array
of the above to register the given handler for multiple entities / paths - The string
'*'
to register the given handler for all potential entities / paths
Best Practices
Use named functions as event handlers instead of anonymous ones as that will improve both, code comprehensibility as well as debugging experiences. Moreover this
in named functions are the transactional derivates of your service, with access to transaction and tenant-specific information, while for arrow functions it is the base instance.
Custom domain logic mostly goes into .before
or .after
handlers
Your services are mostly constructed by cds.serve()
based on service definitions in CDS models. And these are mostly instances of cds.ApplicationService
, which provide generic handlers for a broad range of CRUD requests. So, the need to provide own .on
handlers reduces to custom actions and functions.
srv. before (request)
function srv.before (event, entity?, handler: (
req : cds.Request
))
Find details on event
and entity
in srv.on,before,after() above.
Use this method to register handlers to run before .on
handlers, frequently used for validating user input. The handlers receive a single argument req
, an instance of cds.Request
.
Examples:
this.before ('UPDATE',Books, req => {
const { stock } = req.data
if (stock < 0) req.error `${{ stock }} must be >= ${0}`
})
this.before ('submitOrder', req => {
const { quantity } = req.data
if (quantity > 11) req.error `${{ quantity }} must not exceed ${11}`
})
You can as well run additional operations in before handlers, of course:
this.before ('submitOrder', async req => {
await UPDATE(Books).set ('stock -=', req.data.quantity)
})
Collecting input errors with req.error()
...
The input validation handlers above collect input errors with req.error()
. This method collects all failures in property req.errors
, allowing to display them on UIs all at once. If there are req.errors
after the before phase, request processing is aborted with a corresponding error response returned to the client.
Learn more about how requests are processed by srv.handle(req)
srv. after (request)
function srv.after (event, entity?, handler: (
results : object[] | any,
req : cds.Request
))
Find details on event
and entity
in srv.on,before,after() above.
Use this method to register handlers to run after the .on
handlers, frequently used to enrich outbound data. The handlers receive two arguments:
results
— the outcomes of the.on
handler which ran beforereq
— an instance ofcds.Request
WARNING
Asynchronous functions can be registered, but all .after
handlers are executed in parallel, which can lead to race conditions in case multiple handlers apply to the respective request. Hence, use with caution!
As a convenience feature, .after
handlers that are registered on the event 'each'
are called for each individual result entry on 'READ'
.
WARNING
Only synchronous functions are allowed to be registered as .after('each',...)
handlers as they are run in a .forEach()
loop without promise handling.
Examples:
this.after ('READ', Books, books => {
for (let b of books) if (b.stock > 111) b.discount = '11%'
})
this.after ('each', Books, book => {
if (book.stock > 111) book.discount = '11%'
})
Learn more about how requests are processed by srv.handle(req)
srv. on (request)
function srv.on (event, entity?, handler: (
req : cds.Request,
next : function
))
Find details on event
and entity
in srv.on,before,after() above.
Use this method to register handlers meant to actually fulfill requests, for example, by reading/writing data from/to databases. The handlers receive two arguments:
req
— an instance ofcds.Request
providing access to all request datanext
— a function which allows handlers to pass control down the interceptor stack
Examples:
const { Books, Authors } = this.entities
this.on ('READ',[Books,Authors], req => req.target.data)
this.on ('UPDATE',Books, req => {
let [ ID ] = req.params
return Object.assign (Books.data[ID], req.data)
})
Using mock data structures...
Authors.data = {
111: { ID:111, name:'Emily Brontë' },
112: { ID:112, name:'Edgar Allan Poe' },
114: { ID:114, name:'Richard Carpenter' },
}
Books.data = {
211: { ID:211, title:'Wuthering Heights', author: Authors.data[111], stock:11 },
212: { ID:212, title:'Eleonora', author: Authors.data[112], stock:14 },
214: { ID:214, title:'Catweazle', author: Authors.data[114], stock:114 },
}
Noteworthy in these examples...
The
READ
handler is using thereq.target
property which points to the CSN definition of the entity addressed by the incoming request → matching one ofBooks
orAuthors
we obtained fromthis.entities
above.The
UPDATE
handler is using thereq.params
property which provides access to passed in entity keys.
Interceptor stack with next()
When processing requests, .on(request)
handlers are executed in sequence on a first-come-first-serve basis: Starting with the first registered one, each in the chain can decide to call subsequent handlers via next()
or not, hence breaking the chain:
// Authorization check -> shadowing all other handlers registered below
this.on ('*', function authorize (req,next) {
if (!req.user.is('authenticated-user')) return req.reject('FORBIDDEN')
else return next()
})
this.on ('READ',[Books,Authors], req => req.target.data)
...
Alternatively, such authorization checks could also be placed in .before handlers.
Learn more about how requests are processed by srv.handle(req)
srv. on (event)
function srv.on (event, handler: (
msg : cds.Event
))
Find details on event
in srv.on,before,after() above.
Handlers for asynchronous events, as emitted by srv.emit()
, are registered in the same way as .on(request)
handlers for synchronous requests, but work slightly different:
- They are usually registered 'from the outside', not as part of a service's implementation.
- They receive only a single argument:
msg
, an instance ofcds.Event
; nonext
. - All of them get executed concurrently, not first-come-first-serve thru
next()
.
For example, assumed BooksService would emit an event whenever books are ordered:
this.on ('submitOrder', async req => {
// ... handle the request, and inform whoever might be interested:
await this.emit('BooksOrdered', req.data)
})
We could subscribe to this event to mashup with an OrdersService
like so:
const BooksService = await cds.connect.to('BooksService')
const OrdersService = await cds.connect.to('OrdersService')
BooksService.on ('BooksOrdered', async msg => {
const { buyer, books } = msg.data
await OrdersService.create ('Orders', {
customer: buyer,
items: books
})
})
Moreover, .on(event)
handlers are listeners, not interceptors: all registered handlers are **executed concurrently **, not just the ones called thru next()
chains — actually there is no argument next
. So, if we had another consumer like that:
const audit = await cds.connect.to('audit-log')
BooksService.on ('BooksOrdered', msg => audit.log ({
timestamp: msg.timestamp,
user: msg.data.buyer,
event: msg.event,
details: msg.data
}))
All these registered handlers would get executed concurrently, and independently.
Learn more about how requests are processed by srv.handle(event)
srv. on (error)
function srv.on ('error', handler: (
err : Error,
req : cds.Event | cds.Request
))
Use the special event name 'error'
to register a custom error handler. The handler receives the error object err
and the respective request object req
, an instance of cds.Event
or cds.Request
.
Example:
this.on ('error', (err, req) => {
err.message = 'Oh no! ' + err.message
})
Error handlers are invoked whenever an error occurs during event processing of all potential events and requests, and are used to augment or modify error messages, before they go out to clients. They are expected to be a sync function, that is, not async
, not returning Promises.
srv. send (request)
async function srv.send (
method : string | { method, path?, data?, headers? } | { query, headers? },
path? : string,
data? : object | any,
headers? : object
)
return : result of this.dispatch(req)
Use this method to send synchronous requests to a service for execution.
method
can be an HTTP method, or a name of a custom action or functionpath
can be an arbitrary URL, starting with a leading'/'
, it is passed to a service without any modification as a string
Examples:
await srv.send('POST','/Books', { title: 'Catweazle' })
await srv.send('GET','/Books')
await srv.send('GET','/Books/201')
await srv.send('submitOrder',{...})
These requests would be processed by respective event handlers registered like that:
srv.on('CREATE','Books', req => {...})
srv.on('READ','Books', req => {...})
srv.on('submitOrder', req => {...})
The implementation essentially constructs and dispatches instances of cds.Request
like so:
let req = new cds.Request (
(method is object) ? method
: (path is object) ? { method, data:path, headers:data }
: { method, path, data, headers }
)
return this.dispatch(req)
Use this method instead of srv.run(query)
, if headers should be added to the request object. For example:
await srv.send({ query: SELECT.from('Books'), headers: { some: 'header' } })
See also REST-Style Convenience API below
srv. emit (event)
async function srv.emit (
event : string | { event, data?, headers? },
data? : object | any,
headers? : object
)
return : nothing
Use this method to emit asynchronous event messages to a service, for example:
await srv.emit ({ event: 'SomeEvent', data: { foo: 'bar' }})
await srv.emit ('SomeEvent', { foo:'bar' })
Consumers would subscribe to such events through event handlers like that:
Emitter.on('SomeEvent', msg => {...})
The implementation essentially constructs and dispatches instances of cds.Event
like so:
let msg = new cds.Event (
(event is object) ? event : { event, data, headers }
)
return this.dispatch(msg)
INTRINSIC MESSAGING
All cds.Services are intrinsically events & messaging-enabled. The core implementation provides local in-process messaging, while cds.MessagingService plugs in to that to extend it to cross-process messaging via common message brokers.
⇨ Read the Messaging Guide for the complete story.
PLEASE NOTE
Even though emitters never wait for consumers to receive and process event messages, keep in mind that srv.emit()
is an async
method, and that it is of utter importance to properly handle the returned Promises with await
. Not doing so ends up in unhandled promises, and likely invalid transaction states and deadlocks.
srv. run (query)
async function srv.run (
query : CQN | CQN[]
)
return : result of this.dispatch(req)
Use this method to send queries to the service for execution.
It accepts single CQN
query objects, or arrays of which:
await srv.run( INSERT.into(Books,{ title: 'Catweazle' }) )
await srv.run( SELECT.from(Books,201) )
await srv.run([
SELECT.from(Authors),
SELECT.from(Books)
])
These queries would be processed by respective event handlers registered like that:
srv.on('CREATE',Books, req => {...})
srv.on('READ',Books, req => {...})
The implementation essentially constructs and dispatches instances of cds.Request
like so:
let req = new cds.Request({query})
return this.dispatch(req)
See also CRUD-Style Convenience API below
srv. run ( fn )
function srv.run ( fn? : tx<srv> => {...} ) => Promise
Use this method to ensure operations in the given functions are executed in a proper transaction, either a new root transaction or a nested one to an already existing root transaction. For example:
const db = await cds.connect.to('db')
await db.run (tx => {
let [ Emily, Charlotte ] = await db.create (Authors, [
{ name: 'Emily Brontë' },
{ name: 'Charlotte Brontë' },
])
await db.create (Books, [
{ title: 'Wuthering Heights', author: Emily },
{ title: 'Jane Eyre', author: Charlotte },
])
})
Without the enclosing
db.run(...)
the two INSERTs would be executed in two separate transactions, if that code would have run without an outer tx in place already.
This method is also used by srv.dispatch()
to ensure single operations happen within a transaction. All subsequent nested operations started from within an event handler, will all be nested transactions to the root transaction started by the outermost service operation.
Learn more about transactions and tx<srv>
transaction objects in cds.tx
docs
srv. dispatch (event)
async function srv.dispatch (
this : srv | Transactional <srv>,
event : cds.Event | cds.Request | cds.Event[] | cds.Request[]
)
return : result of this.handle(event)
This is the central method handling all requests or event messages sent to a service. Argument event
is expected to be an instance of cds.Event
or cds.Request
.
The implementation basically works like that:
// Ensure we are running in a proper tx, nested or root
if (!this.context) return this.run (tx => tx.dispatch(req))
// Handle batches of queries
if (req.query is array) return Promise.all (req.query.map(this.dispatch))
// Ensure req.target is properly determined
if (!req.target) req.target = _infer_target (req)
// Actually handle the request
return this.handle(req)
Basically, methods srv.dispatch()
and .handle()
are designed as a pair, with the former caring for all preparatory work, and the latter actually processing the request by executing matching event handlers.
TIP
When looking for overriding central event processing, rather choose srv.handle()
as that doesn't have to deal with all such input variants, and is guaranteed to be in tx mode.
srv. handle (event)
async function srv.handle (
this : Transactional <srv>,
event : cds.Event | cds.Request
)
return : result of executed .on handlers
This is the internal method called by this.dispatch()
to actually process requests or events by executing registered event handlers. Argument event
is expected to be an instance of cds.Event
or cds.Request
.
The implementation basically works like that:
// before phase
await Promise.all (matching .before handlers)
if (req.errors) throw req.reject()
// on phase
await (event.reply //> synchronous?
? Promise.seq (matching .on handlers) // for synchronous requests
: Promise.all (matching .on handlers) // for asynchronous events
)
if (req.errors) throw req.reject()
// after phase
await Promise.all (matching .after handlers)
if (req.errors) throw req.reject()
return req.results
With Promise.seq()
defined like this:
Promise.seq = handlers => async function next(){
req.results = await handlers.shift()?.(req, next)
}()
All matching .before
, .on
, and .after
handlers are executed in corresponding phases, with the next phase being started only if no req.errors
have occurred. In addition, note that...
before
handlers are always executed concurrentlyon
handlers are executed...- sequentially for instances of
cds.Requests
- concurrently for instances of
cds.Event
- sequentially for instances of
after
handlers are always executed concurrently
In effect, for asynchronous event messages, that is, instances of cds.Event
, sent via srv.emit()
, all registered .on
handlers are always executed. In contrast to that, for synchronous resuests, that is, instances of cds.Requests
this is up to the individual handlers calling next()
. See srv.on(request)
for an example.
srv. foreach (entity)
function foreach(
query: CQN, callback: (row: object) => void
)
Executes the statement and processes the result set row by row. Use this API instead of cds.run
if you expect large result sets. Then they're processed in a streaming-like fashion instead of materializing the full result set in memory before processing.
Common Usages:
cds.foreach (SELECT.from('Foo'), each => console.log(each))
cds.foreach ('Foo', each => console.log(each))
As depicted in the second line, a plain entity name can be used for the
entity
argument in which case it's expanded to aSELECT * from ...
.
REST-style API
As an alternative to srv.send(method,...)
you can use these convenience methods:
- srv. get (path, ...)
- srv. put (path, ...)
- srv. post (path, ...)
- srv. patch (path, ...)
- srv. delete (path, ...)
Essentially they call srv.send()
with method filled in as follows:
srv.get('/Books',...) --> srv.send('GET','/Books',...)
srv.put('/Books',...) --> srv.send('PUT','/Books',...)
srv.post('/Books',...) --> srv.send('POST','/Books',...)
srv.patch('/Books',...) --> srv.send('PATCH','/Books',...)
srv.delete('/Books',...) --> srv.send('DELETE','/Books',...)
Leading slash in the path
argument results in the same behaviour as in srv.send()
: path
is sent unmodified to a service. Omitting the leading slash, or passing a reflected entity definition instead, constructs bound cds.ql
query objects, equivalent to CRUD-style API:
await srv.get(Books,201)
await srv.get(Books).where({author_ID:106})
await srv.post(Books).entries({title:'Wuthering Heights'})
await srv.post(Books).entries({title:'Catweazle'})
await srv.patch(Books).set({discount:'10%'}).where({stock:{'>':111}})
await srv.patch(Books,201).with({stock:111})
await srv.delete(Books,201)
CRUD-style API
As an alternative to srv.run(query)
you can use these convenience methods:
- srv. read (entity, ...)
- srv. create (entity, ...)
- srv. insert (...).into(entity)
- srv. upsert (...).into(entity)
- srv. update (entity, ...)
- srv. delete (entity, ...)
Essentially, they start constructing bound cds.ql
query objects as follows:
srv.read('Books',...)... --> SELECT.from ('Books',...)...
srv.create('Books',...)... --> INSERT.into ('Books',...)...
srv.insert(...).into('Books')... --> INSERT.into ('Books',...)...
srv.upsert(...).into('Books')... --> UPSERT.into ('Books',...)...
srv.update('Books',...)... --> UPDATE.entity ('Books',...)...
srv.delete('Books',...)... --> DELETE.from ('Books',...)...
You can further construct the queries using the cds.ql
fluent APIs, and then await
them for execution thru this.run()
. Here are some examples:
await srv.read(Books,201)
await srv.read(Books).where({author_ID:106})
await srv.create(Books).entries({title:'Wuthering Heights'})
await srv.insert(Books).entries({title:'Catweazle'})
await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}})
await srv.update(Books,201).with({stock:111})
await srv.delete(Books,201)
Which are equivalent to these usages of srv.run(query)
:
await srv.run( SELECT.from(Books,201) )
await srv.run( SELECT.from(Books).where({author_ID:106}) )
await srv.run( INSERT.into(Books).entries({title:'Wuthering Heights'}) )
await srv.run( INSERT.into(Books).entries({title:'Catweazle'}) )
await srv.run( UPDATE(Books).set({discount:'10%'}).where({stock:{'>':111}}) )
await srv.run( UPDATE(Books,201).with({stock:111}) )
await srv.run( DELETE.from(Books,201) )
We can also use tagged template strings as provided by cds.ql
:
await srv.read `Books` .where `ID=${201}`
await srv.create `Books` .entries ({title:'Wuthering Heights'})
await srv.update `Books` .where `ID=${201}` .with `title=${'Sturmhöhe'}`
await srv.delete `Books` .where `ID=${201}`