Create a CAP Business Service with Node.js Using Visual Studio Code
Develop a sample business service using Core Data & Services (CDS), Node.js, and SQLite, by using the SAP Cloud Application Programming Model (CAP) and developing on your local environment.
Overview
You will learn
- How to develop a sample business service using CAP and
Node.js - How to define a simple data model and a service that exposes the entities you created in your data model
- How to run your service locally
- How to deploy the data model to an
SQLitedatabase - How to add custom handlers to serve requests that aren’t handled automatically
Prerequisites
Prerequisites
- You’ve installed Node.js. Make sure you run the latest long-term support (LTS) version of Node.js with an even number like 20. Refrain from using odd versions, for which some modules with native parts will have no support and thus might even fail to install. In case of problems, see the Troubleshooting guide for CAP.
- You’ve installed the latest version of Visual Studio Code (VS Code).
- (Windows only) You’ve installed the SQLite tools for Windows. Find the steps how to install it in the How Do I Install SQLite section of the CAP documentation.
- You’ve installed an HTTP client, for example, REST client.
- If you don’t have a Cloud Foundry Trial subaccount and dev space on SAP BTP yet, create your Cloud Foundry Trial Account with US East (VA) as region and, if necessary Manage Entitlements. You need this to continue after this tutorial.
Steps
Intro
Before you start, make sure that you’ve completed the prerequisites.
Open a command line window and install the
cdsdevelopment kit globally by executing the following command:Shell/Bashnpm i -g @sap/cds-dkThis process takes some minutes installing the
cdscommand, which you will use in the next steps.On MacOS/Linux, you need to follow the steps as described here.
If there’s an older
@sap/cdspackage already installed on your machine, you have to remove it first; if so, you’ll be instructed to do so.In case of problems, see the Troubleshooting guide in the CAP documentation for more details.
To verify that the installation was successful, run
cdswithout arguments:Shell/Bashcds
cds commands This lists the available
cdscommands. For example, usecds --versionto check the version that you’ve installed. To know what is the latest version, see the Release Notes for CAP.
Go to Visual Studio Marketplace.
Choose Install.

extension_marketplace VS Code opens the extensions details page.
In VS Code choose Install to enable the extension for SAP CDS Language Support.

