Search

Core Services API

Classes cds.Service, cds.Event, and cds.Request, plus additional factory methods to bootstrap, serve and connect to services, provide the essential APIs to implement and consume services.


cds.server — bootstrapping

A Node.js CAP server process is started with the cds serve CLI command, with cds run and cds watch as convenience variants.

Besides handling command line arguments and adding log output, cds serve essentially loads a built-in server.js module, which can be accessed through cds.server.

You can plug-in custom logic to the default bootstrapping choreography using a custom server.js in your project.

Built-in server.js

The built-in server.js constructs an express.js app, and bootstraps all CAP services using cds.connect and cds.serve. Its implementation essentially is as follows:

const cds = require('./lib'), {folders} = cds.env
const express = require('express')
module.exports = async(o)=>{                //> o = options from CLI

    const app = cds.app = o.app || express() 
    cds.emit ('bootstrap',app)              //> before bootstrapping

    app.use (express.static (folders.app))  //> default is ./app
    app.use ('/favicon.ico', o.favicon)     //> if none in ./app
    app.get ('/', o.index_html)             //> if none in ./app
    app.use (o.logger)                      //> basic logging

    cds.model = await cds.load (o.from)     //> default is '*'
    if (cds.requires.messaging)   await cds.connect.to('messaging')
    if (cds.requires.db) cds.db = await cds.connect.to('db')
    if (o.in_memory) await cds.deploy(cds.model).to(cds.db,o)
    cds.services = await cds.serve(o).from(cds.model).in(app)

    cds.emit ('served', cds.services)       //> after bootstrapping
    return app.listen (o.port || process.env.PORT || 4004)
}

Custom server.js

The CLI command cds serve optionally bootstraps from project-local ./server.js or ./srv/server.js. In there, register own handlers to bootstrap events emitted to the cds facade object as below:

const cds = require('@sap/cds')
// handle bootstrapping events...
cds.on('bootstrap', (app)=>{
  // add your own middleware before any by cds are added
})
cds.on('served', ()=>{
  // add more middleware after all CDS servies
})
// delegate to default server.js:
module.exports = cds.server 

Provide an own bootstrapping function if you want to access and process the command line options. This also allows you to override certain options before delegating to the built-in server.js. In the example below, we construct the express.js app ourselves and fix the models to be loaded.

const cds = require('@sap/cds')
// handle bootstrapping events...
cds.on('bootstrap', ...)
cds.on('served', ...)
// handle and override options
module.exports = (o)=>{
  o.from = 'srv/precompiled-csn.json'
  o.app = require('express')()
  // then delegate to default server.js:
  return cds.server(o)
}

cds.once (‘bootstrap’, (express.js app)=>{})

A one-time event, emitted immediately after the express.js app has been created and before any middleware or CDS services are added to it.

cds.on (‘loaded’, (csn)=>{})

Emitted whenever a CDS model got loaded using cds.load()

cds.on (‘serving’, (service)=>{})

Emitted for each service constructed by cds.serve.

cds.on (‘connect’, (service)=>{})

Emitted for each service constructed through cds.connect.

cds.on (‘subscribe’, (service,event)=>{})

Emitted for each handler registered with a service through srv.on.

cds.once (‘served’, (services)=>{})

A one-time event, emitted when all services have been bootstrapped and added to the express.js app.

cds.once (‘listening’, ({server,url})=>{})

A one-time event, emitted when the server has been started and is listening to incoming requests.

cds.serve… service(s)

Use cds.serve in to construct service providers from the service definitions in corresponding CDS models. Subsequently, you can add event handlers to the constructed providers.

Note: By default, bootstrapping of servers is handled by the framework automatically. So, you only need cds.serve if you want to control the bootstrapping yourself, for example, in a custom express server.js.

cds.serve (name) … service or { services }

Initiates a fluent API chain to construct service providers; use the methods documented below to add more options.

Common Usages:

const { CatalogService } = await cds.serve ('my-services')
const app = require('express')()
cds.serve('all') .in (app)

Arguments:

  • name specifies which service to construct a provider for; use all to construct providers for all definitions found in the models.
cds.serve('CatalogService')  //> serve a single service
cds.serve('all')             //> serve all services found

You may alternatively specify a string starting with './' or refer to a file name with a nonidentifer character in it, like '-' below, as a convenient shortcut to serve all services from that model:

cds.serve('./reviews-service')  //> is not an identifer thru '/'
cds.serve('reviews-service')    //> dito by '-', hence both act as:
cds.serve('all').from('./reviews-service')

The method returns a fluent API object, which is also a Promise resolving to either an object with 'all' constructed service providers, or to the single one created in case you specified a single service:

const { CatalogService, AdminService } = await cds.serve('all')
const ReviewsService = await cds.serve('ReviewsService')

Caching:

The constructed service providers are cached in cds.services, which (a) makes them accessible to cds.connect, as well as (b) allows us to extend already constructed services through subsequent invocation of cds.serve.

Common Usages and Defaults

Most commonly, you’d use cds.serve in a custom file to add all the services to your express.js app as follows:

const app = require('express')()
cds.serve('all').in(app)
app.listen()

This uses these defaults for all options:

Option Description Default
cds.serve … which services to construct 'all' services
.from models to load definitions from './srv' folder
.in express app to mount to — none —
.to client protocol to serve to 'fiori'
.at endpoint path to serve at @path or .name
.with implementation function @impl or ._source.js

