Skip to Content

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.
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 SQLite database
  • How to add custom handlers to serve requests that aren’t handled automatically

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 16. 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.

Before you start, make sure that you’ve completed the prerequisites.

  • Step 1
    1. Open a command line window and install the cds development kit globally by executing the following command:

      Shell/Bash
      Copy
      npm i -g @sap/cds-dk
      

      This process takes some minutes installing the cds command, 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/cds package 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.

    2. To verify that the installation was successful, run cds without arguments:

      Shell/Bash
      Copy
      cds
      
      cds commands

      This lists the available cds commands. For example, use cds --version to check the version that you’ve installed. To know what is the latest version, see the Release Notes for CAP.

  • Step 2
    1. Go to Visual Studio Marketplace.

    2. Choose Install.

      extension_marketplace

      VS Code opens the extensions details page.

    3. 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.

  • Step 3
  • Step 4

    After initializing the project, you should see the following empty folders:

    • app: for UI artifacts
    • db: for the database level schema model
    • srv: for the service definition layer
    Folder structure
    1. Let’s feed it by adding a simple domain model. In the srv folder choose the New File icon in VS Code and create a new file called cat-service.cds.

    2. Add the following code to the file cat-service.cds:

      CDS
      Copy
      using { 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.

    3. As soon as you’ve saved your file, the still running cds watch reacts immediately with some new output as shown below:

      Shell/Bash
      Copy
      [cds] - connect using bindings from: { registry: '~/.cds-services.json' }
      [cds] - connect to db > sqlite { database: ':memory:' }
       > init from ./db/csv/my.bookshop-Authors.csv
      /> successfully deployed to sqlite in-memory db
      
      [cds] - serving CatalogService { at: '/catalog', impl: './srv/cat-service.js' }
      
      [cds] - server listening on { url: 'http://localhost:4004' }
      [cds] - launched at 18/05/2022, 19:49:32, in: 874.456ms
      [cds] - [ terminate with ^C ]
      

      This means, cds watch detected the changes in srv/cat-service.cds and automatically bootstrapped an in-memory SQLite database when restarting the server process.

    4. To test your service, go to: http://localhost:4004

      application

      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.

  • Step 5

    Add service provider logic to return mock data.

    1. In the srv folder, create a new file called cat-service.js.

    2. Add the following code to the file cat-service.js:

      JavaScript
      Copy
      module.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.

    3. To test your service, click on these links:

      You should see the mock data that you’ve added for the Books and Authors entities.

  • Step 6

    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.

    1. In the db folder choose the New File icon in VS Code and create a new file called data-model.cds.

    2. Add the following code to the file data-model.cds:

      CDS
      Copy
      namespace 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;
      }
      
    3. Open the file cat-service.cds and replace the existing code with:

      CDS
      Copy
      using my.bookshop as my from '../db/data-model';
      
      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.

  • Step 7

    In VS Code you will add plain CSV files in folder db/csv to fill your database tables with initial data.

    1. In the db folder, choose New File and enter csv/my.bookshop-Authors.csv to create a new folder csv with the file named my.bookshop-Authors.csv. Add the following to the file:

      CSV
      Copy
      ID;name
      101;Emily Brontë
      107;Charlote Brontë
      150;Edgar Allen Poe
      170;Richard Carpenter
      
    2. In the newly created csv folder, choose New File and create a file called my.bookshop-Books.csv. Add the following to the file:

      CSV
      Copy
      ID;title;author_ID;stock
      201;Wuthering Heights;101;12
      207;Jane Eyre;107;11
      251;The Raven;150;333
      252;Eleonora;150;555
      271;Catweazle;170;22
      

      Remember to save your files choosing Ctrl + S.

      Make sure that you now have a folder hierarchy db/csv/.... Remember that the csv files must be named like the entities in your data model and must be located inside the db/csv folder.

      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
      Copy
      [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] - serving CatalogService { at: '/catalog', impl: './srv/cat-service.js' }
      
      [cds] - server listening on { url: 'http://localhost:4004' }
      [cds] - launched at 18/05/2022, 19:49:47, in: 861.036ms
      [cds] - [ terminate with ^C ]
      
    3. Remove the code with mock data in cat-service.js, because you want to see the data loaded from the csv files.

    4. To test your service, open a web browser and go to:

      http://localhost:4004/catalog/Books

      http://localhost:4004/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/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.

  • Step 8

    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.

    1. If cds watch is running, choose Ctrl + C in the command line to stop the service.

    2. Install SQLite3 packages.

      Shell/Bash
      Copy
      npm i sqlite3 -D
      
    3. Deploy the data model to an SQLite database:

      Shell/Bash
      Copy
      cds deploy --to sqlite:db/my-bookshop.db
      

      You’ve now created an SQLite database file under db/my-bookshop.db.

      This configuration is saved in your package.json as your default data source. For subsequent deployments using the default configuration, you just need to run cds deploy.

      The difference to the automatically provided in-memory database is that you now get a persistent database stored in the local file.

    4. Open SQLite and view the newly created database:

      Shell/Bash
      Copy
      sqlite3 db/my-bookshop.db -cmd .dump
      

      If 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.

    5. To stop SQLite and go back to your project directory, choose Ctrl + C.

    6. Run your service.

      Shell/Bash
      Copy
      cds watch
      
      Shell/Bash
      Copy
      [cds] - connect using bindings from: { registry: '~/.cds-services.json' }
      [cds] - connect to db > sqlite { url: 'sqlite.db', database: 'db/my-bookshop.db' }
      [cds] - serving CatalogService { at: '/catalog', impl: './srv/cat-service.js' }
      
      [cds] - server listening on { url: 'http://localhost:4004' }
      [cds] - launched at 18/05/2022, 19:54:53, in: 830.398ms
      [cds] - [ terminate with ^C ]
      
  • Step 9

    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.

    HTTP
    Copy
    ###
    #
    # Browse Books
    #
    GET http://localhost:4004/catalog/Books?
      # &$select=title,stock
      # &$expand=currency
      # &sap-language=de
    
    ###
    #
    # Get Author wit ID 101
    #
    GET http://localhost:4004/catalog/Authors(101)
    
    ###
    #
    # Update Author with ID 101
    #
    POST http://localhost:4004/catalog/Authors
    Content-Type: application/json
    
    {"ID": 101, "name": "Some Author"}
    
    
    ###
    #
    # Order a Book
    #
    POST http://localhost:4004/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.

    Send a request

    This Send Request button is provided by the REST client. It appears for every single request. This is important for the following step, when you execute, for example, the Order a Book request.

    The REST client gives you the response of your service and you see immediately if the request was successful.

    You can only GET books and authors, but modifications won't work. Why is that? The response to your request helps you answer this question.

  • Step 10
    1. In VS Code open the file cat-service.js and replace the existing code with:

      JavaScript
      Copy
        module.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.

    2. In the test.http file, execute the Browse Books request.

      Look at the stock of book 201.

      Test the request
    3. Execute the Order a Book request.

      This triggers the logic above and reduces the stock.

    4. Execute the Browse Books request again.

      The stock of book 201 is lower than before. In the response you also see, that overstocked books get a discount now.

Back to top