I'm trying to improve my code below with error handling such that I can use the error handling code else where in my application where it's relevant.
I have the following sequence of events:
Route handler --> middleware --> service --> DAO --> MongoDB
I want to implement a robust error handling solution that I can use throughout the application.
I ran into an issues where when DB is down or CRUD operation fails, DAO does not handle the error and propagate it to relevant levels. I certainly need to be able handle HTTP 500 type errors so that my API is robust. Any suggestions highly appreciated.
As someone who ran into such "robust error-handling solutions" a few times (usually implemented as a middleware) I highly recommend against it:
Different routes will experience different edge cases which you'll want to handle separately. Trying to create a "silver bullet" that will handle everything will be inherently more complex and more difficult to maintain.
Further, due to the async nature of many routes, you may find yourself reading stacktraces of that error-handler without having the context of which route triggered it...
Yes, you can improve your code and use a centralised error handler in case something goes wrong and the error is not already handled in your controller.
Let us create a simple app to understand the code flow and centralised error handling. Please note that I am writing the below code in
StackOverflow editor hence running it directly might give syntax errors.
|- src
|-- app.js
|-- controller
|-- middleware
|-- routes
app.js
const express = require("express");
const routes = require("./routes");
const app = express();
// use /api endpoint for backend routes
app.use("/api", routes);
app.listen(8080);
middleware/error.js
function errorHandler(err, req, res, next) {
console.log("some error has occurred");
res.status(503).json({
msg: "something went wrong"
});
}
module.exports = errorHandler;
route/index.js
const express = require("express");
const router = express.Router();
const errorHandler = require("./middleware/error");
const HelloController = require("./controller/Hello.js");
router.route("/hello", HelloController);
router.use(errorHandler);
module.exports = router;
controller/Hello.js
function HelloController(req, res, next) {
try {
// Do some stuff, call dao
// Any Special Error Handling will go here
res.json({ msg: "success" });
} catch (e) {
// Any other error will call the errorHandler and send the 503 to the client thus preventing the
// server to crash
next(e); // this will call the errorHandler
}
}
module.exports = HelloController;
The request flow will be as follows
App.Js -> route/index.js -> controller/Hello.js -> IF error in controller a) middleware/error.js, else b) exit
You can add more categorisation in your code based on routes like /api/public, /api/private, /api/admin
Related
I have an Express.js app in which I'm trying to log the validation errors returned by Celebrate to the console so that I can analyze them with the logging service that I use (which is GCP's Cloud Logging).
I'm currently just using the error handling middleware provided by Celebrate as suggested in the documentation:
// app.js
const { errors } = require('celebrate');
...
app.use(errors());
...
How can I extend the middleware (without re-implementing it) so that it also logs the validation errors to the console?
The simplest way to achieve this seems to be by defining another error middleware before the Celebrate error middleware, that checks whether the error is a Celebrate error (using the isCelebrateError method) and if so it logs it to the console:
// app.js
const { errors, isCelebrateError } = require('celebrate');
...
// middleware to log Celebrate validation errors
app.use((err, req, res, next) => {
if (isCelebrateError(err)) {
console.error(err);
}
next(err);
});
// Celebrate middleware to return validation errors
app.use(errors());
...
It is important to include the logging middleware before Celebrate's errors() middleware since errors() returns a JSON response and no other middleware is run after it (you can check out the Celebrate source code for the implementation details of errors()).
I'm making a simple API in express js. I've an end point where I'll make a call to GitHub api int turn. My Front-end application will utilize it. Here is my code:
api.js
const express = require('express');
const router = express.Router();
...
var http = require('http');
var https = require("https");
router.get('/github', function (req, res) {
// APPROACH 1: Failed : fetch is not defined
// fetch('https://api.github.com/users/tmtanzeel')
// .then(response => response.json())
// .then(json => console.log(json))
// APPROACH 2: Failed : throw er; // Unhandled 'error' event
/*try {
https.get('https://api.github.com/users/tmtanzeel',function (res) {
console.log(res);
})
} catch (error) {
...
}*/
});
Both my approaches are failing. I've almost no experience with Express. Please picth in
The second method is almost corrent. Add the error handler and send to the caller the data you just received.
https.get('https://api.github.com/users/tmtanzeel',function (apiRes) {
apiRes.pipe(res);
}).on('error', (e) => {
console.error(e);
res.status(500).send('Something went wrong');
});
Handling the response (stream) received from the API call can be done in two ways.
Using pipes which is automatic
Handle read events and manage the data writing manually
I have used the first approach.
However, it is very well recommended that you get sound knowledge in handling Streams if you use Node JS. Streams are the basis of Node JS requests and responses.
https://nodejs.dev/learn/nodejs-streams
You should use Express to handle incoming requests.
(for example if your webapp fetches data from your (express) server).
Read the docs: http://expressjs.com
Your first attempt failed because fetch is an implementation of the web-browser, not one of nodejs.
If you want to use fetch try: https://www.npmjs.com/package/node-fetch
its a well documented, easy to use fetch function.
From your examples, all seems fine except I can't see you sending the returned data to the client.
You can try something similar like adding res.send(data) to send the data and make it available on the /github route
I have a nuxt single page application. The api that I work with has a list of codes for various errors. So, in onError interceptor, the error has to be checked in a dictionary or in a more desired way in a json file. For this, I added a error-handler.js plugin in my nuxt project. But, I don't know how to read from json file.
1) Loading of the json file would occur every time an error thrown?
2) What is the best practice to show the error message to the user? Is this plugin only responsible to create the error-message and in the page try-catch is needed to toast that message?
export default function ({ $axios, store, app, redirect }) {
$axios.onError(error => {
if (error.config.hasOwnProperty('errorHandle') && error.config.errorHandle === false) {
return Promise.reject(error);
}
if (error.message === 'Network Error') {
error.message = 'check the Internet connection';
return
}
const code = parseInt(error.response && error.response.status)
if (error.response)
console.log('error.response', error.response.status, error.response)
if (error.response.data.Errors) {
let errMessage = ''
error.response.data.Errors.forEach(item => {
switch (item.Message.MessageText) {
case 'OrganizationNotFound':
errMessage = 'the organization that you are looking for does not exists'
break
}
})
}
}
}
In Nuxt, plugin code is loaded once or twice per user visit, after that in universal mode it is not executed (of course your onError handler will be). It's once if you make it a client/server-side only plugin, or twice if you need it both on client and server. In your case client-side only plugin sounds like a good choice - just make sure that loading of JSON goes outside of the onError function.
As for how to show it, it depends on your design. We are using Vuetify and have v-snackbar in default layout so it's on every page. Snackbar is bound to VueX value. Then your error plugin can populate that value as appropriate as it will have access to store. This keeps the "raising the error" (dispatch to store) and "showing the error" reasonably decoupled, whilst very reusable (components can also dispatch to store if they face a problem).
I have an node / express js app that was generated using the yoman full stack generator. I have swapped out mongo / mongoose for cloudant db (which is just a paid for version of couchdb). I have a written a wrapper for the Cloudant node.js library which handles cookie auth with my instance via an init() method wrapped in a promise. I have refactored my application to not start the express server until the connection to the db has been established as per snippet below taken from my app.js
myDb.init(config).then(function (db) {
logger.write(1001, '','Connection to Cloudant Established');
// Start server
server.listen(config.port, config.ip, function () {
logger.write(1001, "",'Express server listening on '+config.port+', in '+app.get('env')+' mode');
});
});
On my express routes I have introduced a new middleware which attaches the db object to the request for use across the middleware chain as per below. This gets the db connection object before setting the two collections to use.
exports.beforeAll = function (req, res, next) {
req.my = {};
// Adding my-db
req.my.db = {};
req.my.db.connection = myDb.getDbConnection();
req.my.db.orders = req.my.db.connection.use(dbOrders);
req.my.db.dbRefData = req.my.db.connection.use(dbRefData);
next();
};
This mechanism works when i manually drive my apis through POSTman as the express server won't start until after the promise from the db connection has been resolved. However when running my automated tests the first few tests are now always failing because the application has not finished initialising with the db before jasmine starts to run my tests against the APIs. I can see in my logs the requests on the coming through and myDb.getDbConnection(); in the middleware returning undefined. I am using supertest and node-jasmine to run my tests. For example
'use strict';
var app = require('../../app');
var request = require('supertest');
describe('GET /api/content', function () {
it('should respond with JSON object', function (done) {
request(app)
.get('/api/content')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).toEqual(jasmine.any(Object));
done();
});
});
});
So, my question is how can I prevent supertest from making the requests until the server.listen() step has been completed as a result of the myDb.init() call being resolved? OR perhaps there is some kind of jasmine beforeAll that I can use to stop it running the describes until the promise has been resolved?
You could make you app return an EventEmitter which emits a "ready" event when it has completed its initialisation.
Then your test code, in a before clause, can wait until the "ready" event arrives from the app before proceeding with the tests.
How can I implement a custom error handler in Express using CSRF middleware after users click the back button in browser and resubmit the form? By default Express return a 403 page with lots of stack traces. I want to replace it by for example redirecting user to a custom error page. How can I do that?
Here are some examples of writing custom error handlers in Express: https://github.com/visionmedia/express/blob/master/examples/error-pages/index.js
Here are the custom error handlers I use: Error handling in an Express route
You might also want to consider modifying connect to return a different code than 403 when CSRF fails. You can change it here: https://github.com/senchalabs/connect/blob/master/lib/middleware/csrf.js#L82
You might choose 428 Precondition Required. The full list is here: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
That way, you could have a special message shown only for CSRF failures.
Like any other well designed middleware csurf passes the error to next. So it's possible to react on the raised error in the following way:
var csurf = require('csurf')();
app.use(function (req, res, next) {
csurf(req, res, function (err) {
if (err) {
// do what ever with err
} else {
next();
}
});
});