Search

    Bootstrapping Servers and Service Providers

    A Node.js CAP server process is started with the cds serve CLI command, with cds run and cds watch as convenience variants.

    Besides, handling command line arguments and adding log output, cds serve essentially loads a built-in server.js module, which can be accessed through cds.server.

    You can plug-in custom logic to the default bootstrapping choreography using a custom server.jsin your project.

    cds serve command line

    By default, bootstrapping of servers is handled by the framework automatically. So, you only need cds.serve if you want to control the bootstrapping yourself, for example, in a custom express server.js.

    cds.server (options) => {…}

    This is essentially a shortcut getter to require('@sap/cds/server'), that is, it loads and returns the built-in server.js implementation. You’d mainly use this in custom server.js to delegate to the default implementation, for example:.

    const cds = require('@sap/cds')
    // ... some custom bootstrapping ...
    module.exports = cds.server //> delegate to default server.js
    

    Built-in server.js

    The built-in server.js constructs an express.js app, and bootstraps all CAP services using cds.connect and cds.serve. Its implementation essentially is as follows:

    const cds = require('@sap/cds')
    const express = require('express')
    module.exports = async function cds_server (o) {    //> o = options from CLI
    
        const app = cds.app = o.app || express()
        cds.emit ('bootstrap',app)                      //> hook for project-local server.js
    
        // mount static resources and logger middleware
        if (o.static)    app.use (express.static (o.static))  //> defaults to ./app
        if (o.favicon)   app.use ('/favicon.ico', o.favicon)  //> if none in ./app
        if (o.index)     app.get ('/',o.index)                //> if none in ./app
        if (o.correlate) app.use (o.correlate)                //> request correlation
        if (o.logger)    app.use (o.logger)                   //> basic request logging
    
        // load specified models or all in project
        const csn = await cds.load (o.from||'*')
        cds.model = o.from = cds.linked (cds.compile.for.odata(csn))
    
        // connect to essential framework services if required
        // note: cds.deploy() is not a public API
        const _init = o.in_memory && (db => cds.deploy(csn).to(db,o))
        if (cds.requires.db) cds.db = await cds.connect.to ('db') .then (_init)
        if (cds.requires.messaging) await cds.connect.to ('messaging')
        if (cds.requires.multitenancy) await cds.mtx.in (app)
    
        // serve all services declared in models
        await cds.serve (o.service,o).in (app)
        cds.emit ('served', cds.services)               //> hook for listeners
    
        // start http server
        const port = (o.port !== undefined) ? o.port : (process.env.PORT || 4004)
        return app.listen (port)
    

    Custom server.js

    The CLI command cds serve optionally bootstraps from project-local ./server.js or ./srv/server.js. In there, register own handlers to bootstrap events emitted to the cds facade object as below:

    const cds = require('@sap/cds')
    // react on bootstrapping events...
    cds.on('bootstrap', ...)
    cds.on('served', ...)
    module.exports = cds.server //> delegate to default server.js
    

    Provide an own bootstrapping function if you want to access and process the command line options. This also allows you to override certain options before delegating to the built-in server.js. In the example below, we construct the express.js app ourselves and fix the models to be loaded.

    const cds = require('@sap/cds')
    // react on bootstrapping events...
    cds.on('bootstrap', ...)
    cds.on('served', ...)
    // handle and override options
    module.exports = (o)=>{
      o.from = 'srv/precompiled-csn.json'
      o.app = require('express')()
      return cds.server(o) //> delegate to default server.js
    }
    

    The req object in your express middleware is not the same as req in your CDS handlers.

    cds.once (‘bootstrap’, (express.js app)=>{})

    A one-time event, emitted immediately after the express.js app has been created and before any middleware or CDS services are added to it.

    const cds = require('@sap/cds')
    cds.on('bootstrap', (app)=>{
      // add your own middleware before any by cds are added
    })
    

    cds.on (‘loaded’, (csn)=>{})

    Emitted whenever a CDS model got loaded using cds.load()

    cds.on (‘serving’, (service)=>{})

    Emitted for each service constructed by cds.serve.

    cds.on (‘connect’, (service)=>{})

    Emitted for each service constructed through cds.connect.

    cds.on (‘subscribe’, (service,event)=>{})

    Emitted whenever a handler is registered for a declared event with [srv.on].

    cds.once (‘served’, (services)=>{})

    A one-time event, emitted when all services have been bootstrapped and added to the express.js app.

    const cds = require('@sap/cds')
    cds.on('served', ()=>{
      // add more middleware after all CDS services
    })
    

    cds.once (‘listening’, ({server,url})=>{})

    A one-time event, emitted when the server has been started and is listening to incoming requests.

    cds.serve… service(s)

    Use cds.serve() to construct service providers from the service definitions in corresponding CDS models. As stated above, this is usually done automatically by the built-in cds.server.

    cds.serve (service, options) fluent api… 

    Initiates a fluent API chain to construct service providers; use the methods documented below to add more options.

    Common Usages:
    const { CatalogService } = await cds.serve ('my-services')
    
    const app = require('express')()
    cds.serve('all') .in (app)
    
    Arguments:
    • name specifies which service to construct a provider for; use all to construct providers for all definitions found in the models.
    cds.serve('CatalogService')  //> serve a single service
    cds.serve('all')             //> serve all services found
    

    You may alternatively specify a string starting with './' or refer to a file name with a non-identifier character in it, like '-' below, as a convenient shortcut to serve all services from that model:

    cds.serve('./reviews-service')  //> is not an identifier through './'
    cds.serve('reviews-service')    //> same as '-', hence both act as:
    cds.serve('all').from('./reviews-service')
    

    The method returns a fluent API object, which is also a Promise resolving to either an object with 'all' constructed service providers, or to the single one created in case you specified a single service:

    const { CatalogService, AdminService } = await cds.serve('all')
    const ReviewsService = await cds.serve('ReviewsService')
    
    Caching:

    The constructed service providers are cached in cds.services, which (a) makes them accessible to cds.connect, as well as (b) allows us to extend already constructed services through subsequent invocation of cds.serve.

    Common Usages and Defaults

    Most commonly, you’d use cds.serve in a custom file to add all the services to your express.js app as follows:

    const app = require('express')()
    cds.serve('all').in(app)
    app.listen()
    

    This uses these defaults for all options:

    Option Description Default
    cds.serve … which services to construct 'all' services
     ↳  .from models to load definitions from './srv' folder
     ↳  .in express app to mount to — none —
     ↳  .to client protocol to serve to 'fiori'
     ↳  .at endpoint path to serve at @path or .name
     ↳  .with implementation function @impl or ._source.js

    Alternatively you can construct services individually, also from other models, and also mount them yourself, as document in the subsequent sections on individual fluent API options.

    If you just want to add some additional middleware, it’s recommended to bootstrap from a custom server.js.

     ↳  .from (model)

    Allows to determine the CDS models to fetch service definitions from, which can be specified as one of:

    • A filename of a single model, which gets loaded and parsed with [cds.load]
    • A name of a folder containing several models, also loaded with [cds.load]
    • The string 'all' as a shortcut for all models in the './srv' folder
    • An already parsed model in CSN format

    The latter allows you to [cds.load] or dynamically construct models yourself and pass in the CSN models, as in this example:

    const csn = await cds.load('my-services.cds')
    cds.serve('all').from(csn)...
    

    If omitted, './srv' is used as default.

     ↳  .to (protocol)

    Allows to specify the protocol through which to expose the service. Currently supported values are:

    • 'rest' plain http rest protocol without any OData-specific extensions
    • 'odata' standard OData rest protocol without any Fiori-specific extensions
    • 'fiori' OData protocol with all Fiori-specific extensions like Draft enabled

    If omitted, 'fiori' is used as default.

     ↳  .at (path)

    Allows to programmatically specify the mount point for the service.

    Note that this is only possible when constructing single services:

    cds.serve('CatalogService').at('/cat')
    cds.serve('all').at('/cat') //> error
    

    If omitted, the mount point is determined from annotation @path, if present, or from the service’s lowercase name, excluding trailing Service.

    service MyService @(path:'/cat'){...}  //> served at: /cat
    service CatalogService {...}           //> served at: /catalog
    

     ↳  .in (express app)

    Adds all service providers as routers to the given express app.

    const app = require('express')()
    cds.serve('all').in(app)
    app.listen()
    

     ↳  .with (impl function)

    Allows to specify a function that adds [event handlers] to the service provider, either as a function or as a string referring to a separate node module containing the function.

    cds.serve('./srv/cat-service.cds') .with ('./srv/cat-service.js')
    
    cds.serve('./srv/cat-service') .with (srv => {
      srv.on ('READ','Books', (req) => req.reply([...]))
    })
    

    Learn more about using impl annotations. Learn more about adding event handlers.

    Note that this is only possible when constructing single services:

    cds.serve('CatalogService') .with (srv=>{...})
    cds.serve('all') .with (srv=>{...})  //> error
    

    If omitted, an implementation is resolved from annotation @impl, if present, or from a .js file with the same basename than the CDS model, for example:

    service MyService @(impl:'cat-service.js'){...}
    
    srv/cat-service.cds  #> CDS model with service definition
    srv/cat-service.js   #> service implementation used by default
    
    Show/Hide Beta Features