Alternatively you can construct services individually, also from other models, and also mount them yourself, as document in the subsequent sections on individual fluent API options.

If you just want to add some additional middleware, it’s recommended to bootstrap from a custom server.js.

.from (model)

Allows to determine the CDS models to fetch service definitions from, which can be specified as one of:

  • A filename of a single model, which get’s loaded and parsed with [cds.load]
  • A name of a folder containing several models, also loaded with [cds.load]
  • The string 'all' as a shortcut for all models in the './srv' folder
  • An already parsed model in CSN format

The latter allows you to [cds.load] or dynamically construct models yourself and pass in the CSN models, as in this example:

const csn = await cds.load('my-services.cds')
cds.serve('all').from(csn)...

If omitted, './srv' is used as default.

.to (protocol)

Allows to specify the protocol through which to expose the service. Currently supported values are:

  • 'rest' plain http rest protocol without any OData-specific extensions
  • 'odata' standard OData rest protocol without any Fiori-specific extensions
  • 'fiori' OData protocol with all Fiori-specific extensions like Draft enabled

If omitted, 'fiori' is used as default.

.at (path)

Allows to programmatically specify the mount point for the service.

Note that this is only possible when constructing single services:

cds.serve('CatalogService').at('/cat')
cds.serve('all').at('/cat') //> error

If omitted, the mount point is determined from annotation @path, if present, or from the service’s lowercase name, excluding trailing Service.

service MyService @(path:'/cat'){...}  //> served at: /cat
service CatalogService {...}           //> served at: /catalog

.in (express app)

Adds all service providers as routers to the given express app.

const app = require('express')()
cds.serve('all').in(app)
app.listen()

.with (impl function)

Allows to specify a function that adds event handlers to the service provider, either as a function or as a string referring to a separate node module containing the function.

cds.serve('./srv/cat-service.cds') .with ('./srv/cat-service.js')
cds.serve('./srv/cat-service') .with (srv => {
  srv.on ('READ','Books', (req) => req.reply([...]))
})

learn more about using impl annotations learn more about adding event handlers

Note that this is only possible when constructing single services:

cds.serve('CatalogService') .with (srv=>{...})
cds.serve('all') .with (srv=>{...})  //> error

If omitted, an implementation is resolved from annotation @impl, if present, or from a .js file with the same basename than the CDS model, for example:

service MyService @(impl:'cat-service.js'){...}
srv/cat-service.cds  #> CDS model with service definition
srv/cat-service.js   #> service implementation used by default

cds.connect service

Use cds.connect to connect to local or external services; the latter include databases.

cds.connect.to (name?, options?) service

Connects to a required service and returns a Promise resolving to a corresponding Service instance. Subsequent invocations with the same service name all return the same instance.

Common Usage:

const srv = await cds.connect.to ('some-service')
const { Books } = srv.entities
srv.run (SELECT.from(Books))

Arguments:

If both, service and options are given, the ad-hoc options are merged with the configured ones (overriding the configured values on same properties). If service is omitted, this is an ad-hoc connection only.

Caching:

Service instances are cached in cds.services, thus subsequent connects with the same service name return the initially connected one. As services constructed by cds.serve are registered with cds.services as well, a connect finds and returns them as local service connections.

Alternative Usages:

Connect to a required service (→ standard for productive code)

cds.connect.to ('db')

Ad-hoc connection (→ only for tests)

cds.connect.to ({ kind:'sqlite', credentials:{database:'my.db'} })

Shortcut for ad-hoc connections

cds.connect.to ('sqlite:my.db')

Configuring Required Services

When connecting to services using cds.connect, the provided service names are used to look up service implementation classes, as well as other details and credentials, from respective configurations in cds.requires sections in your package.json or in .cdsrc.json (omitting the cds. prefix). These configurations are constructed as follows…

cds.requires.<service>.impl

Service implementations are ultimately configured in cds.requires like that:

"cds": { "requires": {
  "some-service": { "impl": "some/node/module/path" },
  "another-service": { "impl": "./local/module/path" }
}}

Given that configuration, a cds.connect.to('some-service') would load the specific service implementation from some/node/module/path. Prefix the module path in impl with ./ to refer to a file relative to your project root.

cds.requires.<service>.kind

As service configurations inherit from each others along kind chains, we can refer to default configurations shipped with @sap/cds, as you commonly see that in our cap/samples, like so:

"cds": { "requires": {
  "db": { "kind": "sqlite" },
  "remote-service": { "kind": "odata" }
}}

This is backed by these default configurations:

"cds": { "requires": {
  "sqlite": { "impl": "@sap/cds-runtime/.../sqlite/service" },
  "odata": { "impl": "@sap/cds-runtime/.../odata/service" },
}}

Run cds env get requires to see all default configurations.
Run cds env get requires.db.impl to see the impl used for your database.

Given that configuration, a cds.connect.to('db') would load the generic service implementation.

Learn more about cds.env

cds.requires.<service>.model

Specify (imported) models for remote services in this property. This allows the service runtime to reflect on the external API and add generic features. The value can be either a single string referring to a CDS model source, resolved as absolute node module, or relative to the project root, or an array of such.

"cds": { "requires": {
  "remote-service": { "kind": "odata", "model":"some/imported/model" }
}}

