Bootstrapping Servers
CAP Node.js servers a bootstrapped through 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.js
in your project.
CLI Command cds serve
A Node.js CAP server process is usually started with the cds serve
CLI command, with cds run
and cds watch
as convenience variants.
For deployment, when the @sap/cds-dk
package providing the cds
CLI executable is not available, use the cds-serve
binary provided by the @sap/cds
package:
{
"scripts": {
"start": "cds-serve"
}
}
{
"scripts": {
"start": "cds-serve"
}
}
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')
cds.server = module.exports = async function (options) {
const app = cds.app = o.app || require('express')()
cds.emit ('bootstrap', app)
// load model from all sources
const csn = await cds.load('*')
cds.model = cds.compile.for.nodejs(csn)
cds.emit ('loaded', cds.model)
// connect to prominent required services
if (cds.requires.db) cds.db = await cds.connect.to ('db')
if (cds.requires.messaging) await cds.connect.to ('messaging')
// serve own services as declared in model
await cds.serve ('all') .from(csn) .in (app)
await cds.emit ('served', cds.services)
// launch HTTP server
cds .emit ('launching', app)
const port = o.port ?? process.env. PORT || 4004
const server = app.server = app.listen(port) .once ('listening', ()=>
cds.emit('listening', { server, url: `http://localhost:${port}` })
)
}
const cds = require('@sap/cds')
cds.server = module.exports = async function (options) {
const app = cds.app = o.app || require('express')()
cds.emit ('bootstrap', app)
// load model from all sources
const csn = await cds.load('*')
cds.model = cds.compile.for.nodejs(csn)
cds.emit ('loaded', cds.model)
// connect to prominent required services
if (cds.requires.db) cds.db = await cds.connect.to ('db')
if (cds.requires.messaging) await cds.connect.to ('messaging')
// serve own services as declared in model
await cds.serve ('all') .from(csn) .in (app)
await cds.emit ('served', cds.services)
// launch HTTP server
cds .emit ('launching', app)
const port = o.port ?? process.env. PORT || 4004
const server = app.server = app.listen(port) .once ('listening', ()=>
cds.emit('listening', { server, url: `http://localhost:${port}` })
)
}
cds. server()
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, as shown below.
cds. app
The express.js app
constructed by the server implementation.
Custom server.js
The CLI command cds serve
optionally bootstraps from project-local ./server.js
or ./srv/server.js
.
Plug-in to Lifecycle Events
In custom server.js
, you can plugin to all parts of @sap/cds
. Most commonly you'd register own handlers to lifecycle events emitted to the cds
facade object as below:
const cds = require('@sap/cds')
// react on bootstrapping events...
cds.on('bootstrap', ...)
cds.on('served', ...)
const cds = require('@sap/cds')
// react on bootstrapping events...
cds.on('bootstrap', ...)
cds.on('served', ...)
Override cds.server()
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
}
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
}
TIP
The req
object in your express middleware is not the same as req
in your CDS event handlers.
Lifecycle Events
bootstrap
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')
const express = require('express')
cds.on('bootstrap', (app)=>{
// add your own middleware before any by cds are added
// for example, serve static resources incl. index.html
app.use(express.static(__dirname+'/srv/public'))
})
const cds = require('@sap/cds')
const express = require('express')
cds.on('bootstrap', (app)=>{
// add your own middleware before any by cds are added
// for example, serve static resources incl. index.html
app.use(express.static(__dirname+'/srv/public'))
})
loaded
Emitted whenever a CDS model got loaded using cds.load()
connect
Emitted for each service constructed through cds.connect
.
serving
Emitted for each service constructed by cds.serve
.
served
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', (services)=>{
// We can savely access service instances through the provided argument:
const { CatalogService, db } = services
// ...
})
const cds = require('@sap/cds')
cds.on('served', (services)=>{
// We can savely access service instances through the provided argument:
const { CatalogService, db } = services
// ...
})
This event supports asynchronous event handlers.
listening
A one-time event, emitted when the server has been started and is listening to incoming requests.
shutdown
A one-time event, emitted when the server is closed and/or the process finishes. Listeners can execute cleanup tasks.
This event supports asynchronous event handlers.
Event Handlers
Synchronous vs. asynchronous
Unless otherwise noted, event handlers execute synchronously in the order they are registered. This is due to cds.on()
and cds.emit()
using Node's EventEmitter contract.
In other words this asynchronous handler code does not work as expected:
cds.on ('bootstrap', async ()=> {
await asyncCode() // will NOT be awaited
}
cds.on ('bootstrap', async ()=> {
await asyncCode() // will NOT be awaited
}
You can use the served event's asynchronous nature though to wait for such bootstrap code:
let done
cds.on('bootstrap', ()=> {
done = asyncCode()
}
cds.on('served', async ()=> {
await moreCode()
await done
})
let done
cds.on('bootstrap', ()=> {
done = asyncCode()
}
cds.on('served', async ()=> {
await moreCode()
await done
})
See Also...
The cds-plugin
package technique provides more options to customize server startup.