Express.js: Pass asynchronous errors thrown - javascript

I notice a recurring pattern in my express app which I think could be optimized. Basically I have a route calling a method with some asynchronous functions.
index.js
const controller = require('./controller.js');
const router = new Router();
router.post('/user', controller.createUser);
module.exports = router;
controller.js
exports.createUser = async (req, res, next) => {
try {
// asynchronous calls, etc.
} catch (e) {
// pass error to middleware
next(e);
}
}
The try/catch blocks are recurring in each of my controller methods. I'd want errors caught to be passed to my error-handling middleware. Therefore it seems impractical and repetitive to pass errors in each of my controller functions. Could I refactor this?
What if I wrap the controller method in a function as such:
index.js
const controller = require('./controller.js');
const router = new Router();
const handleErrors = (func) => async (req, res, next) => {
try { await func(req, res, next) }
catch (e) { return next(e) }
};
router.post('/user', handleErrors(controller.createUser));
module.exports = router;
controller.js
exports.createUser = async (req, res, next) => {
// asynchronous calls, etc.
if (a !== b) {
// errors can be passed to middleware as such
throw new CustomError(400, 'a is not equal to b');
}
}
Would this be an appropriate solution? Does Express have any built-in ways of accomplishing the same thing? Should I be cautious about refactoring my entire application in this way?

Would this be an appropriate solution?
Yes, looks nice.
Does Express have any built-in ways of accomplishing the same thing?
No, Express was written before async / await was introduced.
Should I be cautious about refactoring my entire application in this way?
I don't think so. How i would write that:
const handleErrors = (func) => (req, res, next) => func(req, res).then(() => next(), next);

I recommend you this article: https://medium.com/#Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
As in the article, this should be the middleware:
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
This is how a controller should look like:
router.get('/users/:id', asyncMiddleware(async (req, res, next) => {
/*
if there is an error thrown in getUserFromDb, asyncMiddleware
will pass it to next() and express will handle the error;
*/
const user = await getUserFromDb({ id: req.params.id })
res.json(user);
}));
router.post('/users', asyncMiddleware(async (req, res, next) => {
const user = await makeNewUser(req.body);
res.json(user)
}))

Related

Execute middleware on every route call

In my Express application I implement routes in routes.ts:
var Router = express.Router();
Router.route('/models/:modelId')
.get(function (req, res) {
service.setParameter(req)
service.get(req,res)
});
Router.route('/models/:modelId')
.post(function (req, res) {
service.setParameter(req)
service.post(req,res)
});
And express.ts:
export const App = express()
App.use(express.json())
App.use(express.urlencoded({ extended: true }))
App.use(helmet())
App.use('/', Router)
At each router call I'd like to execute a piece of code service.setParameter(req) that gets particular parameter from 'params', but I don't want to add to each router method explicitly.
I tried adding it at as middleware before and after Router
App.use('/', Router)
App.use(function(req, res, next){
service.setParameter(req)
next()
})
But if I define it before Router then route hasn't been set yet, and I don't get the parameter I want, and if I define it after, then middleware is not executed.
How can execute service.setParameter(req) in a generic way so that it applies to all the routes?
In express.ts file, you can add a middleware that would do it before mounding the Router, and then just procced forward with next(). You can do it like this:
App.use('/*', (req, res, next) => {
service.setParameter(req);
next();
});
App.use('/', Router)
You need to place your custom middleware between the context path and your router inside app.use(..):
const router = express.Router();
router.post('/', (req, res) => {
service.post(req,res);
});
router.get('/', (req, res) => {
service.get(req,res)
});
app.use('/models', (req, res, next) => {
service.setParameter(req);
next();
}, router);
With above code the middleware will be excecuted for all requests to '/models'.
You can use app.use(async (req,res,next) => {...}) in order to declare a middleware that executes in all the requests, if you want this middleware to be called first, it must be declare before than your routes, the middleware have to call next() in order to continue with the execution flow, if you want to be called at the end of you request, you have to put at the end of your declarations but before of the error middleware, in that approach each route have to call next() at the end of your function.
First approach
const express = require('express');
const app = express();
const router = express.Router();
router.post('/', async (req, res) => {
await service.post(req,res);
});
router.get('/', async (req, res) => {
await service.get(req,res)
});
app.use((req,res,next) => {
console.log("always called");
next();
});
app.use('/',router);
Second approach
const express = require('express');
const app = express();
const router = express.Router();
router.post('/', async (req, res, next) => {
await service.post(req,res);
next();
});
router.get('/', async (req, res, next) => {
await service.get(req,res);
next();
});
app.use('/',router);
app.use((req,res) => {
console.log("always called");
});
Thanks for all the answers, they helped me better understand how routing works on Express.
I found another solution, which I think works best in my case - using Router.all() method:
const setRequest = function(req, res, next){
logger.setRequest(request)
next()
}
Router.route('/models/:model_id')
.all(setRequest)
.get(function (req, res) {service.execute()})
.put(function (req, res) {service.execute()})

How to use a middleware in NodeJS if you are using controllers? [duplicate]

I'm trying to upload an image in my server.
In the front-end I'm working with Angular.
The front-end is working fine, I only posted to show you how I'm passing the file to back-end!
component.html
<div fxLayout="column" fxLayoutAlign="center center">
<div>
<mat-form-field>
<ngx-mat-file-input placeholder="Only photos" [accept]="'.jpg, .jpeg, .png'" (change)="onChange($event)"></ngx-mat-file-input>
</mat-form-field>
</div>
<div>
<button mat-button (click)="onSubmit()">Send</button>
</div>
</div>
component.ts - functions
imagem: File;
constructor(private uploadService: UploadService) { }
onChange(event) {
this.imagem = event.target.files[0];
}
onSubmit() {
this.uploadService.upload(this.imagem);
}
upload.service.ts - functions
constructor(private http: HttpClient) { }
upload(file: File) {
const formData = new FormData();
formData.append('img', file, file.name);
this.http.post(environment.apiBaseUrl + '/upload', formData, {responseType: 'text'}).subscribe(
res => console.log('Done')
);
}
In the back-end I have this structure:
app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const rtsIndex = require('./routes/index.router');
var app = express();
// middleware
app.use(bodyParser.json());
app.use(cors());
app.use('/api', rtsIndex);
// start server
app.listen(3000, () => console.log('Port: 3000'));
index.router.js
const express = require('express');
const router = express.Router();
const ctrlUpload = require('../controllers/upload.controller');
router.post('/upload', ctrlUpload.send);
module.exports = router;
upload.controller.js
const express = require('express');
const multer = require('multer');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now()+'-'+file.originalname);
}
});
const upload = multer({ storage });
module.exports.send = (req, res) => {
upload.single('img');
console.log(req.body, req.files);
res.send('ok');
}
I've tried to call the middleware inside the routing, but I don't think it's correctly and I didn't reach the goal. Algo, the upload is not one.
On server side I get: {} undefined as result, which probably means the multer is not treating the file.
On client side I get: Done.
So what am I doing wrong? And how can I make it works with this back end structure?
Express middlewares are designed to be installed at the routing level. Indeed, in the MVC model express programmers call controllers "routes" (personally I perefer to call them controllers instead of routes in my code). Separating controllers from routes (they both mean the same thing) doesn't really make sense when viewed from traditional MVC frameworks - but you can if you want.
To use multer as designed you need to do it in index.router.js:
index.router.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const ctrlUpload = require('../controllers/upload.controller');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now()+'-'+file.originalname);
}
});
const upload = multer({ storage });
router.post('/upload', upload.single('img'), ctrlUpload.send);
module.exports = router;
Then you need to remove all the multer related code from upload.controller.js
You can however insist on doing it in upload.controller.js. The key here is to understand what middlewares are.
In Express, a middleware is a function with the prototype:
function (req, res, next) { // next is optional
// middleware logic
}
Yes, that's right. The code in your upload.controller.js file is a middleware. You are writing a middleware yourself that happens to be at the end of the middleware chain.
You see, Express only accepts middlewares. Express has nothing else. Routes are middlewares that happen to be at the end.
Express .use(), .get(), .post() and related methods accept an infinite number of arguments. The first is optionally a route specifier (but not necessary) and the rest of the arguments are middlewares. For example:
app.get('/foo',
(req, res, next) => {
// first middleware
next(); // next is what allows processing to continue
},
(req, res, next) => {
// second middleware
next();
},
(req, res, next) => {
res.send('hello'); // controller logic - a controller
// is just the last middleware
// Note: if you call next() instead of res.send() in a
// controller express will respond with a 500 internal
// server error status with whatever string you pass
// to next() as the error message.
}
);
Knowing this, we know what the function upload.single('img') returns. The function does not execute the middleware logic. Instead it returns the middleware function:
let middleware = upload.single('img');
// middleware is now a function with the prototype:
// (req, res, next) => {}
So to execute the middleware logic we have to call it (express would automatically call it as part of route processing, just like how it calls your controller function, but if we want to do it ourselves we can).
Here's what you need to do if you want to implement the middleware in upload.controller.js:
module.exports.send = (req, res, next) => {
upload.single('img')(req, res, () => {
// Remember, the middleware will call it's next function
// so we can inject our controller manually as the next()
console.log(req.body, req.files);
res.send('ok');
});
}
That's a lot to unpack. We can make the code easier to understand if we refactor it a little:
let middleware = upload.single('img');
module.exports.send = (req, res, next) => {
// Define the controller here to capture
// req and res in a closure:
let controller = () => {
console.log(req.body, req.files);
res.send('ok');
};
middleware(req, res, controller); // call the middleware with
// our controller as callback
}
But this is very non-standard and would be highly unexpected to an experienced Express.js programmer. I wouldn't do this even though it's possible. It also tightly couple the middleware with your controller completely negating the very flexible nature of Express middleware configuration system.
An example of a separated file of Multer Middleware based in the #slebetman answer
./middlewares/multer.js
const multer = require('multer')
const ErrorMessages = require('../constants/ErrorMessages')
function makeid (length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
const DIR = './uploads/'
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-')
cb(null, makeid(16) + '_' + fileName)
}
})
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype === 'image/png' || file.mimetype === 'application/pdf') {
cb(null, true)
} else {
cb(null, false)
return cb(new Error('Only .png, .jpg, .mp4 and .jpeg format allowed!'))
}
}
})
module.exports.send = (req, res, next) => {
return upload.single('file')(req, res, () => {
// Remember, the middleware will call it's next function
// so we can inject our controller manually as the next()
if (!req.file) return res.json({ error: ErrorMessages.invalidFiletype })
next()
})
}
./routes.js
routes.post('/object', multer.send, ObjectController.createObject)
This avoids the status 500 for wrong filetype
Hope that helps someone :D
A working example of how you can use it in an expressjs handler
import multer from 'multer';
export default {
async upload(req: Request, res: Response, next: NextFunction) {
const middleware = upload.single('photo');
return middleware(req, res, () => {
try {
const file = req.file;
console.log('req.file', req.file);
if (!file) {
throw new ResourceValidationError('media-library', [
{
property: 'avatar',
constraints: {
isNotEmpty: 'avatar should not be empty',
},
},
]);
}
console.log('filename:', file.filename);
res.status(StatusCodes.OK).json({
status: { code: StatusCodes.OK, phrase: ReasonPhrases.OK },
});
} catch (error) {
next(error);
}
});
},
};

Add middleware to all firebase functions in one line / function

In express you can add middleware such as app.use(cors()) which adds it to all of the endpoints, however I can't find something similar in firebase examples. Here is the example (see below) of how to apply it in every function. However I want to apply the middleware (cors or other) globally, as I have many functions.
import * as cors from 'cors';
const corsHandler = cors({origin: true});
export const exampleFunction= functions.https.onRequest(async (request, response) => {
corsHandler(request, response, () => { return handler(req, res) });
});
What is the equivalent of app.use() in firebase? Is adding and express server the only option?
Use currying to create a handler, you have to repeat it across all the functions, but it's easier than writing the middleware each time:
const applyMiddleware = handler => (req, res) => {
return cors(req, res, () => {
return handler(req, res)
})
}
exports.handler = functions.https.onRequest(applyMiddleware(yourHandler))
Edit, an example of a more complex middleware:
const applyMiddleware =
(handler, { authenticatedRoute = false } = {}) =>
(req, res) => {
if (authenticatedRoute) {
const isAuthorized = isAuthenticated(req)
if (!isAuthorized) {
return res.status(401).send('Unauthorized')
}
}
return cors(req, res, () => {
return handler(req, res)
})
}
exports.handler = functions.https.onRequest(
applyMiddleware(yourHandler, { authenticatedRoute: true })
)
import cors from 'cors'
const corsHandler = cors({origin: true});
const applyCORS = handler => (req, res) => {
return corsHandler(req, res, _ => {
return handler(req, res)
})
}
export const firebasefunc = functions.https.onRequest(applyCORS(myhandler))

Return inside express router middleware

If I have a simple express router that just returns nothing (undefined), is there a way to recover from it or will the request just hang until there's a timeout?
For example if I had this code:
const express = require('express');
const app = express();
const router = express.Router();
router.use((req, res, next) => {
console.log('stuck');
return;
});
app.use(router);
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(3000, () => console.log('Example app listening on port 3000!'));
The app hangs after printing stuck. If I return next() the request continues, but I'm trying to see if there's a way to recover from an empty return statement without calling next() at some point.
Thanks in advance!
Since you don't want to call next(), you could probably wrap the handler.
Something like this:
const withReturnNext = (func) => (req, res, next) => {
func(req, res);
next();
}
app.use(withReturnNext((req, res) => res.set('x-headername', 'value'));
However, this is not advisable because any async code will return before the asyncronous action is finished, leading to next() being called before it was supposed to.
You have to use next() to continue the middleware chain. If you return from the function you just terminate the chain and if subsequent function is responsible for the response you won't get it. If you want to terminate (for example because something went wrong) but still finish the request you can use something like that:
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something went wrong')
})

Cannot call a class as a function in nodejs express

Hi I am trying to follow ES6 syntax to create a middleware of my node.js application.
index.js
export default class Middleware {
constructor() {
//do nothing
}
fun1 = (req, res, next) => {
console.log("------------------------------------");
console.log("AAa");
console.log("------------------------------------");
next();
};
fun2 = (req, res, next) => {
console.log("------------------------------------");
console.log("AAa");
console.log("------------------------------------");
next();
};
}
app.js
import Middleware from ".index";
app.use(Middleware);
I am getting an error Cannot call a class as a function. Does anyone know what is wrong?
Express app#use expects a function with the following signature:
function(req, res, next) {
To make it work, you need to do:
Create an instance of Middleware class.
Register middleware for each function in the class.
Example:
let middleware = new Middleware();
app.use(middleware.func1);
app.use(middleware.func2);

Categories