Upon bootstrapping, all these required models will be loaded and compiled into the effective cds.model as well.

Service Bindings

In addition to the static configuration of requires services documented above, additional information, such as urls, secrets, or passwords are required to actually send requests to remote endpoints. These are dynamically filled into property credentials from process environments as explained below…

cds.requires.<service>.credentials

All service binding information goes into this property. It’s filled in from process environment when starting server processes, managed by deployment environments. For example, they’re filled in via VCAP_SERVICES if Cloud Foundry, or via direct process env assignments in other environments.

For development purposes, you can pass them on the command line or add them to a .env or default-env.json file like so:

# .env file
cds.requires.remote-service.credentials = { "url":"http://...", ... }

IMPORTANT:

  • Never ever add secrets or passwords to package.json or .cdsrc.json!
  • General rule of thumb: .credentials are always filled (and overridden) from process environment on process start.

One prominent exception of that, which you would frequently add to your package.json is the definition of a database file for persistent sqlite database during development:

  "cds": { "requires": {
    "db": {
      "kind": "sql",
      "[development]": {
        "kind": "sqlite",
        "credentials": {
          "database": "db/bookshop.db"
        }
      }
    }
  }}

cds.Service class

Class cds.Service provides the essential APIs for consuming services — that is sending requests, queries, or asynchronous events — as well as implementing services, by adding event handlers.

All constructed or connected services are instances of cds.Service or of subclasses thereof, most prominently:

Instances of cds.Service are constructed by cds.serve (→ for the first line in the list above) or cds.connect (→ all others).

Learn more about Service Implementations Learn more about Service Consumption

cds.Service — Properties

srv.name = string

The service’s name, that means, the definition’s name for services constructed with cds.serve, or the name of required services as passed to cds.connect.

cds.serve ('CatalogService')   //> .name = 'CatalogService'
cds.connect.to ('audit-log')   //> .name = 'audit-log'
cds.connect.to ('db')          //> .name = 'db'

srv.model csn

The linked model from which this service’s definition was loaded.

This is === cds.model by default, that is, unless you created services yourself with cds.serve, specifying alternative models to load and construct new services from.

srv.options = { … }

The effective options used to create this service in cds.serve or cds.connect, both in service configurations as well as the ad-hoc options parameter to cds.connect:

↳ o.impl = class | instance | function | module name

The implementation used by this service.

↳ o.kind = id

Refers to a predefined entry in the defaults of cds.requires, that means, one of:

  • sql, sqlite, hana → for database services
  • odata, rest, messaging → for external services

↳ o.model = csn

The model to use for this connection, either as an already parsed csn or as a filename of a model, which is then loaded with [cds.load].

↳ o.credentials = { … }

Specific options, passed to services. For example, in case of a SQLite database service these are driver-specific options node-sqlite3

srv.definition def

The linked service definition contained in the model which served as the blueprint for the given service instance.

srv.entities (namespace) {defs}
srv.events (namespace) {defs}
srv.operations (namespace) {defs}

These methods provide convenient access to the linked definitions of all entities, events, and operations (that means, actions and functions), provided by this service.

Common Usage:

const db = await cds.connect.to('db')
const { Books, Authors } = db.entities('my.booskhop')

These methods are actually shortcuts to their counterparts provided by linked models, with the default namespace being the service definition’s name.

Using Fully Qualified Names:

Many service consumption methods, such as srv.run expect fully qualified names of entities or an entity definition. Using these reflection methods greatly simplifies you coding as it avoids repeating namespaces all over the place.

For example, the fully qualified name of entity Books in cap/samples/bookshop is sap.capire.bookshop.Books. So, queries would have to be written like that using name strings:

const books = await cds.read ('sap.capire.bookshop.Books')

So, rather prefer that pattern:

const { Books } = cds.entities
const books = await cds.read (Books)

cds.Service — Implementations

Essentially, service implementations are collections of event handlers registered with the service instance to handle incoming requests and event messages. So, while actual documentation about how to write custom logic is in the event handlers section, the following sections introduce where to place that code, and in what forms.

See the Event Handlers section on how to actually add and write custom logic

Where to Implement Services?

In sibling .js files next to .cds sources

The easiest way to add service implementations is to simply place equally named .js files next to the .cds files containing the respective service definitions. In addition to direct siblings you can place them into relative subdirectories ./lib or ./handlers, allowing layouts like that:

srv/
  # all in one
  foo-srv.cds
  foo-srv.js
  bar-srv.cds
  bar-srv.js
srv/
  lib/
    foo-srv.js
    bar-srv.js
  foo-srv.cds
  bar-srv.cds
srv/
  handlers/
    foo-srv.js
    bar-srv.js
  foo-srv.cds
  bar-srv.cds

Using @impl annotations

Alternatively, add an @impl annotation to your service definition referring to a file in your project to load the implementation from.

 // relative to project root
service FooService @(impl:'src/lib/foo-srv') {...}
// relative to .cds source
service FooService @(impl:'./lib/bar-srv') {...}

Using @kind annotations

Unless you provide subclasses of cds.Service as implementations (→ see below), cds.serve constructs instances of cds.ApplicationService by default.

You can override this by adding a @kind annotation to your service definition:

