Express make try/catch block into middleware to avoid duplication - node.js

Im writing my expressJs application, and Im finding in my routes controller the same duplicated code for catching exception, I was wondering how to avoid this.
I have checked this thread, but I get this error "Cannot read property 'catch' of undefined" : Express Try and Catch in Form of middleware
this is my route.js
const express = require("express");
const createHttpError = require("http-errors");
const Validator = require("../middlewares/Validator");
const TaskNotFoundException = require("../services/TaskNotFoundException");
const TaskService = require("../services/TaskService");
router.get("/tasks", async (req, res, next) => {
try {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
} catch (error) {
next(createHttpError(500));
}
});
router.get("/task/:id", async (req, res, next) => {
const { id } = req.params;
try {
const data = await TaskService.getTask(id);
res.send({ code: 200, message: "Success", data });
} catch (error) {
if (error instanceof TaskNotFoundException) {
next(createHttpError(404));
} else {
next(createHttpError(500));
}
}
});
and the list goes on
as you see in all my routes I have a try catch block with the possible errors (either only a 500, or a 500/404). And I would like to avoid this repetition.
this is my app.js
const express = require("express");
const bodyParser = require("body-parser");
const createHttpError = require("http-errors");
const api = require("./routes/api");
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use("/api", api);
// Catch HTTP 404
app.use((req, res, next) => {
next(createHttpError(404));
});
// Error Handler
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
error: {
status: err.status || 500,
message: err.message,
},
});
});
module.exports = app;
Like I said, it works perfectly now, I would just like to try to avoid the try catch code duplication, and Ive checked the other questions in Stackoverflow but havent helped. The solution ive linked returns a 500 with this catch undefined message (which is not what I want) and on other routes that also have a 404 it just doesnt work.
Thanks a lot!
Update:
I followed Heikos advice but still not working
api.js
const express = require("express");
const createHttpError = require("http-errors");
const Validator = require("../middlewares/Validator");
const TaskNotFoundException = require("../services/TaskNotFoundException");
const TaskService = require("../services/TaskService");
const router = express.Router();
router.get("/tasks", async (req, res, next) => {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
});
app.js
const express = require("express");
const bodyParser = require("body-parser");
const createHttpError = require("http-errors");
const api = require("./routes/api");
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use("/api", api);
function catchAsyncErrors(middleware) {
return async function(req, res, next) {
try {
await middleware(req, res, next);
} catch(err) {
next(err);
}
};
}
// Catch HTTP 404
app.use(catchAsyncErrors((req, res, next) => {
next(createHttpError(404));
}));
// Error Handler
app.use(catchAsyncErrors((err, req, res, next) => {
res.status(err.status || 500);
res.json({
error: {
status: err.status || 500,
message: err.message,
},
});
}));
module.exports = app;

If the code inside your async middleware functions contains an await, you must also wrap it in a try-catch block, otherwise a rejected promise will be unhandled. For example:
app.use(async function(req, res, next) {
try {
await Promise.reject("error");
} catch(err) {
next(err);
}
});
propagates the error to the error handler, but without the try-catch block it leads to an "UnhandledPromiseRejection".
You can save some typing if you wrap your middleware into a catchAsyncErrors function:
function catchAsyncErrors(middleware) {
return async function(req, res, next) {
try {
await middleware(req, res, next);
} catch(err) {
next(err);
}
};
}
router.get("/tasks", catchAsyncErrors(async (req, res, next) => {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
}));

Related

error message occur when im trying redirecting the user on server side

im trying to redirect the user to the dashboard if ever the id is not on database.
but I get an error message saying "Cannot set headers after they are sent to the client"
my middleware code
const mongoose = require('mongoose');
const Room = require('./model/room');
module.exports.isOnDB = async (req, res, next) => {
const { id } = req.params;
console.log(id);
const room = Room.findById(id, function (err) {
console.log('ge');
//console.log(err.message);
res.redirect('/');
});
next();
};
my routes code
const { isOnDB } = require('../middleware');
router.get('/', (req, res) => {
res.render('layouts/boilerplate');
});
router.get('/room/:id', isOnDB, (req, res) => {
res.render('room', { roomId: req.params.room });
});
my app.js
const adminRoutes = require('./routes/admin');
app.use('/', adminRoutes);
The problem is that you are not waiting for your Room.findById function to finish before calling next(). I would move the next() call into the Room.findById callback so you can handle both situations - if the id is in the DB and if it isn't.
For example:
module.exports.isOnDB = async (req, res, next) => {
const { id } = req.params;
const room = Room.findById(id, function (err) {
if (err) {
// if id is NOT found, or if there's an error, call next()
console.log(err);
return next();
}
// if the id is found in the DB, then redirect
res.redirect('/');
});
};

