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 immediately 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 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 through 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 difference to the automatically provided in-memory database 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’ll run in the out-of-stock error situation as coded in the “before CREATE Orders” handler.

How to Continue

With this basic Node.js application, you might now: