Express.js Designing Error Handling - node.js

I'm stuck on how to design error handling in an Express.js application.
What are the best design practices to handle errors in Express?
To my understanding, I can handle errors in 2 different ways:
First way would be to use an error middleware and, when an error is thrown in a route, propagate the error to that error middleware. This means that we have to insert the logic of the error handler in the middleware itself (note, the middleware here was purposely kept simple).
app.post('/someapi', (req, res, next) => {
if(req.params.id == undefined) {
let err = new Error('ID is not defined');
return next(err);
}
// do something otherwise
});
app.use((err, req, res, next)=>{
// some error logic
res.status(err.status || 500).send(err);
});
Another option is to deal with the errors on the spot, when the error happens. This means that the logic must be in the route itself
app.post('/someapi', (req, res, next) => {
if(req.params.id == undefined) {
let err = new Error('ID is not defined');
// possibly add some logic
return res.status(ErrorCode).send(err.message);
}
// do something otherwise
});
What is the best approach, and what are the best design practices for this?
Thank you

I think there are much more extensive cases but the main idea is using middleware design. Add your validation logic to this middleware.
yourRouter.post('/message', routerValidator.messageValidator, yourController.saveMessage.bind(yourController));
Below is my sample structure;
// controller
const BaseRoute = require('../infra/base/BaseRoute');
const log = require('./../../utils/log-helper').getLogger('route-web');
const { ErrorTypes } = require('../infra/middlewares/ErrorMiddleware');
const GameService = require('../../service/GameService');
const { SystemMessages } = require('../../statics/default_types');
module.exports = class WebController {
constructor() {
this._logger = log;
this._gameService = new GameService();
}
getGameInfo(req, res) {
var self = this;
try {
const info = self._gameService.getGameInfo(req.body.query);
return BaseRoute.success(res, { info });
} catch (err) {
self._logger.error('Something went wrong while getting game information', err);
return BaseRoute.internalError(res, SystemMessages.GENERIC_ERROR, req.getErrorCode(ErrorTypes.UNHANDLED, 1));
}
}
};
// router index
const express = require('express');
const ErrorMiddleware = require('../infra/middlewares/ErrorMiddleware').ErrorMiddlewarePath;
const baseValidator = require('../infra/validators/BaseRouterValidator');
const AndroidController = require('./AndroidController');
const IosController = require('./IosController');
const WebController = require('./WebController');
const AndroidRouter = express.Router();
const IosRouter = express.Router();
const WebRouter = express.Router();
const androidController = new AndroidController();
const iosController = new IosController();
const webController = new WebController();
AndroidRouter.post('/message', ErrorMiddleware(1), baseValidator.teamQueryValidator, androidController.getGameInfo.bind(androidController));
IosRouter.post('/message', ErrorMiddleware(1), baseValidator.teamQueryValidator, iosController.getGameInfo.bind(iosController));
WebRouter.post('/message', ErrorMiddleware(1), baseValidator.teamQueryValidator, webController.getGameInfo.bind(webController));
module.exports = {
AndroidRouter,
IosRouter,
WebRouter
};
// validator
const log = require('../../../utils/log-helper').getLogger('route-validator-base');
const BaseRoute = require('../base/BaseRoute');
const _ErrorTypes = require('../middlewares/ErrorMiddleware').ErrorTypes;
function teamQueryValidator(req, res, next) {
if (!req.body || !req.body.query) {
const params = req.body ? JSON.stringify(req.body) : 'Empty';
log.error('Invalid Parameters req body', params);
return BaseRoute.httpError(res, 'Bir takım adı giriniz..', 400, req.getErrorCode(_ErrorTypes.VALIDATION, 1));
}
return next();
}
module.exports = {
teamQueryValidator
};
// app.js that assigns to express
this._router = require('./src/route/api/index');
this._ErrorMiddleware = require('./src/route/infra/middlewares/ErrorMiddleware').ErrorMiddlewareRouter;
this.app.use('/api/android', this._ErrorMiddleware(1), this._router.AndroidRouter);
this.app.use('/api/ios', this._ErrorMiddleware(2), this._router.AndroidRouter);
this.app.use('/api/web', this._ErrorMiddleware(3), this._router.WebRouter);