How to use express middleware on router level

I'm trying to add a simple middleware function for every request on the router level.
The docs are stating:
a middleware function with no mount path will be executed for every
request to the router
In my application I have only one router with one endpoint that is listening for every request and I'm placing my middleware function above this endpoint, but the middleware never gets launched.
Express setup:
const initializeExpress = (): void => {
const port = process.env.PORT;
const app = express();
app.use(helmet());
app.use(express.json());
app.use(cors());
app.use('/api', Router);
app.listen(port, () => {
console.log(`Listening for requests at http://localhost:${port}`);
});
};
My router code:
const Router = express.Router();
Router.use((req, res, next) => {
const token = req.header('authorization');
if (!token) res.status(401).send({ message: 'Unauthorized' });
const isAuthenticated = isAuthorized(token!);
if (isAuthenticated) {
next();
} else {
res.status(401).send({ message: 'Unauthorized' });
}
});
Router.get(
'/:arg1/:arg1Id?/:arg2?/:arg2Id?/:arg3?/:arg3Id?/:arg4?/:arg4Id?',
async (req, res): Promise<void> => {
const routeParams = filterRouteParams(req.params);
const path = basePath + getPathFromRouteParams(routeParams) + '/data.json';
if (await pathExists(path)) {
const data = await getJsonFromPath(path);
if (!isEmpty(data)) {
res.status(200).json(data);
return;
}
res.status(400).send({ message: 'Data not found' });
}
}
);
What am I doing wrong here?
On which route the middleware will be active, you need to define it.
There's two way to make this
First, call middleware before the your route:
const Router = express.Router();
const myMiddleware = (req, res, next) => {
const token = req.header('authorization');
if (!token) res.status(401).send({ message: 'Unauthorized' });
const isAuthenticated = isAuthorized(token!);
if (isAuthenticated) {
next();
} else {
res.status(401).send({ message: 'Unauthorized' });
}
}
Router.get(
'/:arg1/:arg1Id?/:arg2?/:arg2Id?/:arg3?/:arg3Id?/:arg4?/:arg4Id?',
myMiddleware(), //Call middleware here
async (req, res): Promise<void> => {
const routeParams = filterRouteParams(req.params);
const path = basePath + getPathFromRouteParams(routeParams) + '/data.json';
if (await pathExists(path)) {
const data = await getJsonFromPath(path);
if (!isEmpty(data)) {
res.status(200).json(data);
return;
}
res.status(400).send({ message: 'Data not found' });
}
}
);
Second, calling middleware for all routes that you define:
const Router = express.Router();
const myMiddleware = (req, res, next) => {
const token = req.header('authorization');
if (!token) res.status(401).send({ message: 'Unauthorized' });
const isAuthenticated = isAuthorized(token!);
if (isAuthenticated) {
next();
} else {
res.status(401).send({ message: 'Unauthorized' });
}
}
Router.use('/:arg1/:arg1Id?/:arg2?/:arg2Id?/:arg3?/:arg3Id?/:arg4?/:arg4Id?', myMiddleware());
Router.get(
'/:arg1/:arg1Id?/:arg2?/:arg2Id?/:arg3?/:arg3Id?/:arg4?/:arg4Id?'
async (req, res): Promise<void> => {
const routeParams = filterRouteParams(req.params);
const path = basePath + getPathFromRouteParams(routeParams) + '/data.json';
if (await pathExists(path)) {
const data = await getJsonFromPath(path);
if (!isEmpty(data)) {
res.status(200).json(data);
return;
}
res.status(400).send({ message: 'Data not found' });
}
}
);

How to return a JSON response in errorHandler Middleware in NodeJS using Express?

How to return a JSON response from errorHandler Middleware in NodeJS using Express. My code is:
UserController.js
const UserService = require("../services/UserService");
exports.createUser = async (req, res) => {
try {
throw new Error("Error from UserController");
} catch (error) {
throw error;
}
};
index.js
const express = require("express");
const conectarDB = require("./config/DBConfig");
const cors = require("cors");
//Crear el servidor
const app = express();
//Conectar a la BD
conectarDB();
//Habilitar cors
app.use(cors());
//Habilitar express.json
app.use(express.json({ extended: true }));
//Puerto de la app
const PORT = process.env.PORT || 4000;
//Rutas
app.use("/api/user", require("./routes/UserRoute"));
//ErrorHandler middleware
app.use(function (err, req, res, next) {
res.status(500).json({ error: err.message });
});
//Arrancar el app
app.listen(PORT, () => {
console.log(`El servidor está funcionando en el puerto ${PORT}`);
});
UserRoute.js
const express = require("express");
const router = express.Router();
const UserController = require("../controllers/UserController");
router.post("/", UserController.createUser);
module.exports = router;
I would like to get a JSON response from my errorHandler middleware, like that:
{
error: "Error from UserController"
}
My current code doesn't work. it doesn't return any JSON response.
Thanks in advance.
Did you handle the error properly in the router earlier?
For example, as below.
router.get('/user', async (req, res, next) => {
try {
.... // some logics...
} catch (err) {
// you should call 'next' for throwing error to your error handler.
next(err);
}
});
As #Won Gyo Seo mentions you need to use next to call the next middlewere in the stack, if you don't you'll never reach your error handler. That means putting catch and next on every route. I understand if you don't want to do that.
One way would be to make a helper function
bindAndCatch (fn) {
return function(req, res, next) {
fn(req, res, next)
.then(res.json.bind(res))
.catch(next)
}
You can now do
router.get('/user', bindAndCatch((req) => {
return createUser(req.id)
}))
It will return the promise and catch the error, call the next middlewere and pass the error to it.

Nodejs authentication middleware not working

I am learning a new way to authenticate all my APIs using the application-level middleware. I looked into multiple examples. I tried the following code as one of the ways.
Below is my code, I am writing firebase function with the help of necessary fields already there. I use "firebase serve" to host my functions locally.
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')()
const cors = require('cors')({ origin: true })
const app = express()
const router = express.Router()
app.use(cors)
app.use(cookieParser)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(async (err, req, res, next) => {
console.log('Middleware')
try {
const { authorization } = req.headers
if (!authorization) {
throw new ErrorHandler(401, "User is not unathorized")
}
if (!authorization.startsWith('Bearer')) {
throw new ErrorHandler(401, "User is not unathorized")
}
const split = authorization.split('Bearer ')
if (split.length !== 2) {
throw new ErrorHandler(401, "User is not unathorized")
}
const token = split[1]
const decodedToken = await admin.auth().verifyIdToken(token);
res.setHeader("email", decodedToken.email)
next()
} catch (error) {
console.log("END")
next(error)
}
});
router.get('/', (req, res) => {
res.end(`${Date.now()}`)
})
router.post('/data', async (req, res, next) => {
res.setHeader("Content-Type", "application/json")
console.log('DATA')
try {
// my other logic goes here
res.end()
} catch (error) {
next(error)
}
})
app.use('/api', router)
app.use((err, req, res, next) => {
if (err) {
handleError(err, res);
}
console.log(JSON.stringify(req.body))
});
exports.app = functions.https.onRequest(app)
I have created a cloud function named app. I am using API like this:
http://localhost:5000/app/api/data
I have written a middleware for authorizing all my APIs that are coming. Middleware is fetching bearer token and token is being verified with the help of firebase.
But when I call "/api/data" this API from postman or web the middleware is not called. For debugging purpose I used console.log to check.
My current flow is POSTMAN -> DATA
What I want is:
POSTMAN -> MIDDLEWARE(if authenticated) -> DATA
POSTMAN -> MIDDLEWARE(if not authenticated) -> END
Please let me know what is the issue with my code is.
Remove err parameter from the middleware, You are setting it as an error handler instead of a middleware, this is the reason the code is not getting executed,
Below code will execute the handler every time you access the /api route
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')()
const cors = require('cors')({ origin: true })
const app = express()
const router = express.Router()
app.use(cors)
app.use(cookieParser)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(async (req, res, next) => {
console.log('Middleware')
try {
const { authorization } = req.headers
if (!authorization) {
throw new ErrorHandler(401, "User is not unathorized")
}
if (!authorization.startsWith('Bearer')) {
throw new ErrorHandler(401, "User is not unathorized")
}
const split = authorization.split('Bearer ')
if (split.length !== 2) {
throw new ErrorHandler(401, "User is not unathorized")
}
const token = split[1]
const decodedToken = await admin.auth().verifyIdToken(token);
res.setHeader("email", decodedToken.email)
next()
} catch (error) {
console.log("END")
next(error)
}
});
router.get('/', (req, res) => {
res.end(`${Date.now()}`)
})
router.post('/data', async (req, res, next) => {
res.setHeader("Content-Type", "application/json")
console.log('DATA')
try {
// my other logic goes here
res.end()
} catch (error) {
next(error)
}
})
app.use('/api', router)
app.use((err, req, res, next) => {
if (err) {
handleError(err, res);
}
console.log(JSON.stringify(req.body))
});
exports.app = functions.https.onRequest(app)

Why my error is not being handled in express.js?

I am learning express.js, and I am thinking why I receive UnhandledPromiseRejectionWarning and no error response is being sent after executing http api call..
For error handling I have created helper class and middleware:
/helpers/errorHander.js
------------------------------------
class ErrorHandler extends Error {
constructor(statusCode, message) {
super();
this.statusCode = statusCode;
this.message = message;
}
}
const handleError = (err, res) => {
const { statusCode, message } = err;
res.status(statusCode).json({
status: 'error',
statusCode,
message
});
};
module.exports = {
ErrorHandler,
handleError
};
I have enabled this middleware in app.js file:
/app.js
------------------------------------
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const config = require('./config/init');
const cors = require('cors');
const { handleError, ErrorHandler } = require('./helpers/errorHandler');
//routes
const apiRoutes = require('./api');
// connect db
config.initializeDB();
// configure bodyParser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Enable Cors
app.use(cors());
// Set Routes
app.use('/', apiRoutes);
app.get('/error', (req, res) => {
throw new ErrorHandler(500, 'Internal server error');
});
// Enable error handling middleware
app.use((err, req, res, next) => {
handleError(err, res);
});
module.exports = app;
When I perform request to the /error endpoint, it works correctly:
/error endpoint
But when performing request to the endpoint which performs operations with mongoose I am not getting desired result:
/v1/users/ endpoint
user.route.js:
const router = require('express').Router();
const userController = require('./user.controller');
router.get('/', userController.getUsers);
router.post('/', userController.createUser);
module.exports = router;
user.controller.js:
const userService = require('../../../services/user');
module.exports = {
getUsers: (req, res, next) => {
return res.json(userService.getUsers());
},
createUser: (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
res.json(userService.createUser(username, password));
}
};
user.service.js:
const User = require('../models/user');
const { ErrorHandler } = require('../helpers/errorHandler');
module.exports = {
getUsers: async () => {
return User.find({});
},
createUser: async (username, password) => {
const user = new User({ username, password });
try {
await user.save();
return user;
} catch (err) {
if (err.code === 11000) {
throw new ErrorHandler(409, 'Username already exists!');
}
throw new ErrorHandler(500, 'Internal server error');
}
}
};
Console gives such a warning:
Console Output
Why it is not working as I want, and how can I make it work?
=========================================================
Update #1
As user - jfriend00 suggested, I tried to await the promise, and here my code looks like on user.controller.js:
const userService = require('../../../services/user');
module.exports = {
getUsers: (req, res, next) => {
return res.json(userService.getUsers());
},
createUser: async (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
try {
let user = await userService.createUser(username, password);
} catch (err) {
next(err);
}
}
};
And Now I get the desired outcome.

Resources