Getting Started in a Nutshell

Using a minimalistic setup

Get started in a minimalistic setup with Node.js, express and sqlite.

You learn step-by-step how to:

Start a New Project

Create a project with minimal defaults as follows (in Windows Powershell, use semicolon “;” to chain the two commands):

cds init bookshop && cd bookshop

Launch cds watch

As a most-automated jumpstart, we just tell cds to watch out for things to come:

cds watch

This actually tries starting a cds server process, but as there’s no content in our project so far it just keeps waiting for content with a message like that:

[cds] - running nodemon...
--exec cds run --with-mocks --in-memory?
--ext cds,csn,csv,ts,mjs,cjs,js,json,edmx,xml

    No models found at ./db ./srv ./schema.cds ./services.cds.
    Waiting for some to arrive...

Or Download Samples

Instead of doing all this step-by-step, you can alternatively download and run our samples from GitHub:

git clone cap-samples
cd cap-samples && npm i

Then cd into the bookshop sample and launch cds watch as follows:

cd packages/bookshop
cds watch

Defining Domain Models

Let’s feed it by adding a simple domain model. Do so by creating a file named db/schema.cds and copy the following into it:

namespace sap.capire.bookshop;
using { Currency, managed } from '@sap/cds/common';

entity Books : managed {
  key ID   : Integer;
  title    : localized String(111);
  descr    : localized String(1111);
  author   : Association to Authors;
  stock    : Integer;
  price    : Decimal(9,2);
  currency : Currency;

entity Authors : managed {
  key ID   : Integer;
  name     : String(111);
  books    : Association to many Books on = $self;

entity Orders : managed {
  key ID   : UUID;
  OrderNo  : String @title:'Order Number'; //> readable key
  Items    : Composition of many OrderItems on Items.parent = $self;
entity OrderItems {
  key ID   : UUID;
  parent   : Association to Orders;
  book     : Association to Books;
  amount   : Integer;

Learn more about Domain Modelling Learn more about CDS

Deployed to in-memory DB automatically

As soon as you saved your file, the running cds watch should react immediatelly with an output like that:

[cds] - connect to datasource - sqlite::memory:
/> successfully deployed to sqlite in-memory db

This means, cds watch detected the changes in db/schema.cds and automatically bootstrapped an in-memory sqlite database when restarting the server process.

Compiling Models (optional)

We can also test-compile models individually to check for validity and produce a parsed output in CSN format:

cds db/schema.cds

This dumps the compiled CSN model as a plain JavaScript object to stdout.
Add --to <target> (shortcut -2) to produce other outputs, for example:

cds db/schema.cds -2 json
cds db/schema.cds -2 yml
cds db/schema.cds -2 sql

Defining Services

After the recent changes, cds watch also printed this message:

Error: [cds] -
  No service models found in current working directory.

So, let’s go on feeding it with service definitions. Following the best practice of single-purposed services, we’ll define two services for different use cases…

A Service for Administrators to Maintain Books and Authors

Copy this into a file named srv/admin-service.cds:

using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(_requires:'admin') {
  entity Books as projection on my.Books;
  entity Authors as projection on my.Authors;
  entity Orders as select from my.Orders;

A Service for End Users to Browse and Order Books

Copy this into a file named srv/cat-service.cds:

using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {

  @readonly entity Books as SELECT from my.Books {*, as author
  } excluding { createdBy, modifiedBy };

  @requires_: 'authenticated-user'
  @insertonly entity Orders as projection on my.Orders;

Learn more about defining services

Served to OData automatically

This time cds watch should have reacted with additional output like that:

[cds] - serving AdminService at /admin
[cds] - serving CatalogService at /browse
[cds] - launched in: 1228.289ms
[cds] - server listening on http://localhost:4004 ...

As can be read from the log output, the two service definitions have been compiled and generic service providers have been constructed to serve requests on the listed endpoints /admin and /browse.

Open http://localhost:4004 in your browser and try out the provided links.

Compiling APIs (optional)

As above, you can also compile service definitions explicitly, for example to an OData model:

cds srv/cat-service.cds -2 edmx

Essentially this invokes via CLI what happened automatically behind the scenes in the steps before. While we don’t need such explicit compile steps, you can do that, for example to test correctness on model level.

Using Databases

As shown above, we use an automatically bootstrapped sqlite in-process and in-memory database by default, that is unless told otherwise, which speeds up development significantly. cds has built-in support for that through cds watch, which in turn is a shortcut for cds serve --in-memory ....

Add Initial Data

Now let’s fill our database with initial data by adding a few plain CSV files under db/data as follows:


201;Wuthering Heights;101;12
207;Jane Eyre;107;11
251;The Raven;150;333


101;Emily Brontë
107;Charlotte Brontë
150;Edgar Allen Poe
170;Richard Carpenter

Try some Requests

As we now have a fully capable SQL database connected filled with some initial data, we can send complex OData queries, served by the built-in generic providers:

Learn more about generic providers

Use a Persistent Database

Instead of using in-memory, we can also use persistent databases, of course. For example still with SQLite:

npm add sqlite3 -D
cds deploy --to sqlite:my.db

The differrence to the automatically provided in-memory db is that we now get a persistent database stored in the local file ./my.db. This is also recorded in the package.json.

To see what that did, use the sqlite3 cli with the newly created database:

sqlite3 sqlite.db .dump
sqlite3 sqlite.db .tables

Similarly you could deploy to a provisioned SAP HANA database using this variant:

cds deploy --to hana

Adding Custom Logic

While generic handlers serve all the standard CRUD requests, including reading and writing structured documents, we may add custom handlers to care for the specific domain logic of our application. For example, paste the following into a new file srv/cat-service.js:


 * Implementation for CatalogService defined in ./cat-service.cds
module.exports = (srv)=>{

  // Use reflection to get the csn definition of Books
  const {Books} = cds.entities

  // Add some discount for overstocked books
  srv.after ('READ','Books', (each)=>{
    if (each.stock > 111) each.title += ' -- 11% discount!'

  // Reduce stock of books upon incoming orders
  srv.before ('CREATE','Orders', async (req)=>{
    const tx = cds.transaction(req), order =;
    if (order.Items) {
      const affectedRows = await =>
        UPDATE(Books) .where({ID:item.book_ID})
          .and(`stock >=`, item.amount)
          .set(`stock -=`, item.amount)
      if (affectedRows.some(row => !row)) req.error(409, 'Sold out, sorry')


Note: Implementations are automatically loaded from equally named .js files placed next to a service definition’s .cds file.

After the server restarted, try the following requests:

Learn more about adding custom handlers to cds services Learn more about reading and writing data using cds.ql Learn more about reflection APIs provided by cds services

Testing with Postman

Use the free Postman app, to try out more requests, including write operations. Having installed it, import this Postman Collection and test the contained requests.

You can execute the requests in the given order. Pay attention to the write requests in the CRUD section such as “POST Orders without UUID” and see how the generic handlers automatically fill-in new primary key values. If you execute “POST Orders without UUID” multiple times, you will run in the out-of-stock error situation as coded in the “before CREATE Orders” handler.

Deploy to SAP Cloud Platform

CAP applications can be deployed into the Cloud Foundry Environment of SAP Cloud Platform. At the end, it’s about deploying regular Node.js and/or Java applications, creating and binding appropriate service instances (see the CF Developer Guide).


Enhance project configuration for SAP HANA

It’s now time to switch to SAP HANA as a database.

Add the following configuration to package.json (overwrite any existing cds configuration):

"cds": {
  "requires": {
      "db": {
        "kind": "hana",
        "model": ["db","srv"]

Add the SAP HANA driver as a dependency to your project:

  npm add hdb

If your .cdsrc.json file contains a "target" entry, remove it or set it to: "target": "gen". This will cause deployment files to be written to this folder. Otherwise the deployment files would be written to the source folders.

Deploy using cf push

Cloud Foundry Environment of SAP Cloud Platform has a built-in cf push command to deploy applications. It needs the application files plus an optional manifest.yml file to push the application code and to bind the relevant services to the application.

As cf push can only bind but not create services, you need to create the SAP HANA service manually (along with an HDI container and a database schema):

cf create-service hana hdi-shared bookshop-db-hdi-container

On trial landscapes, replace the service type hana by hanatrial. If service creation fails, see the troubleshooting guide.

Now, build and deploy both the database part and the actual application:

cds build/all
cf push -f gen/db
cf push -f gen/srv --random-route

The first part of the command creates the SAP HANA table and view definitions along with manifest.yaml files in both in gen/db and gen/srv folders. Take a look at gen/db/manifest.yaml and see that it binds to the bookshop-db-hdi-container service that you created above.

In the deploy log, find the application URL in the routes line at the very end:

name:              bookshop-srv
requested state:   started

Open this URL in the browser and try out the provided links, for example, .../browse/Books. Application data is fetched from SAP HANA.

Deploy using MTA

SAP provides a more convenient, declarative way of defining and configuring deployments through Multitarget Applications (MTA).

CAP provides a tool to generate an MTA out of your CAP project. You can extend generated descriptors with your own MTA extension descriptors if required.