What are the best design practices to handle errors in Express?
There is no best design, it's all subjective.
To my understanding, I can handle errors in 2 different ways:
Correct. You used error middleware for the first and then handled the error directly in the route handler.
To me, it makes sense to separate out the error handling logic from the business logic. It makes for cleaner code. So the former (error middleware) would be better IMO.
You would have a different error handler for different errors.

Related

Modified Req Url in Express middleware ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client

Hey guys I am facing the error Error "[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when I am trying to modify the req.url in a express middleware.
My middleware
export function ModifyQueryMiddleware(config, Authconfig, Repo ){
const accessTokenMap = new Map();
return async (request, res, next) => {
const accessToken = request.header('authorization') as string;
if(!accessToken){
throw new HttpException(res, 403)
}
if(!accessTokenMap.get(accessToken)){
const JWKS = jose.createRemoteJWKSet(new URL(config.jkwsUri));
try {
const jwtVerifyResult = await jose.jwtVerify(accessToken.replace('Bearer ', ''), JWKS);
const {payload} = jwtVerifyResult;
accessTokenMap.set(accessToken, payload)
const aumParams = await authentication(payload, authConfig,Repo);
const queryRestrictionStrategy = QueryRestrictionStrategyFactory(aumParams, request)
queryBuilder(queryRestrictionStrategy)
next()
} catch(err){
}
}
const payload = accessTokenMap.get(accessToken);
const aumParams = await authentication(payload, authConfig, repo);
const queryRestrictionStrategy = QueryRestrictionStrategyFactory(aumParams, request)
queryBuilder(queryRestrictionStrategy)
next()
}
}
My queryBuilder:
export function queryBuilder(strategy: QueryRestrictionStrategy){
const {req, id} = strategy
if(req.url === '/someurl'){
req.url = `/someurl?${id}`
}
return
}
I am really confused as I don't modify the header of a response instead I am just modifying the query without the querybuilder the middleware works fine. I already looked at a few questions regarding this error however the res was there always modified.
Any help or tips would be really appreciated !
Your code can call next twice when !accessTokenMap.get(accessToken) is true. You have to return once that part of your code is handled:
if (!accessTokenMap.get(accessToken)) {
const JWKS = jose.createRemoteJWKSet(new URL(config.jkwsUri));
try {
...
next();
} catch(err) {
next(err); // make sure to pass the error along!
}
return;
}

Not getting any response - Express Validator

I'm using the latest version of express-validator for validation.
I'm not getting any response, However Old method i.e checkBody is working fine while new method i.e check('keyName') is not working properly.
Below is my code.
package.json
"express-validator": "^5.0.3",
routes.js
var authValidator = require('./../validation/auth.validation');
var routes = require('express').Router();
routes.post('/login', [
authValidator.validateLogin,
authValidator.checkValidationResult ], function (req, res) {
console.log('3');
//res.send("Some other stuffs");
}
);
module.exports = routes;
auth.validation.js
module.exports.validateLogin = validateLogin;
module.exports.checkValidationResult = checkValidationResult;
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
function validateLogin(req, res, next) {
console.log('1');
return [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED)
.isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({min: 1}).withMessage(messages.PASSWORD_REQUIRED),
]
}
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(
res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}
I've noticed that node js not able to go ahead from the function validateLogin in auth.validation.js.
Can anyone tell me what's wrong with above code.
Inside console, Only 1 is displaying.
I'm attaching screenShot for referance.
We need to use simple Array and don't need to create function.
Follow this link
Is it possible to do the validation in a separate file and not inline in the route? - GitHub for more details.
Code should be like this.
auth.validation.js
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
module.exports.validateLogin = [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED).isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({ min: 1 }).withMessage(messages.PASSWORD_REQUIRED),
];
module.exports.checkValidationResult = checkValidationResult;
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}
`
validateLogin and checkValidationResult are being applied as middlewares to your route. In middlewares you use next()method to call next middleware in the queue. Just like in your checkValidationResult.
In case of validateLogin, its not passing control to next middleware. But check method from express-validator v5 is itself a middleware method. Thus I guess it won't work correctly.
Please have a look at: https://github.com/ctavan/express-validator/issues/449
Try using following Code:
routes.js
var authValidator = require('./../validation/auth.validation');
var routes = require('express').Router();
var authValidations = authValidator.getAuthValidations();
routes.post('/login',
authValidations,
authValidator.checkValidationResult, function (req, res) {
console.log('3');
//res.send("Some other stuffs");
}
);
module.exports = routes;
auth.validations.js
module.exports.getAuthValidations = getAuthValidations;
module.exports.checkValidationResult = checkValidationResult;
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
function getAuthValidations(req, res, next) {
return [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED)
.isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({min: 1}).withMessage(messages.PASSWORD_REQUIRED),
]
}
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(
res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}

Use Express Router to match a route

I'm trying to consolidate a bunch of route usage throughout my Express API, and I'm hoping there's a way I can do something like this:
const app = express()
const get = {
fetchByHostname({
name
}) {
return `hey ${name}`
}
}
const map = {
'/public/hostname/:hostname': get.fetchByHostname
}
app.use((req, res, next) => {
const url = req.originalUrl
const args = { ...req.body, ...req.query }
const method = map[url] // this won't work
const result = method(args)
return res.json({
data: result
})
})
I'm trying to avoid passing round the req and res objects and just handle the response to the client in one place. Is there an Express/Node/.js module or way to match the URL, like my map object above?
I really don't understand what you are trying to achieve, but from what i can see, your fectchByHostname({name})should be fetchByHostname(name) and you might be able to return hey $name. You should be sure you are using ES6 because with you args. Else you have to define the as in es5 args = {body: req.body, query: req.query};. Hope it helps.

Using (and reusing) multiple mongoose database connections on express.js

I'm looking for the easiest & performant way to make a multitenant express.js app for managing projects.
Reading several blogs and articles, I figured out that, for my application, would be nice to have a database per tenant architecture.
My first try has been to use subdomains to detect the tenant, and then map the subdomain to a mongodb database.
I came up with this express middlewares
var mongoose = require('mongoose');
var debug = require('debug')('app:middleware:mongooseInstance');
var conns [];
function mongooseInstance (req, res, next) {
var sub = req.sub = req.subdomains[0] || 'app';
// if the connection is cached on the array, reuse it
if (conns[sub]) {
debug('reusing connection', sub, '...');
req.db = conns[sub];
} else {
debug('creating new connection to', sub, '...');
conns[sub] = mongoose.createConnection('mongodb://localhost:27017/' + sub);
req.db = conns[sub];
}
next();
}
module.exports = mongooseInstance;
Then I register the models inside another middleware:
var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');
module.exports = registerModels;
var models = [];
var path = __dirname + '/../schemas';
function registerModels (req, res, next) {
if(models[req.sub]) {
debug('reusing models');
req.m = models[req.sub];
} else {
var instanceModels = [];
var schemas = fs.readdirSync(path);
debug('registering models');
schemas.forEach(function(schema) {
var model = schema.split('.').shift();
instanceModels[model] = req.db.model(model, require([path, schema].join('/')));
});
models[req.sub] = instanceModels;
req.m = models[req.sub];
}
next();
}
Then I can proceed normally as any other express.js app:
var express = require('express');
var app = express();
var mongooseInstance = require('./lib/middleware/mongooseInstance');
var registerModels = require('./lib/middleware/registerModels');
app.use(mongooseInstance);
app.use(registerModels);
app.get('/', function(req, res, next) {
req.m.Project.find({},function(err, pets) {
if(err) {
next(err);
}
res.json({ count: pets.length, data: pets });
});
});
app.get('/create', function (req, res) {
var p = new req.m.Project({ name: 'Collin', description: 'Sad' });
p.save(function(err, pet) {
res.json(pet);
});
});
app.listen(8000);
The app is working fine, I don't have more than this right now, and I'd like to get some feedback before I go on, so my questions would be:
Is this approach is efficient? Take into account that a lot will be happening here, multiple tenants, several users each, I plan to setup webhooks in order to trigger actions on each instance, emails, etc...
Are there any bottlenecks/pitfalls I'm missing? I'm trying to make this scalable from the start.
What about the model registering? I didn't found any other way to accomplish this.
Thanks!
Is this approach is efficient?
Are there any bottlenecks/pitfalls I'm missing?
This all seems generally correct to me
What about the model registering?
I agree with #narc88 that you don't need to register models in middleware.
For lack of a better term, I would use a factory pattern. This "factory function" would take in your sub-domain, or however you decide to detect tenants, and return a Models object. If a given middleware wants to use its available Models you just do
var Models = require(/* path to your Model factory */);
...
// later on inside a route, or wherever
var models = Models(req.sub/* or req.tenant ?? */);
models.Project.find(...);
For an example "factory", excuse the copy/paste
var mongoose = require('mongoose');
var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');
var models = [];
var conns = [];
var path = __dirname + '/../schemas';
function factory(tenant) {
// if the connection is cached on the array, reuse it
if (conns[tenant]) {
debug('reusing connection', tenant, '...');
} else {
debug('creating new connection to', tenant, '...');
conns[tenant] = mongoose.createConnection('mongodb://localhost:27017/' + tenant);
}
if(models[tenant]) {
debug('reusing models');
} else {
var instanceModels = [];
var schemas = fs.readdirSync(path);
debug('registering models');
schemas.forEach(function(schema) {
var model = schema.split('.').shift();
instanceModels[model] = conns[tenant].model(model, require([path, schema].join('/')));
});
models[tenant] = instanceModels;
}
return models[tenant];
}
module.exports = factory;
Aside from potential (albeit probably small) performance gain, I think it also has the advantage of:
doesn't clutter up the request object as much
you don't have to worry as much about middleware ordering
allows more easily abstracting permissions for a given set of models, i.e. the models aren't sitting on the request for all middleware to see
This approach doesn't tie your models to http requests, so you might have flexibility to use the same factory in a job queue, or whatever.

regarding foodme project in github

hello i have a question regarding the foodme express example over github:
code:
var express = require('express');
var fs = require('fs');
var open = require('open');
var RestaurantRecord = require('./model').Restaurant;
var MemoryStorage = require('./storage').Memory;
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
var removeMenuItems = function(restaurant) {
var clone = {};
Object.getOwnPropertyNames(restaurant).forEach(function(key) {
if (key !== 'menuItems') {
clone[key] = restaurant[key];
}
});
return clone;
};
exports.start = function(PORT, STATIC_DIR, DATA_FILE, TEST_DIR) {
var app = express();
var storage = new MemoryStorage();
// log requests
app.use(express.logger('dev'));
// serve static files for demo client
app.use(express.static(STATIC_DIR));
// parse body into req.body
app.use(express.bodyParser());
// API
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
i don't understand where is the api folder. it doesn't exist and i don't understand how information is going in and out from there. i can't find it.
can someone please explain this to me?
another question:
there is a resource for the restaurant
foodMeApp.factory('Restaurant', function($resource) {
return $resource('/api/restaurant/:id', {id: '#id'});
});
and in the restaurant controller there is a query:
var allRestaurants = Restaurant.query(filterAndSortRestaurants);
and the following lines:
$scope.$watch('filter', filterAndSortRestaurants, true);
function filterAndSortRestaurants() {
$scope.restaurants = [];
// filter
angular.forEach(allRestaurants, function(item, key) {
if (filter.price && filter.price !== item.price) {
return;
}
if (filter.rating && filter.rating !== item.rating) {
return;
}
if (filter.cuisine.length && filter.cuisine.indexOf(item.cuisine) === -1) {
return;
}
$scope.restaurants.push(item);
});
// sort
$scope.restaurants.sort(function(a, b) {
if (a[filter.sortBy] > b[filter.sortBy]) {
return filter.sortAsc ? 1 : -1;
}
if (a[filter.sortBy] < b[filter.sortBy]) {
return filter.sortAsc ? -1 : 1;
}
return 0;
});
};
the things that isn't clear to me is:
how is that we are giving the query just a function without even activating it.
as i understand we should have passed the query somthing like:
{id: $routeParams.restaurantId}
but we only passed a reference to a function. that doesn't make any sense.
could someone elaborate on this?
thanks again.
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
These lines are just defining string constants that are plugged into Express further down. They're not a folder.
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
So this function call to app.get(API_URL... is telling Express "Look out for GET requests that are pointed at the URL (your app's domain)/api/restaurant, and execute this function to handle such a request."
"api" is not a folder.
Every requests will pass through the app.get method.
This method will respond to the routes /api/restaurant as defined in the API_URL variable.

Resources