ExpressJS can have a chain of middlewares for a single route, which at the very least allows to split input validation logic from the actual handling of the inputs.
The usual pattern looks like this:
import { Router } from "express";
const router = Router();
router.use("/entity/:id", async (req, res, next) => {
try {
// validation logic
const isValid = ...
// short circuit if validation fails
if (!isValid) {
throw new Error("error message")
}
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/entity/:id", async (req, res, next) => {
try {
// route handling logic
...
} catch (error) {
next(error)
}
});
This works fine as long as validation middleware doesn't require database calls. But in the most basic usecase, auth, it does.
So auth code looks like this:
import { Router } from "express";
// `db` object created as per instructions
// https://vitaly-t.github.io/pg-promise/index.html
import { db } from "#db"
const router = Router();
router.use("/auth", async (req, res, next) => {
try {
// key retrieval logic
// assume it throws on any mistake
const authKey = ...;
// retrieve session by key
const session = await db.one(
"...",
{ authKey }
)
// throw if it's not found for whatever reason
if (!session) {
throw new Error("error message")
}
// save the session value for the request duration
// so the middleware/handlers can access
res.locals.session = session
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/auth/:id/posts", async (req, res, next) => {
try {
const { id } = req.params;
// it is guarantied to be always there because
// of the middleware before
const session = res.locals.session;
const posts = await db.manyOrNone("...", {session, id})
return res.status(200).json(posts);
} catch (error) {
next(error)
}
});
The issue here is these 2 middlewares will use 2 separate connections, and it will only get worse with more complicated routing.
Is there a way to create a transaction/task object, which is an argument for the callback for db.tx()/db.task(), which could be passed around outside of these methods?
Related
I have an express backend application. The problem I have is that all the routes contains the same try-catch piece which causes code bloat in my program:
// routes.js
router.get('/', async (req, res, next) => {
try {
const data = extractData(req)
await foo(data)
} catch (err) {
next(err)
}
})
// controllers.js
async function foo(data) {...do smh}
As you see above, try { extractData() } catch (err) { next(err) } portion of the code exists in all of the routes defined in the app.
I tried to create a wrapper function that takes controller function as parameter and use it as:
// routes.js
router.get('/', controllerWrapper(req, res, next, foo))
// controller-wrapper.js
async function controllerWrapper(req, res, next, controllerFunc) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
But this does not work due to function being invoked, and not being actually a callback.
How can I achieve this?
You should use a closure for this, so you can return the middleware function from controllerWrapper and use the controllerFunc inside the returned middleware
function controllerWrapper(controllerFunc) {
return async function (req, res, next) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
}
router.get('/', controllerWrapper(foo))
I a trying to implement a rest API for our project then I go for node js and express. I have built all the models and controllers. I faced an issue while trying to handle an error. Errorhandler function doesn't receive all the properties of error that caught in try/catch block. I can not read its name in a handler but I can use its name in the controller. Could you please help me?
const errorHandler = (err, req, res, next) => {
console.log(`Error in method:${req.method}: ${err.stack}`.bgRed);
let error = { ...err };
console.log(`Error handler: ${err.name}`);
res.status(error.statusCode || 500).json({
success: false,
data: error.message || 'Server Error',
});
};
module.exports = errorHandler;
controller
const mongoose = require('mongoose');
const Product = require('../models/Product');
const ErrorResponse = require('../utils/error');
const routeName = 'PRODUCT';
// #desc getting single product via id
// #route GET api/v1/products
// #acces public
exports.getProdcut = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return next(
new ErrorResponse(`Product not found with id:${req.params.id}`, 404)
);
}
res.status(200).json({
success: true,
data: product,
});
} catch (err) {
console.log(err.name);
console.log('ERRO APPEND');
next(new ErrorResponse(`Product not found with id:${req.params.id}`, 404));
}
};
Assuming that errorHandler is part of your middleware that is somewhere after getProdcut, you can try just throwing the error and Express will automatically detect that for you, because error handling middleware such as yours accepts 4 parameters. So the following would work:
const getProdcut = async (req, res, next) => {
try {
// ...
} catch (err) {
throw err;
}
};
const errorHandler = (err, req, res, next) => {
if (err) {
console.log('hello from the error middleware');
console.log(err.name);
}
else {
// next() or some other logic here
}
}
app.use('/yourRoute', getProdcut, errorHandler);
And inside of your errorHandler you should have access to the error object.
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
I am writing a middleware function that looks for validation errors and if the error is found gives out a certain output else continues the program flow. I have two functions with the exact code but they check for different schemas.
My first function runs without any exception. However, when I try to execute the second function I get an error in the console.
const validateCampground = (req, res, next) => {
const { error } = campgroundSchema.validate(req.body);
if (error) {
const msg = error.details.map((el) => el.message).join(",");
throw new ExpressError(msg, 400);
} else {
next();
}
};
const validateReview = (req, res, next) => {
const { error } = reviewSchema.validate(req.body);
if (error) {
const msg = error.details.map((el) => el.message).join(",");
throw new ExpressError(msg, 400);
} else {
next(); //this is the point where the exception occurs
}
};
It is only inside the validateReview function where next middleware function is not recognised as a valid function.
The problem was not with the next() middleware but instead it was with the route as I was wrapping the route with the validateReview function.
I was doing something like this :
app.post(
"/campgrounds/:id/reviews",
validateReview(
catchAsync(async (req, res) => {
//my Logic here
})
));
Whereas , I should have been doing something like this :
app.post(
"/campgrounds/:id/reviews",
validateReview,
catchAsync(async (req, res) => {
//my logic here
})
);
hi if you want to use a middileware
exports.middileware = (req,res,next)=>{
try{
//middileware logic
next();
}catch(err){
//print the error
})
}
}
and call the exported middileware file in requires file to check the middileware function
const { middileware } = require('path');
and use like this
router.get('/routename',middleware,nextfunction) //router you can choose as you like get,post,patch anything
try this out
I got this error when I omitted "req" and "res" in the function's parameters. When I added them, the error disappeared. Since I was using typescript, the first scenario looked like this:
function traceRoute(next){
console.log(routeTrace);
next();
}
Corrected to:
function traceRoute(req, res, next){
console.log(routeTrace);
next();
}
What I'm trying to do is to consume data from a firebase databse. I'm trying to achieve that from a middleware, but I couldn't do that. I'm wondering if this is the best way to do this. This is my code:
exports.userExist = function (req, res, next) {
(async () => {
try {
var query = db
.collection("users")
.where("user", "==", req.query.user)
.where("password", "==", req.query.password);
query.get().then(function (querySnapshot) {
if (querySnapshot.size > 0) {
res.json(true);
next();
} else {
res.json(false);
next();
}
});
} catch (error) {
return res.status(500).send(error);
}
})();
};
My doubt is how can I consume this method from my middleware, I'm trying to do something like that:
function verifyUser(req, res, next) {
let user= userController.findUser; //Hi have doubt about of how consume the middelware..
if(user!=null){
//The rest of the code.
}
next();
}
Is it the correct approach? or maybe I'm trying to achieve this wrong?
Couple of problems with your code.
There is no need for an async IIFE inside a middleware. The middleware function itself can be an async function.
If you call res.json(), that ENDS the request, and sends the response. You likely don't want that behavior here.
exports.userExist = async function (req, res, next) {
try {
var query = db
.collection("users")
.where("user", "==", req.query.user)
.where("password", "==", req.query.password);
const querySnapshot = await query.get()
if (querySnapshot.size > 0) {
// assume the query only returns 1 user?
req.userObj = querySnapshot.docs[0].data()
}
next();
} catch (error) {
return res.status(500).send(error);
}
};
Then, in downstream handlers:
function verifyUser(req, res, next) {
if (req.userObj) {
// previous middleware found a user
} else {
// previous middleware did not find a user
}
next();
}
Example usage:
app.use(userExist)
app.use(verifyUser)
I have created some standard middleware with some logic, and depending on the logic I need to call some 3rd party middleware.
Middleware is added using app.use(), which is where I add my custom middleware.
Once in my middleware I no longer have access to app.use(), how do I call the middleware?
Here is some code:
Any ideas ?
const customerData = (req, res, next) => {
try {
console.log('Started');
if (process.env.STORE_CUSTOMER_DATA === 'true') {
// Here is some custom middleware that doesn't belong to me
//
// Returns a function (I confirmed it) ready to be called with res,req, next
//
let externalMiddlware = logger({
custom:true
});
// Do I return it ? Call it ? Trying everything and nothing seems to work
externalMiddlware(req,res,next); // ???
} else {
// DO not call external middleware, will break out of if and then call next()
}
console.log('Finished');
next();
} catch (err) {
next(err);
}
};
module.exports = customerData;
I think this should work but if you delegate the callback to this other externalMiddlware you should not call next() in customerData use this 3rd middleware
so have you try
const customerData = (req, res, next) => {
try {
console.log('Started');
if (process.env.STORE_CUSTOMER_DATA === 'true') {
let externalMiddlware = logger({
custom:true
});
return externalMiddlware(req,res,next);
} else {
return next(); // <= next moved
}
} catch (err) {
next(err);
}
};
module.exports = customerData;