Skip to content
On this page

Core Services

TIP

This is an overhauled documentation for cds.Service→ find the old one here.

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:

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 }
}
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:

js
cds.app = require('express')()
cds.model = await cds.load('*')
cds.services = await cds.serve('all').from(cds.model).in(cds.app)
cds.app = require('express')()
cds.model = await cds.load('*')
cds.services = await cds.serve('all').from(cds.model).in(cds.app)

Learn more about cds.serve

Required Services

In addition to provided services, your applications commonly need to consume required services. Most prominent example for that is the primary database cds.db. Others could be application services provided by other enterprise applications or micro services, or 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.

json
{"cds":{
  "requires": {
    "ReviewsService": { "kind": "odata", "model": "@capire/reviews" },
    "db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }},
  }
}}
{"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:

js
const ReviewsService = await cds.connect.to('ReviewsService')
const db = await cds.connect.to('db')
const ReviewsService = await cds.connect.to('ReviewsService')
const db = await cds.connect.to('db')

Learn more about cds.connect

Implementing Services

By default cds.serve creates instances of cds.ApplicationService for each found service definition, which 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. Learn below about:

  • How to provide custom implementations?
  • Where, that is, in which files, to put that?

In sibling .js files, next to .cds sources

The easiest way to add custom service implementations is to simply place an equally named .js file next to the .cds file containing the respective service definition. For example, as in cap/samples/bookshop:

zsh
bookshop/
├─ srv/
 ├─ admin-service.cds
 ├─ admin-service.js
 ├─ cat-service.cds 
 └─ cat-service.js 
└─ ...
bookshop/
├─ srv/
 ├─ admin-service.cds
 ├─ admin-service.js
 ├─ cat-service.cds 
 └─ cat-service.js 
└─ ...
Alternatively in subfolders lib/ or handlers/...

In addition to direct neighbourhood you can place your impl files also in nested subfolders lib/ or handlers/ like that:

zsh
bookshop/
├─ srv/
 └─ lib/ # or handlers/ 
  ├─ admin-service.js
  └─ cat-service.js
 ├─ admin-service.cds
 └─ cat-service.cds
└─ ...
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:

cds
@impl: 'srv/cat-service.js' 
service CatalogService { ... }
@impl: 'srv/cat-service.js' 
service CatalogService { ... }

The impl configuration property for required services:

json
{ "cds": {
  "requires": {
    "ReviewsService": {
      "impl": "srv/reviews-services.js" 
    }
  }
}}
{ "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():

js
class BooksService extends cds.ApplicationService {
  init() {
    const { Books, Authors } = this.entities
    this.before ('READ', Authors, req => {...})
    this.after ('READ', Books, req => {...})
    this.on ('submitOrder', req => {...})
    return super.init()
  }
}
module.exports = BooksService
class BooksService extends cds.ApplicationService {
  init() {
    const { Books, Authors } = this.entities
    this.before ('READ', Authors, req => {...})
    this.after ('READ', Books, req => {...})
    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:

js
const cds = require('@sap/cds')
module.exports = cds.service.impl (function(){ ... }) 
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:

js
const cds = require('@sap/cds')
module.exports = new class extends cds.ApplicationService {
  async init() {
    await srv_impl_fn .call (this,this) 
    return super.init()
  }
}
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:

cds
// services.cds
namespace foo.bar;
service Foo {...}
service Bar {...}
// 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:

js
// services.js
exports['foo.bar.Foo'] = class Foo {...}
exports['foo.bar.Boo'] = class Bar {...}
// services.js
exports['foo.bar.Foo'] = class Foo {...}
exports['foo.bar.Boo'] = class Bar {...}

The exports' names must match the servce definitions' fully-qualified names.

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:

js
const srv = await cds.connect.to ('BooksService')
const srv = await cds.connect.to ('BooksService')

Using REST-style APIs:

js
await srv.create ('/Books', { title: 'Catweazle' })
await srv.read ('GET','/Books/206')
await srv.send ('submitOrder', { book:206, quantity:1 })
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:

js
await srv.submitOrder({ book:206, quantity:1 })
await srv.submitOrder(206,1)
await srv.submitOrder({ book:206, quantity:1 })
await srv.submitOrder(206,1)

Using Query-style APIs:

js
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}}) )
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:

js
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}})
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:

js
await srv.emit ('SomeEvent', {foo:'bar'})
await srv.emit ('SomeEvent', {foo:'bar'})
js
await srv.emit ({ event: 'OrderedBooks', data: {
  book: 206, quantity: 1,
  buyer: 'alice@wonderland.com'
}})
await srv.emit ({ event: 'OrderedBooks', data: {
  book: 206, quantity: 1,
  buyer: 'alice@wonderland.com'
}})
js
await srv.emit ('OrderedBooks', {
  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 extend. 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.

Class 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:

js
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))
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:

js
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
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, or cds.connect() for required ones.

Service ( ... )

tsx
function constructor (
  name    : string,
  model   : CSN,
  options : { kind: string, ... }
)
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:
js
const srv = await cds.connect.to('audit-log')
srv.name //> 'audit-log'
const srv = await cds.connect.to('audit-log')
srv.name //> 'audit-log'

. model

tsx
var srv.model      : LinkedCSN
var srv.definition : LinkedCSN service definition
var srv.model      : LinkedCSN
var srv.definition : LinkedCSN service definition
  • model, a LinkedCSN, is the CDS model from which this service was constructed
  • definiton, a LinkedCSN definition from which this service was constructed

. options

tsx
var srv.options : { //> from cds.requires config
  service : string, // the definition's name if different from srv.name
  kind    : string,
  impl    : string,
}
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

tsx
var srv.entities/events/operations : Iterable <{
  name : CSN definition
}>
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:

js
// 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
// 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()

tsx
async function 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:

js
class BooksService extends cds.ApplicationService {
  init(){
    const { Books, Authors } = this.entities
    this.before ('READ', Authors, req => {...})
    this.after ('READ', Books, req => {...})
    this.on ('submitOrder', req => {...})
    return super.init()
  }
}
class BooksService extends cds.ApplicationService {
  init(){
    const { Books, Authors } = this.entities
    this.before ('READ', Authors, req => {...})
    this.after ('READ', Books, req => {...})
    this.on ('submitOrder', req => {...})
    return super.init()
  }
}

Ensure to call super.init() to allow subclassses 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()

tsx
async function srv.prepend(()=>{...})
async function srv.prepend(()=>{...})

Sometimes, you need to register handlers to run before handlers registered by others before. Use srv.prepend() to do so ´, for example like this:

js
cds.on('served',()=>{
  const { SomeService } = cds.services
  SomeService.prepend (()=>{
    SomeService.on('READ','Foo', (req,next) => {...})
  })
})
cds.on('served',()=>{
  const { SomeService } = cds.services
  SomeService.prepend (()=>{
    SomeService.on('READ','Foo', (req,next) => {...})
  })
})

srv. on, before, after()

tsx
function srv.on/before/after (
  event   : string | string[] | '*',
  entity? : CSN definition | CSN definition[] | string | string[] | '*',
  handler : function
)
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:

js
class BooksService extends cds.ApplicationService {
  init(){
    const { Books, Authors } = this.entities
    this.on ('READ',[Books,Authors], req => {...})
    this.after ('READ',Books, books => {...})
    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()
  }
}
class BooksService extends cds.ApplicationService {
  init(){
    const { Books, Authors } = this.entities
    this.on ('READ',[Books,Authors], req => {...})
    this.after ('READ',Books, books => {...})
    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, e.g. by reading/writing data from/to databases
  • .before handlers run before the .on handlers, frequently for validating inbound data
  • .after handlers run before 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'
  • Any other string name of a custom action or function – e.g., '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 from this.entities
  • A string matching the name of an entity served by this service
  • A path navigating from a served entity to associated ones → e.g. '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)

tsx
function srv.before (event, entity?, handler: (
  req : cds.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:

js
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}`
})
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:

js
this.before ('submitOrder', async req => {
  await UPDATE(Books).set ('stock -=', req.data.quantity)
})
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)

tsx
function srv.after (event, entity?, handler: (
  results : object[] | any,
  req     : cds.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 before
  • req — an instance of cds.Request

Examples:

js
this.after ('READ', Books, books => {
  for (let b of books) if (b.stock > 111) b.discount = '11%'
})
this.after ('READ', Books, books => {
  for (let b of books) if (b.stock > 111) b.discount = '11%'
})

Learn more about how requests are processed by srv.handle(req)

srv. on (request)

tsx
function srv.on (event, entity?, handler: (
  req  : cds.Request,
  next : function
)))
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, e.g. by reading/writing data from/to databases. The handlers receive two arguments:

  • req — an instance of cds.Request providing access to all request data
  • next — a function which allows handlers to pass control down the interceptor stack

Examples:

js
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)
})
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...
js
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 },
}
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 the req.target property which points to the CSN definition of the entity addressed by the incoming request → matching one of Books or Authors we obtained from this.entities above.

  • The UPDATE handler is using the req.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:

js
// 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)
...
// 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)

tsx
function srv.on (event, handler: (
  msg : cds.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 synchrounous requests, but work slightly different:

  1. They are usually registered 'from the outside', not as part of a service's implementation.
  2. They receive only a single argument: msg, an instance of cds.Event; no next.
  3. 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:

js
this.on ('submitOrder', async req => {
  // ... handle the request, and inform whoever might be interested:
  await this.emit('BooksOrdered', req.data) 
})
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:

js
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
  })
})
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:

js
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
}))
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)

ts
function srv.on ('error', handler: (
   err : Error,
   req : cds.Event | cds.Request
))
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:

js
this.on ('error', (err, req) => {
  err.message = 'Oh no! ' + err.message
})
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, i.e., not async, not returning Promises.

srv. send (request)

ts
async function srv.send (
  method   : string | { method, path?, data?, headers? },
  path?    : string,
  data?    : object | any,
  headers? : object
)
return : result of this.dispatch(req)
async function srv.send (
  method   : string | { method, path?, data?, 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 a HTTP method, or a name of a custom action or function
  • path can be an arbitrary URL, starting with a leading '/'

Examples:

js
await srv.send('POST','/Books', { title: 'Catweazle' })
await srv.send('GET','/Books')
await srv.send('GET','/Books/201')
await srv.send('submitOrder',{...})
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:

js
srv.on('CREATE','Books', req => {...})
srv.on('READ','Books', req => {...})
srv.on('submitOrder', req => {...})
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:

js
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)
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)

See also REST-Style Convenience API below

srv. emit (event)

ts
async function srv.emit (
  event    : string | { event, data?, headers? },
  data?    : object | any,
  headers? : object
)
return : nothing
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:

js
await srv.emit ({ event: 'SomeEvent', data: { foo: 'bar' }})
await srv.emit ('SomeEvent', { foo:'bar' })
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:

js
Emitter.on('SomeEvent', msg => {...})
Emitter.on('SomeEvent', msg => {...})

The implementation essentially constructs and dispatches instances of cds.Event like so:

js
let msg = new cds.Event (
  (event is object) ? event : { event, data, headers }
)
return this.dispatch(msg)
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)

ts
async function srv.run (
  query : CQN | CQN[]
)
return : result of this.dispatch(req)
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:

js
await srv.run( INSERT.into(Books,{ title: 'Catweazle' }) )
await srv.run( SELECT.from(Books,201) )
await srv.run([
  SELECT.from(Authors),
  SELECT.from(Books)
])
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:

js
srv.on('CREATE',Books, req => {...})
srv.on('READ',Books, req => {...})
srv.on('CREATE',Books, req => {...})
srv.on('READ',Books, req => {...})

The implementation essentially constructs and dispatches instances of cds.Request like so:

js
let req = new cds.Request({query})
return this.dispatch(req)
let req = new cds.Request({query})
return this.dispatch(req)

See also CRUD-Style Convenience API below

srv. run ( fn )

tsx
function srv.run ( fn? : tx<srv> => {...} ) => Promise
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:

js
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 },
  ])
})
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 all 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)

ts
async function srv.dispatch (
  this  : srv | Transactional <srv>,
  event : cds.Event | cds.Request | cds.Event[] | cds.Request[]
)
return : result of this.handle(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:

js
// 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)
// 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)

ts
async function srv.handle (
  this  : Transactional <srv>,
  event : cds.Event | cds.Request
)
return : result of executed .on handlers
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:

js
// 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
// 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:

js
Promise.seq = handlers => async function next(){
  req.results = await handlers.shift()?.(req, next)
}()
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 occured. In addition, note that...

  • before handlers are always executed concurrently
  • on handlers are executed...
    • sequentially for instances of cds.Requests
    • concurrently for instances of cds.Event
  • after handlers are always executed concurrently

In effect, for asynchronous event messages, i.e., instances of cds.Event, sent via srv.emit(), all registered .on handlers are always executed. In contrast to that, for synchronous resuests, i.e., instances of cds.Requests this is up to the individual handlers calling next(). See srv.on(request) for an example.

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:

js
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',...)
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',...)

You can also use them as REST-style variants to run queries by omitting the leading slash in the path argument, or by passing a reflected entity definition instead. In that case they start constructing bound cds.ql query objects, as their CRUD-style counterparts:

js
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)
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 (entity, ...)
  • srv. upsert (entity, ...)
  • srv. update (entity, ...)
  • srv. delete (entity, ...)

Essentially, they start constructing bound cds.ql query objects as follows:

js
srv.read('Books',...)...   --> SELECT.from ('Books',...)...
srv.create('Books',...)... --> INSERT.into ('Books',...)...
srv.insert('Books',...)... --> INSERT.into ('Books',...)...
srv.upsert('Books',...)... --> UPSERT.into ('Books',...)...
srv.update('Books',...)... --> UPDATE.entity ('Books',...)...
srv.delete('Books',...)... --> DELETE.from ('Books',...)...
srv.read('Books',...)...   --> SELECT.from ('Books',...)...
srv.create('Books',...)... --> INSERT.into ('Books',...)...
srv.insert('Books',...)... --> INSERT.into ('Books',...)...
srv.upsert('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:

js
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)
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):

js
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) )
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:

js
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}`
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}`