Most of the express error handling documentation and tutorials I've read suggest using Express middleware to throw errors up to, and to handle the errors there.
eg. From the Express documentation:
app.get('/', function (req, res, next) {
fs.readFile('/file-does-not-exist', function (err, data) {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
function errorHandler (err, req, res, next) {
if (res.headersSent) {
return next(err)
}
res.status(500)
res.render('error', { error: err })
}
The question I have, is around the use of this when either using external SDKs (like Mongo or GCP) or Axios/Fetch calls to other microservices.
eg.
//GCP example
app.post('/items/:id', async function (req, res, next) {
try {
const id = req.params.id;
const data = req.body;
const snapshot = await db.doc(`items/${id}`).create(data); //This will throw an error if that document already exists
res.send("Success");
} catch(err) {
next(err);
}
});
//Microservice example
app.get('/foo/:id', async function (req, res, next) {
try {
const id = req.params.id;
const response = fetch(`/microservice/${id}`); //Say errors with a 404 status or a 500 status
res.send(response.body);
} catch(err) {
next(err);
}
});
The question is - if we want to now return useful error responses, what's the right way to do this?
For example - the errors throw from the fetch
function are going to be a fetch Response object, while the GCP function is going to throw an error that looks like this (I couldn't find documentation for the error object):
{ code: 6,
metadata: Metadata { _internal_repr: {} },
details: 'Document already exists: projects/project-id/...'
}
So I could implement my error handling function to cater towards different types of error objects:
function errorHandler (err, req, res, next) {
if (res.headersSent) {
return next(err)
}
//Handle Response Objects
if (err.status) {
switch(err.status) {
case 404: return res.status(404).send("Item not found");
// etc
}
}
//Handle GCP errors
if (err.code && err.details) {
switch(err.code) {
return res.status(409).send("Item already exists");
}
}
}
But this seems like a brittle solution - for example what if another library I was using included 'status' on their error object.
So then, the alternative approach would be to standardise the errors myself at the of the library.
I can throw objects as errors like
throw {
status: 404,
message: "Item not found'
};
And implement my code like:
formatGcpError(err) {
switch(err.code) {
case 6: return {
status: 409,
message: "Item already exists",
}
}
}
app.post('/items/:id', async function (req, res, next) {
try {
const id = req.params.id;
const data = req.body;
const snapshot = await db.doc(`items/${id}`).create(data); //This will throw an error if that document already exists
res.send("Success");
} catch(err) {
next(formatGcpError(err));
}
});
Am wondering - what's the standard approach here?