Error Types
Proper error handling is crucial for today's business applications. Before going into more detail, it is necessary to distinguish between two types of errors:
Programmer errors.
These occur as a result of programming errors (for example,
'foo' cannot be read by undefined
). They must be corrected.Operational errors
These occur during runtime (for example, when sending a request to a faulty remote system). They must be corrected.
Guidelines
The key takeaways for programming errors are:
- Fail loudly: Do not hide errors and continue silently. Make sure to log unexpected errors correctly. Don't catch errors that you can't handle.
- Don't develop in a defensive fashion. Focus on your business logic and only handle errors when you know they will occur. Use
try/catch
blocks only when necessary.
Never try to catch and handle unexpected errors, rejections of promises, and so on. If it is unexpected, you cannot handle it correctly. If you could, it would be expected (and should already be handled). Even if your apps should be stateless, you can never be 100% sure that a shared resource was not affected by the unexpected error. Therefore, you should never allow an app to continue running after such an event, especially for multi-tenant apps where there is a risk of information disclosure.
Following this will make your code shorter, clearer and simpler.
Never hide the causes of errors.
When an error occurs, it should be possible to know the root cause. The SAP Cloud Application Programming Model SDK for Node.js also throws exceptions, for example when a CRUD operation violates the foreign key constraints. In this case, the framework throws the exception UNIQUE_CONSTRAINT_VIOLATION. The problem in this case is that the end user will only see a more or less cryptic error message:

It is therefore useful to provide a meaningful error message.
For this purpose, you can register an error handler in your service implementation:
Example:
// Imports
const cds = require("@sap/cds");
/**
* The service implementation with all service handlers
*/
module.exports = cds.service.impl(async function () {
/**
* Custom error handler
*
* throw a new error with: throw new Error('something bad happened');
*
**/
this.on("error", (err, req) => {
switch (err.message) {
case "UNIQUE_CONSTRAINT_VIOLATION":
err.message = "The entry already exists.";
break;
default:
err.message =
"An error occured. Please retry. Technical error message: " +
err.message;
break;
}
});
});
This handler now steps in whenever this exception gets triggered and overrides it with an alternative error message:

Raising and catching exceptions
You will certainly add your implementations to your services. It is very likely, that you want to interrupt some operations before something crashes. In this case, you can throw a Node.js exception. for error handling.
Request response
You can also use the req.error()
method to collect messages or errors and return them to the caller in the request-response.
this.on("submitOrder", async (req) => {
const { book, amount } = req.data;
let { stock } = await db.read(Books, book, (b) => b.stock);
if (stock >= amount) {
await db.update(Books, book).with({ stock: (stock -= amount) });
await this.emit("OrderedBook", { book, amount, buyer: req.user.id });
return req.reply({ stock }); // <-- Normal reply
} else {
// Reply with error code 409 and a custom error message
return req.error(409, `${amount} exceeds stock for book #${book}`);
}
});