Skip to Content
Event Information

Annotated links: Episode 15 of Hands-on SAP dev with qmacro

This is a searchable description of the content of a live stream recording, specifically “Episode 15 – Starting to look at a frontend for our Northbreeze app” in the “Hands-on SAP dev with qmacro” series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the “Catch the replays” section of the series blog post.

This episode, titled “Starting to look at a frontend for our Northbreeze app“, was streamed live on Fri 29 Mar 2019 and is approximately one hour in length. The stream recording is available on YouTube.

Below is a brief synopsis, and links to specific highlights – use these links to jump directly to particular places of interest in the recording, based on ‘hh:mm:ss’ style timestamps.

Brief synopsis

This session comes to you live from the location of the SAP CodeJam event in the AOK offices in Frankfurt. In this episode we investigate at a few more cool features of CAP and CDS and start to look at a user interface for our Northbreeze app, and even get to serve it from the CAP runtime itself.

00:01:00: Introducing Riley Rainey to this episode, who tells us a bit about himself and the work of the evangelists and developer relations as a whole, and mentions the CodeJams that are running that day. By the way, if you would like to know more about CodeJams or request one, visit the Community Events page.

00:05:00: Talking about the recent announcement relating to the SAP S/4HANA Cloud SDK – which is the availability of release 1.0.0 of the JavaScript version. This has been in beta for a while but has now gone Generally Available (GA). This is great news! See the post Announcing the SAP S/4HANA Cloud SDK by Henning Heitkoetter for more info on this.

00:09:00: Using npm info @sap/cds we look at the latest info on the @sap/cds module, we can see that it’s at version 3.7.1. I’ve deliberately not upgraded the version I have installed globally, which is still at 3.5.2. This resulted in some interesting effects, for new projects created (with cds init) which we explore at this point.

00:14:24: Looking at which actual cds executable is being used:

=> which cds

which is the globally installed one, and also looking what type of file that is:

=> file `which cds`
/Users/i347491/.nvm/versions/node/v8.12.0/bin/cds: a /usr/bin/env node script text executable, ASCII text

This is also possible with the following sequence of commands:

=> which cds
=> file $(!!)
/Users/i347491/.nvm/versions/node/v8.12.0/bin/cds: a /usr/bin/env node script text executable, ASCII text

00:17:45: Looking a bit more closely at the cds file, we see that it’s actually a symbolic link to another file elsewhere in the @sap/cds package:

=> ls -l ~/.nvm/versions/node/v8.12.0/bin/cds
lrwxr-xr-x  1 i347491  staff  39 12 Apr 07:14 /Users/i347491/.nvm/versions/node/v8.12.0/bin/cds@ -> ../lib/node_modules/@sap/cds/bin/cds.js

00:18:35: A cameo appearance from Max Streifeneder, who, along with Former Member (who also makes an appearance a few minutes later – bearing gifts in the form of SAP Cloud Application Programming Model sticksers!) is co-running the CAP CodeJam there at AOK Systems.

00:23:20: Having a look inside the cds entrypoint, in particular, the “bootstrap” section which has this comment: “try to find a locally installed cds, otherwise launch this one”.

This is something we’ll try to debug, to see it in action.

00:25:30: We’re going to use the Node Inspector to debug:

=> node --inspect-brk `which cds`
Debugger listening on ws://
For help see

We can now attach the Chrome Developer tools to this, to debug! Opening up the special chrome://inspect address we can see the Node process waiting for us to connect and start inspecting.

00:29:00: We stop on the use of require_local which is a module inside the global @sap/cds package, specifically in lib/utils/. This module attempts to load a project-local version of the module specified, and back in cds.js we see the result of this, if successful, is assigned to the _main constant, falling back to the main function that’s defined further down in this global cds.js script.

We can see how this works now, and understand why, when we invoke cds from within a CAP project directory,We can see how this works now, and understand why, when we invoke cds from within a CAP project directory, we get a different version of cds compared to when we invoke it from outside a CAP project directory. The power of Node, Chrome Developer Tools and debugging in general! (And of course this would be almost impossible if we didn’t have access to the source code).

00:33:00: Discussing the differences (and similarities) between cds run and cds serve all.

00:35:00: Getting back to our Northbreeze project, to remind ourselves where we ended up last time, and starting to talk a bit about a function import on our OData service, something we haven’t previously considered. This causes me to go on a bit about how function imports are quite orthogonal to the rest of the OData protocol, how they are more like remote procedure calls, in the context of OData’s otherwise nicely RESTful design.

00:37:50: So despite my (mostly philosophical) reservations about function imports, we plan to take a quick look at how we go about defining one in the service definition in this Northbreeze project.

00:39:00: Reminding ourselves that adding a .js file, with the same base name as a service definition file, allows us to provide custom implementation logic for the contents of that service. You can use this technique to enhance or override standard CAP service functionality as you see fit.

00:40:35: To warm up, we add a service.js file alongside the service.cds file, with the following contents:

module.exports = srv => {
  console.log("IN SERVICE IMPLEMENTATION", srv)

When we start the service, we see a whole lot of information produced by this console.log statement.

00:41:49: Changing the reference in the console.log statement from srv to, and we can more easily see that in fact the function we’ve defined gets called for each of the services defined in service.cds:


This is because we have both these services defined in service.cds, thus:

using northbreeze from '../db/model';

service Breezy {
        entity Products as projection on northbreeze.Products;
        entity Suppliers as projection on northbreeze.Suppliers;
        entity Categories as projection on northbreeze.Categories;
        function hello (to:String) returns String;

service Restricted {
        entity Orders as projection on northbreeze.Orders;

00:44:15: Next up, having replaced the body of the exported function in service.js with this:

if ( === 'Breezy') {
  srv.before('READ', 'Products', x => {

… we can get a glimpse of the sort of information that’s passed to such a custom hook function.

00:45:00: We define a simple function import definition in the service.cds file. Of course, a function import will belong to a specific OData service, so we write the definition inside one of the two services defined there, specifically the ‘Breezy’ service, and the definition looks like this:

function hello (to:String) returns String;

The corresponding implementation for this function definition can be written in the service.js implementation file, which we do, like this:

srv.on('hello', x => {

00:46:45: Calling the function import like this:


we see, via a breakpoint, that the data passed to the function import definition contains all sorts of information, including, in data, the value of the to parameter that we passed (“dj”), which we now use in what we return, modifying the function so it now looks like this:

srv.on('hello', x => `Hello there ${} !`)

And with that, I end the stream a little bit early, to allow the CAP CodeJam participants to enter the room and get set up for the day’s session! We’ll continue the move towards looking at a UI layer next time!

Be the first to leave a comment
You must be Logged on to comment or reply to a post.