extension_VSCode If the extension is already installed and enabled in VS Code, it will be updated automatically.
Learn more about the features in this short demo and see the features and commands in the CAP documentation.
After initializing the project, you should see the following empty folders:
app: for UI artifactsdb: for the database level schema modelsrv: for the service definition layer
Folder structure
Let’s feed it by adding a simple domain model. In the
srvfolder choose the New File icon in VS Code and create a new file calledcat-service.cds.Add the following code to the file
cat-service.cds:CDSusing { Country, managed } from '@sap/cds/common'; service CatalogService { entity Books { key ID : Integer; title : localized String; author : Association to Authors; stock : Integer; } entity Authors { key ID : Integer; name : String; books : Association to many Books on books.author = $self; } entity Orders : managed { key ID : UUID; book : Association to Books; country : Country; amount : Integer; } }
Remember to save your files choosing Ctrl + S.
As soon as you’ve saved your file, the still running cds watch reacts immediately with some new output as shown below:
[cds] - using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > sqlite { database: ':memory:' }
/> successfully deployed to in-memory database.
[cds] - using auth strategy { kind: 'mocked' }
[cds] - serving CatalogService {
at: [ '/odata/v4/catalog' ],
decl: 'srv/cat-service.cds:3'
}
[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - server v9.7.0 launched in 668 ms
[cds] - [ terminate with ^C ]This means,
cds watchdetected the changes insrv/cat-service.cdsand automatically bootstrapped an in-memory SQLite database when restarting the server process.
To test your service, go to: http://localhost:4004

You won’t see data, because you haven’t added a data model yet. Click on the available links to see the service is running.
Add service provider logic to return mock data.
In the
srvfolder, create a new file calledcat-service.js.Add the following code to the file
cat-service.js:JavaScriptmodule.exports = (srv) => { // Reply mock data for Books... srv.on ('READ', 'Books', ()=>[ { ID:201, title:'Wuthering Heights', author_ID:101, stock:12 }, { ID:251, title:'The Raven', author_ID:150, stock:333 }, { ID:252, title:'Eleonora', author_ID:150, stock:555 }, { ID:271, title:'Catweazle', author_ID:170, stock:222 }, ]) // Reply mock data for Authors... srv.on ('READ', 'Authors', ()=>[ { ID:101, name:'Emily Brontë' }, { ID:150, name:'Edgar Allen Poe' }, { ID:170, name:'Richard Carpenter' }, ]) }Remember to save your files choosing Ctrl + S.
To test your service, click on these links:
You should see the mock data that you’ve added for the
BooksandAuthorsentities.
To get started quickly, you’ve already added a simplistic all-in-one service definition. However, you would usually put normalized entity definitions into a separate data model and have your services expose potentially de-normalized views on those entities.
In the
dbfolder choose the New File icon in VS Code and create a new file calledschema.cds.Add the following code to the file
schema.cds:CDSnamespace my.bookshop; using { Country, managed } from '@sap/cds/common'; entity Books { key ID : Integer; title : localized String; author : Association to Authors; stock : Integer; } entity Authors { key ID : Integer; name : String; books : Association to many Books on books.author = $self; } entity Orders : managed { key ID : UUID; book : Association to Books; country : Country; amount : Integer; }
Open the file cat-service.cds and replace the existing code with:
using my.bookshop as my from '../db/schema';
service CatalogService {
entity Books @readonly as projection on my.Books;
entity Authors @readonly as projection on my.Authors;
entity Orders @insertonly as projection on my.Orders;
}Remember to save your files choosing Ctrl + S.
You will add plain CSV files in folder db/data to fill your database tables with initial data. In the command line window execute the following:
cds add dataThis adds csv files with a single header line for all entities to the db/data/ folder. The name of the files matches the entities’ namespace and name, separated by -.
Open the file
db/data/my.bookshop-Authors.csvand add the following sample data to the file:CSV101,Emily Brontë 107,Charlote Brontë 150,Edgar Allen Poe 170,Richard CarpenterOpen the file
db/data/my.bookshop-Books.csvand add the following sample data to the file:CSV201,Wuthering Heights,101,12 207,Jane Eyre,107,11 251,The Raven,150,333 252,Eleonora,150,555 271,Catweazle,170,22Remember to save your files choosing Ctrl + S.
For now, you can ignore the additional files that
cds add datagenerated for you.After you added these files,
cds watchrestarts the server with an output, telling that the files have been detected and their content been loaded into the database automatically:Shell/Bash[cds] - connect using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { database: ':memory:' } > init from ./db/csv/my.bookshop-Books.csv /> successfully deployed to sqlite in-memory db [cds] - using auth strategy { kind: 'mocked' } [cds] - serving CatalogService { at: [ '/odata/v4/catalog' ], decl: 'srv/cat-service.cds:3', impl: 'srv/cat-service.js' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - server v9.7.1 launched in 419 ms [cds] - [ terminate with ^C ]Remove the code with mock data in
cat-service.js, because you want to see the data loaded from thecsvfiles.To test your service, open a web browser and go to:
http://localhost:4004/odata/v4/catalog/Books
http://localhost:4004/odata/v4/catalog/Authors
As you now have a fully capable SQL database with some initial data, you can send complex OData queries, served by the built-in generic providers.
http://localhost:4004/odata/v4/catalog/Authors?$expand=books($select=ID,title)
You should see a book titled Jane Eyre. If not, make sure you’ve removed the mock data from
cat-service.js.
Before you continue, make sure that you’ve completed the prerequisites and installed SQLite (for Windows users only).
Instead of using in-memory, you can also use persistent databases.
If
cds watchis running, choose Ctrl + C in the command line to stop the service.Configure the database in your
package.jsonin thecds.requires.dbsection.JSON{ "name": "my-bookshop", "version": "1.0.0", "description": "A simple CAP project.", "repository": "<Add your repository here>", "license": "UNLICENSED", "private": true, "dependencies": { "@sap/cds": "^7", "express": "^4" }, "devDependencies": { "@cap-js/sqlite": "^1" }, "scripts": { "start": "cds-serve" }, "cds": { "requires": { "db": { "kind": "sqlite", "credentials": { "url": "db/my-bookshop.sqlite" } } }} }Deploy the data model to an
SQLitedatabase:Shell/Bashcds deployYou’ve now created an
SQLitedatabase file underdb/my-bookshop.sqlite.The difference to the automatically provided in-memory database is that you now get a persistent database stored in the local file.
Open
SQLiteand view the newly created database:Shell/Bashsqlite3 db/my-bookshop.sqlite -cmd .dumpIf this doesn’t work, check if you have SQLite installed. On Windows, you might need to enter the full path to SQLite, for example:
C:\sqlite\sqlite3 db/my-bookshop.db -cmd .dump. Find the steps how to install it in the Troubleshooting guide in section How Do I Install SQLite in the CAP documentation for more details.To stop
SQLiteand go back to your project directory, choose Ctrl + C.Run your service.
Shell/Bashcds watchShell/Bash[cds] - using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { url: 'db/my-bookshop.sqlite' } [cds] - using auth strategy { kind: 'mocked' } [cds] - serving CatalogService { at: [ '/odata/v4/catalog' ], decl: 'srv/cat-service.cds:3', impl: 'srv/cat-service.js' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - server v9.7.1 launched in 296 ms [cds] - [ terminate with ^C ]
You can now see the generic handlers shipped with CAP in action.
In the root of your project, create a file called test.http and copy the following requests in it. This file can be used with the REST client to make requests against your service. The generic handlers CAP provides sent the responses to your requests.
###
#
# Browse Books
#
GET http://localhost:4004/odata/v4/catalog/Books?
# &$select=title,stock
# &$expand=currency
# &sap-language=de
###
#
# Get Author wit ID 101
#
GET http://localhost:4004/odata/v4/catalog/Authors(101)
###
#
# Update Author with ID 101
#
POST http://localhost:4004/odata/v4/catalog/Authors
Content-Type: application/json
{"ID": 101, "name": "Some Author"}
###
#
# Order a Book
#
POST http://localhost:4004/odata/v4/catalog/Orders
Content-Type: application/json;IEEE754Compatible=true
{"book_ID": 201, "amount": 5}Click on Send Request inside the test.http file, to execute requests against your service.

This
Send Requestbutton is provided by the REST client. It appears for every single request. This is important for the following step, when you execute, for example, theOrder a Bookrequest.
The REST client gives you the response of your service and you see immediately if the request was successful.
Use the following request to answer the next question:
POST http://localhost:4004/odata/v4/catalog/Books
Content-Type: application/json
{"ID": 201, "title": "Some Title"}In VS Code open the file
cat-service.jsand replace the existing code with:JavaScriptmodule.exports = (srv) => { const {Books} = cds.entities ('my.bookshop') // Reduce stock of ordered books srv.before ('CREATE', 'Orders', async (req) => { const order = req.data if (!order.amount || order.amount <= 0) return req.error (400, 'Order at least 1 book') const tx = cds.transaction(req) const affectedRows = await tx.run ( UPDATE (Books) .set ({ stock: {'-=': order.amount}}) .where ({ stock: {'>=': order.amount},/*and*/ ID: order.book_ID}) ) if (affectedRows === 0) req.error (409, "Sold out, sorry") }) // Add some discount for overstocked books srv.after ('READ', 'Books', each => { if (each.stock > 111) each.title += ' -- 11% discount!' }) }Remember to save your files choosing Ctrl + S.
Whenever orders are created, this code is triggered. It updates the book stock by the given amount, unless there aren’t enough books left.
In the
test.httpfile, execute theBrowse Booksrequest.Look at the stock of book
201.
Test the request Execute the
Order a Bookrequest.This triggers the logic above and reduces the stock.
Execute the
Browse Booksrequest again.The stock of book
201is lower than before. In the response you also see, that overstocked books get a discount now.
Resources
Discussion
Share feedback on this tutorial or join the conversation in SAP Community.