service FooService @(kind:'app-service') {...}
service BarService @(kind:'api-service') {...}
service BazService @(kind:'api-service', impl:'./baz-srv') {...}

@kind:'app-service' as in the first line, is the default, used by cds.serve.
Run cds env get requires to see all default configurations.
Run cds env get requires.app-service to see default config.

The values of @kind annotations refer to respective configurations in cds.requires sections in your package.json or in .cdsrc.json (omitting the cds. prefix). For example:

"cds": { "requires": {
  "app-service": { "impl": "@sap/cds-runtime/..." },
  "api-service": { "impl": "..." },
}}

Learn more about configuring required services in the API docs of cds.connect.

How to Implement Services?

As cds.service.impl functions

The simplest way to provide custom event handlers is to return a function that registers event handler with the instance of cds.Service as follows:

module.exports = function(){ // `this` is the instance of cds.Service
  this.before (['CREATE','UPDATE'],'Books', (req)=>{...})
  this.on ('UPDATE','Books', (req)=>{...})
  this.after ('READ','Books', (books)=>{...})
}

Alternatively you can provide an arrow function like so:

module.exports = (srv)=>{ // `srv` is the instance of cds.Service
  srv.on ('UPDATE','Books', (req)=>{...})
  // ...
}

Getting Code Assists — Wrap the impl function into cds.service.impl(...), which simply returns the function but gives you code assists in tools like VSCode:

const cds = require('@sap/cds')
module.exports = cds.service.impl (function(){ ... })

Adding async/await — Quite frequently you need to call async functions in your impl functions, just declare your impl function as async to use await inside:

const cds = require('@sap/cds')
module.exports = cds.service.impl (async function(){ 
  const SomeOtherService = await cds.connect.to('SomeOtherService')
  // ...
})

As subclasses of cds.Service

Starting with @sap/cds 4 you can also return subclasses of cds.Service from service implementation modules like so:

// e.g. in srv/cat-service.js
const cds = require('@sap/cds')
module.exports = class CatalogService extends cds.ApplicationService {
  async init(){
    this.on ('UPDATE','Books', (req)=>{...}) // overrides the default handler
    await super.init()
    this.before (['CREATE','UPDATE'],'Books', (req)=>{...})
    // ...
  }
}

The init() method acts like a parameter-less constructor. Ensure to call await super.init() as in the previous example, to have the base class’s handlers added.

As also shown in the previous example, you may register own handlers before the base class’s ones, to intercept requests before the default handlers snap in.

While you should prefer overloading the init() method as shown in the previous example, sometimes you may also have to override the constructor, which has this signature (in typescript):

class Service {
  constructor (name:String, model:csn, options:object)
}

Adding Handlers to cds.connected Services

You can register event handlers with an instance of cds.Service wherever you have access to one, which includes services loaded with cds.connect. Hence, event handlers can be registered not only on the provider side, but also by consumers of services.

Using srv.on/before/after from the outside

Just invoke srv.on, .before, or .after on a connected service to add handlers:

const db = await cds.connect('db')
db.before ('INSERT','Orders', req => {...})
db.after ('READ','*', each => {...})

The first one would intercept all inserts to Orders on the primary database, similar to a before trigger in SQL. For example, you could do so to add mandatory validations. Another use case would be to add support for some special kinds of requests to remote services, which are not covered by generic implementations. Similarly, the second one would allow you to massage the results of all database reads somehow.

Using srv.prepend (<impl function>)

Use srv.prepend in order to register handlers, which shall be executed before already registered handlers. For example, the following would register a handler for inserting Orders that runs instead of the default handlers of the connected database service.

const db = await cds.connect('db')
db.prepend (()=>{
  db.on ('INSERT','Orders', req => {...})
})

Handlers added in subsequent invocations are added and executed before the ones from prior invocations.

cds.Service — Event Handlers

Register event handlers with instances of cds.Service to add custom logic to serve operations or react to events emitted by these services. Event handlers usually do their job by consuming other services.

srv.on (event, path?, handler) this

Handlers registered with this method are run in sequence, with each handler being able to terminate the sequence (cf. Outcomes below). This termination capability in combination with registering handlers using srv.prepend, lets you register custom handlers to run instead of the generic ones. If you want to defer to the generic handlers, invoke next in your custom handler (see Handling Examples below).

Arguments:

  • event — name of a single event or an array of multiple such
  • path — entity CSN object, name of an exposed entity, a path, or an array of multiple such
  • handler(req, next) — the handler function

A service handler registered by entity name can use a relative or fully qualified name, containing the service namespace. A database handler needs a fully qualified entity name.

learn more about the req argument in handler functions

Registration Examples:

// Direct request, for example, GET /Books
srv.on('READ','Books', (req)=>{...})
// Registration by fully-qualified name, for example, GET /Books
srv.on('READ','serviceName.Books', (req)=>{...})
// Navigation requests, for example, GET /Authors/201/authors
srv.on('READ','Authors/books', (req)=>{ let [ID] = req.params; ...})
// Registration by CSN, for example, GET /Books
srv.on('READ', Books, (req)=>{...})
// Calls to unbound actions/functions
srv.on('cancelOrder', (req)=>{...})
// Calls to bound actions/functions
srv.on('cancel','Order', (req)=>{...})

Handling Examples:

Requests can be handled in one of the following ways:

  1. Call req.reply synchronously or asynchronously:
srv.on('READ','Books', async (req)=> req.reply(await cds.tx(req).run(...))
srv.on('READ','Books', (req)=> req.reply(...))
  1. Return results or a Promise resolving to some → framework calls req.reply:
srv.on('READ','Books', (req)=> cds.tx(req).run(...))
srv.on('READ','Books', (req)=> [ ... ])
  1. Return a cds.ql object → framework does cds.run and req.reply:
srv.on('READ','Books', ()=> SELECT.from(Books))
  1. Call next as in express.js to delegate to handlers down the chain:
srv.on('READ','Books', (req,next)=>{
  if (...) return SELECT.from(Books) //> ... handle req my own
  else return next()  //> delegate to next/default handlers
})

IMPORTANT: Ensure to properly add calls to next() in your promise chain, either using await or by passing through its return value as shown in the previous example.

  1. Reject the request through req.reject, throw, or returning a rejecting Promise:
srv.on('READ','Books', (req)=> req.reject(409,'...'))
srv.on('READ','Books', (req)=> Promise.reject(...))
srv.on('READ','Books', (req)=> { throw ... })

Shortcuts:

As a yet more convenient shortcut to 3. you can just pass in the cds.ql object instead of a handler function; so, overall, these examples are equivalent:

const {Books} = srv.entities
srv.on('READ','Books', (req)=> cds.tx(req).run(SELECT.from(Books))
srv.on('READ','Books', ()=> SELECT.from(Books))
srv.on('READ','Books', SELECT.from(Books))

srv.before (event, entity?, handler) this

Registers a handler to run before the ones registered with .on(), that is, before the generic handlers. Commonly used to add custom input validations.

Arguments are the same as in srv.on, except that the handler only has the request argument (that is, handler(req)).

Examples:

this.before ('CREATE','Order', (req)=>{
  const order = req.data
  if (order.amount > 11)  throw 'Order amount must not exceed 11'
})

learn more about the req argument in handler functions

You can as well trigger additional operations in before handlers:

srv.before ('CREATE','Order', (req)=>{
  const order = req.data
  return UPDATE(Books).set ('stock -=', order.amount)
})

Note: This example would fail assumed you have a constraint on book stocks to not become negative, so the UPDATE also acts as a validation.

Note:

  • All before handlers are executed in order of their registration.
  • Always return a promise if your handlers trigger asynchronous tasks.
  • All promises returned are collected in a Promise.all and
  • The next phase running the .on handlers starts when all promises resolved.

Hence: You can terminate requests early by throwing exceptions or through req.reject in the handlers’ synchronous parts. Asynchronous parts however run in parallel, so you can’t react on side-effects between .before handlers.

srv.after (event, entity?, handler) this

Registers a handler to run after the generic handler. To be more precise: It runs on the results returned by the generic handler (which is asynchronous to the incoming handlers). Use it to modify the response.

Arguments are the same as in srv.on, except that the handler has the result set array as first and the request as second argument (that is, handler(data, req)).

Example:

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

Name the param each as convenience shortcut for a per-row handler:

this.after('READ','Books', (each)=>{
  each.stock > 111 && each.discount='11%'
})

Note: This is only meant for synchronous code due to performance.

Use the secondary param req in case you need to reflect on the inbound request:

this.after('READ','Books', (books,req)=>{
  if (req.data.ID) ...
})

Only synchronous modifications are allowed. That is, replacements and asynchronous modifications like in the following two examples aren’t possible:

this.after('READ','Books', (books)=>{
  return \<something else...\>
})
this.after('READ','Books', async (each)=>{
  const orders = await SELECT.from('Orders').where({book_ID: each.ID})
  orders.length > 100 && each.description = `Bestseller! ${each.description}`
})

Reasoning: Multiple after handlers can be added by different involved packages, including extension packages, and each of which would expect the same input. If one would exchange the results, the chain would break.

To replace/ asynchronously modify the result, use on handlers as follows:

this.on('READ','Books', async (req,next)=>{
  const books = await next()
  return \<something else...\>
})

In case the result is a primitive, for example, a string, then also synchronous modifications must be done in an on handler as the change would stay local to the after handler (pass by value).

srv.reject (event, entity?) this

Registers a generic handler that automatically rejects incoming request with a standard error message. You can specify multiple events and entities.

Arguments:

  • event — name of a single event or an array of multiple such
  • entity — name of an exposed entity or an array of multiple such

Examples:

this.reject ('READ', 'Orders')
this.reject (['CREATE','UPDATE','DELETE'], ['Books','Authors'])

Multiple handlers for same events

Arbitrary numbers of handlers for the same events can be registered. Those registered with .on are executed in order of their registration, while those registered with .before or .after the execution order isn’t guaranteed; they might be executed in parallel.

cds.serve('cat-service') .with (function(){
  this.on('READ', Books, ()=>{/* called first */})
  this.on('READ', Books, ()=>{/* called second */})
})

Single handlers for multiple events

Omit the <entity> argument to register handlers for all entities. Or add handlers for all events as well as standard express.js middlewares with method .use:

cds.serve('cat-service') .with (function(){
  this.on ('READ', ()=> {/* handles read events on all entities */})
  this.on ('*', ()=> {/* handles all events on all entities */})
})

cds.Service — Consumption

Each provided or connected instance of cds.Service supports the following client API to read and write data. The central methods are srv.emit to send requests or event messages, and srv.run to execute queries. In addition, methods like srv.read or srv.create are provided as convenient variants.

srv.emit ({ event, data?, … }) results
srv.emit ({ query, … }) results

This is the basic method to send both, asynchronous event messages, as well as synchronous requests. The implementation constructs an instance of cds.Request, which is then dispatched through all registered event handlers.

Common Usages:

You can use srv.emit as a basic and flexible alternative to srv.run, for example to send queries plus additional request headers to remote services:

const query = SELECT.from(Foo), tx = srv.tx(req)
tx.run (query)       //> without custom headers
tx.emit ({ query })  //> equivalent to tx.run()
tx.emit ({ query, headers:{...} })

A typical usage for asynchronous messages looks like that:

this.emit ({ event:'reviewed', data:{ subject, rating }, ... })
this.emit ('reviewed', { subject, rating }) //> see below

Learn more about Event Messages Learn more about Requests

Returns a Promise resolving to the response of respective event handlers.

For results of queries, see srv.run

srv.emit (event, data?) results

Convenience variant to srv.emit, which allows to specify the primary properties for emitting asynchronous event messages.
Here’s an example from cap/samples/reviews:

this.emit ('reviewed', { subject, rating })

srv.run (query) results

This is the central function to run queries. It expects a query or an array of queries in CQN notation, and returns a Promise resolving to the queries’ results.

Common Usage:

const srv = await cds.connect.to ('SomeService') 
const { Books, Authors } = srv.entities //> reflection
const books = await srv.run (SELECT.from(Books))
const [books, authors] = await srv.run ([SELECT.from(Books), SELECT.from(Authors)])

Learn more about service-related reflection using srv.entities Learn more about srv.run variant to send native query string

Returns a Promise resolving to…

  • to a number-like object indicating affected rows in case of UPDATE, DELETE.
  • an iterable allowing to reflect on (generated) primary keys
  • an array of rows for SELECT queries

For example, you can use results like so:

const tx = cds.tx(req)
const [ Emily, Charlotte ] = await tx.run (INSERT.into (Authors,[
   {name:'Emily Brontë'},
   {name:'Charlotte Brontë'},
]))
await tx.run (INSERT.into(Books).columns('title','author_ID').rows(
   [ 'Wuthering Heights', Emily.ID ],
   [ 'Jayne Eyre', Charlotte.ID ],
))
const books = await tx.run (SELECT.from(Books))
const affectedRows = UPDATE(Books,books[0].ID)
  .with(`stock -=`,order.amount)
  .where(`stock>=`,order.amount)
if (affectedRows < 1) req.reject(409,'Sold out, sorry')

srv.run (string, args?) results

Variant of srv.run which accepts native query strings, as understood by the receiving service, instead of CQN queries. For example, a SQL string in case of a connected SQL database.

Common Usage:

cds.run (`SELECT * from sap_capire_bookshop_Books`)
cds.run (`CALL Some_Stored_Procedure (11,'foo')`)
cds.run (`SELECT * from Some_Table_Function (11,'foo')`)

Note You need to pass fully qualified database table names.

Passing Arguments:

Argument args is an optional array or object of binding parameters bound to respective placeholders in the query using one of these placeholders:

  • :name — named parameters bound to respective entries in an args object
  • ? — positional parameters bound to entries of args in order of occurrence
cds.run ('SELECT * from Authors where name like ?',['%Poe%'])

Note: Prefer that over concatenating values into query strings to avoid SQL injection.

srv.foreach (entity | query, args?, callback) void

Executes the statement and processes the result set one row by one. Use that instead of cds.run if you expect large result sets as processes it in a streaming-like fashion instead of materializing the full set in memory before.

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 a SELECT * from ....

srv.create (entity) .entries? (data) query

srv.read (entity, key?) .where? (cond) query

srv.update (entity, key?) .with? (data) query

srv.delete (entity, key?) .where? (cond) query

srv.insert (data) .into (entity) query

These methods construct queries in a fluent method-call style instead of the Embedded QL style provided by cds.ql. The returned queries can be executed with await.

A typical usage is as follows:

const books = await cds.read('Books').orderBy('title')

Essentially, each of these methods simply starts a fluent query construction using their cds.ql counterparts, which can be continued using the respective tail methods, and sent to the service backend upon invocation of .then(). For example, think of the implementation of cds.read as follows:

read (entity) {
  const q = SELECT.from(entity)
  return Object.assign (q, { then:(r,e) => this.run(q).then(r,e) }
}

The following three variants to read Books are equivalent:

const books1 = await cds.run(SELECT.from(Books).where({ID:111}))
const books2 = await cds.read(Books).where({ID:111})
const books3 = await cds.get(Books).where({ID:111})
const books4 = await SELECT.from(Books).where({ID:111})

Method srv.insert is a SQL-reminiscent variant of srv.create with the following being equivalent:

srv.insert(data) .into (entity)
srv.create(entity) .entries (data)

srv.get (entity | path, data?) query

srv.put (entity | path, data?) query

srv.post (entity | path, data?) query

srv.patch (entity | path, data?) query

srv.delete (entity | path)

These methods are HTTP method-style counterparts to the CRUD-style convenience methods. As with these, the returned queries can be executed with await.

REST and OData services support these basic http methods, which are mapped to their CRUD counterpart as documented in the following. They can be used to construct data queries as with the CRUD variants as well as be used to send plain HTTP requests.

Common Usages:

await srv.get(Books).where({ID:111})
await srv.post(Books).entries({ID:111, ...})
await srv.patch(Books,111).with({...})
await srv.delete(Books,111)

These are equivalent to:

await srv.read(Books).where({ID:111})
await srv.create(Books).entries({ID:111, ...})
await srv.update(Books,111).with({...})
await srv.delete(Books,111)

Note: UPDATE translates to PATCH, not PUT.

Plain REST Usages:

While the previous usage samples still constructed queries, and rather worked as using syntax aliases, you can also use these methods to send plain, arbitrary HTTP requests to remote services. Just pass a string starting with / as the first argument to do so:

srv.post('/some/arbitrary/path', {foo:'bar'})
srv.get('/some/arbitrary/path/111')
srv.patch('/some/arbitrary/path', {foo:'bar'})
srv.delete('/some/arbitrary/path/111')

srv.<custom op> (…args) response

In case you declared (unbound) custom actions and functions in the service definition of a connected or provided service, the respective instance of cds.Service will automatically be equipped with corresponding JavaScript methods, allowing you to write concise and comprehensible code.

For example, with that service definition (taken from cap/samples/bookshop):

service CatalogService { ...
  action submitOrder (book: Books:ID, amount: Integer);
}

You can invoke the declared submitOrder action from your code as follows:

const cats = cds.connect.to('CatalogService')
const res1 = await cats.submitOrder ({ book:111, amount:1 })
const res2 = await cats.submitOrder (111,1) // positional args variant

cds.Service — Transactions

srv.tx (req?) Transaction < srv, req >

Starts a new transaction or joins an already started one for the given context. Usually this is used in event handlers as follows:

this.on ('READ','Books', async (req)=>{
  const srv = await cds.connect.to('another-service')
  const { Books } = srv.entities
  const tx = srv.tx(req)
  const books = await tx.run (SELECT.from (Books))
  return books
})

The returned object supports the same methods to read or write data than the service it’s called on, that is, the ones listed below.

Arguments:

  • context can be an arbitrary object to which the transaction is assigned. Most frequently it’s the req argument in an event handler. If omitted, a new transaction will be started.

Analysis:

Use this method whenever you call another service from the event handlers of your service. It ensures…

  • appropriate client credential flows and tenant isolation
  • propagation of requisite request context properties, such as the current user’s preferred language

For database services, this method in addition starts atomic transactions, which are automatically committed or rolled back when an error occurs.

cds.Service — Streaming Data

The following API allows streaming binary data properties from databases.

It comes in two variants: The first method accepting a read query as input returns the result stream only when the query matched to an existing row in the database. The second is a synchronous API providing the resulting stream immediately after necessary input parameters are obtained.

srv.stream (cqn) Promise < Readable Stream >

In this variant, the API is called asynchronously. The input CQN object must be a SELECT requesting for a single column and resulting in a single data row. Otherwise, an error is thrown. For example, the following code pipes the obtained stream to the standard output stream:

const stream = await srv.stream( SELECT('image').from('Foo',111) )
stream.pipe(process.stdout)

srv.stream (column)… Readable Stream
.from (entity)
.where (filter)

In this variant, the API is called synchronously. The three possibilities are equivalent alternative ways to use the API. After all necessary parameters (entity, column and filter) are obtained the resulting stream object is returned synchronously. For example, the following code snippets show how those alternative ways can be used:

const stream = srv.stream().from('T', {ID: 1}, a => a.data)
stream.pipe(process.stdout)
const stream = srv.stream('data').from('T', {ID: 1})
stream.pipe(process.stdout)
const stream = srv.stream('data').from('T').where({ID: 1})
stream.pipe(process.stdout)

cds.Event class

Class cds.Event represents event messages in asynchronous messaging, providing access to the event name, target, payload data, and headers. It also serves as the base class for cds.Request and hence for all synchronous interaction.

msg.event string

The name of the incoming event, which can be one of:

  • The name of an incoming CRUD request like CREATE
  • The name of an emitted CRUD event like UPDATED
  • The name of a custom operation like cancelOrder.
  • The name of a custom event like cancelledOrder

msg.entity string

This is a convenience shortcut to msg.target.name.

msg.target def

Refers to the current request’s target entity definition, if any; undefined for unbound actions/functions and events. The returned definition is a linked definition as reflected from the CSN model.

In case of OData navigation requests along associations, msg.target refers to the last target. For example:

OData Request req.target
Books AdminService.Books
Books/201/author AdminService.Authors
Books(201)/author AdminService.Authors

See also req.path to learn how to access full navigation paths See Entity Definitions in the CSN reference Learn more about linked models and definitions

msg.data {…} or […]

Contains the request payload if CREATE and UPDATE requests, which can either be a single object or an array of objects in case of bulk operations (example: await CatalogService.create('Books').entries([...])).
Contains the keys of the entity if DELETE and READ requests on a single entity through OData or REST.
Contains parameters for functions and payload for actions.

msg.headers {…}

Provides access to headers of the event message or request. In case of asynchronous event messages, it’s the headers information sent by the event source. If HTTP requests it is the standard Node.js headers of class IncomingMessage.

msg.timestamp number

Returns a stable timestamp for the current request being processed as a number representing the milliseconds elapsed since the UNIX epoch.

The first invocation on a request or any nested request calls and returns the response of Date.now(). Subsequent invocations return the formerly determined and pinned value.

The CAP framework uses that to fill in values for the CDS pseudo variable $now, with the guaranteed same timestamp value.

Learn more in the Managed Data guide Learn more on Date.now() in the MDN docs

cds.Request class

Class cds.Request extends cds.Event with additional features to represent and deal with synchronous requests to services in event handlers, such as the query, additional request parameters, the authenticated user, and methods to send responses.

req.method string

The HTTP method of the incoming request. In case of a request constructed with [req.event] as CRUD, the method is derived as follows:

req.event req.method
CREATE POST
READ GET
UPDATE PATCH
DELETE DELETE

req.params iterable

Provides access to parameters in url paths as an iterable with the contents matching the positional occurrence of parameters in the url path. In case of compound parameters, the respective entry is the key value pairs as given in the URL.

For example, the parameters in an HTTP request like that:

GET /catalog/Authors(101)/books(title='Eleonora',edition=2)`

The provided parameters can be accessed as follows:

const [ author, book ] = req.params
// > author === 101
// > book === { title: 'Eleonora', edition: 2 }

req.query cqn

Captures the incoming request as a CQN query object. For example, an HTTP request like GET http://.../Books would be captured as follows:

req.query = {SELECT:{from:{ref:['Books']}}}

If bound custom operations req.query contains the query to the entity, on which the bound custom operation is called. For unbound custom operations req.query contains an empty object.

req.path string

Captures the full canonicalized path information of incoming requests with navigation. If requests without navigation, req.path is identical to req.target.name (or req.entity, which is a shortcut for that).

Examples based on cap/samples/bookshop AdminService:

OData Request req.path req.target.name
Books AdminService.Books AdminService.Books
Books/201/author AdminService.Books/author AdminService.Authors
Books(201)/author AdminService.Books/author AdminService.Authors

See also msg.entity See also msg.target

req.user cds.User

Represents the currently logged in user.

Learn more in the Authentication guide

req._ {…}

Provides access to original inbound protocol-specific request objects. For events triggered by an HTTP request, it contains the original req and res objects as obtained from express.js.

req.on (event, handler)

Use this method to register handlers, executed when the whole request is finished.

req.on('succeeded', () => {...}) // request succeeded
req.on('failed', () => {...}) // request failed
req.on('done', () => {...}) // request succeeded/failed

Warning

Inside the handlers, you don’t have access to a database connection and can’t veto a commit, etc.

Note

The parameters passed to the event listeners are no stable API at this moment, so do not rely on them.

Transactional Boundary

The previously listed request lifecycle events (succeeded, failed, and done) are emitted based on the transactional boundary of the respective request. That is, for an individual request (incl. requests inside OData batch requests without an atomicity group assignment), the events are emitted once the request was completed. For requests that are part of a changeset (that means, requests with an atomicity group assignment), the events are emitted once the entire changeset was completed. Following the atomicity property (“all or nothing”), if at least one of the requests in the changeset fails, all requests fail.

A request or changeset is completed successfully, once the respective transaction was committed (given a database). Hence, the succeeded event handler can’t be used for anything that shall lead to a rejection of the request, for example, additional validity checks. For such cases, use service-level event handlers as shown in the following example:

const AdminService = await cds.connect.to('AdminService')
AdminService.after('UPDATE', 'Orders', function(data, req) {
  if ([...]) {
    req.reject(new Error("Veto UPDATE Orders!"))
  }
})

const DatabaseService = await cds.connect.to('db')
DatabaseService.before('COMMIT', function(req) {
  if ([...]) {
    req.reject(new Error("Veto entire transaction!"))
  }
})

req.reply (results)

Stores the given results in req.results, which will then be sent back to the client, rendered in a protocol-specific way.

req.notify, info, warn, error (code?, msg, target?, args?)

Use these methods to collect messages or error and return them in the request response to the caller. The method variants reflect different severity levels, use them as follows:

Variants

Method Collected in Typical UI Severity
req.notify req.messages Toasters 1
req.info req.messages Dialog 2
req.warn req.messages Dialog 3
req.error req.error Dialog 4

Note: messages with severity < 4 a collected and accessible in property req.messages, while error messages are collected in property req.errors. The latter allows to easily check, whether errors occurred with:

if (req.errors) //> get out somehow...

Arguments

  • code Number, that means, HTTP status codes
  • msg String | Object | Error → see below for details on the non-string version.
  • target the name of an input field/element a message is related to.
  • args array of placeholder values → see Localized Messages for details.

Using an Object as Argument

You can also pass an object as the sole argument, which then contains the properties code, message, target, and args.

req.error ({
  code: 'Some-Custom-Code',
  message: 'Some Custom Error Message',
  target: 'some_field'
})

In OData responses, notifications get collected and put into header sap-messages as a stringified array, while the others are collected in the respective response body properties (→ see OData Error Responses).

req.reject (code?, msg, target?, args?)

Rejects the request with the given HTTP response code and single message. No additional handlers will be executed once req.reject has been invoked. If called multiple times in the same handler, the last call wins (LIFO).

Arguments are the same as for req.error