Ok so I am currently learning more node.js and decided to try out some basic middleware in a small api I created. I was wondering how I would wrap a successfull request. This is my approach.
Example Controller
exports.getTask = async function (req, res, next) {
try {
const task = await db.Task.findOne(
{
where: {
id: req.params.taskId,
userId: req.params.userId
}
});
if (task) {
req.locals.data = task;
res.status(httpStatus.OK);
next();
}
res.status(httpStatus.NOT_FOUND);
next();
} catch (err) {
next(err);
}
};
Middleware
exports.success = function(req, res, next) {
const success = res.statusCode < 400;
const successResponse = {
timestamp: new Date().toUTCString(),
success: success,
status: res.statusCode
};
if (success) {
successResponse.data = req.locals.data;
}
res.send(successResponse);
next();
};
I dont think its very good having to set req.locals.data for every requst and then calling next res.status(status) maybe I just approached the situation the wrong way?
How could you make this better?
In this case, probably using the express middleware concept (calling next()) will be an overkill.
I'd approach this by creating an abstraction for the success path. Consider something like this:
const resWithSuccess = (req, res, data) => {
res.json({
data: data,
timestamp: new Date().toUTCString(),
// success: res.statusCode < 400, // --> actually you don't need this,
// since it will always be true
// status: res.statusCode // --> or whatever else "meta" info you need
});
};
Then, as soon as you need to respond with success, go for it:
exports.getTask = async function (req, res, next) {
// .... bla bla
if (task) {
resWithSuccess(tank);
}
};
PS: ... and you can use the express middleware concept (calling next()) for the error path.
Related
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've been trying to create a checkout on my small business site and I'm trying to use a payment gateway API for it (documentation is subpar), the code below is my attempt to retrieve the paymentMedthod id, but i couldn't figure out on how to get that said id from a middleware funtion without doing this return next(body.data.id) which may cause issue because the middleware would stop and process won't proceed.
Thank you in advance, i know this is kind of dumb question but i really can't use any other API example like stripe in my country. this is the only option i have. Thanks.
e.g.
payment.js
module.exports = {
paymentMethod: function(req, res, next) {
...more lines of code here...
var options = {
...more here...
},
body: {
data: {
id: 1233445,
...more here...
}
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
//console.log(body);
return next(); //here lies the problem i'm avoiding to use return next(body.data.id) since it will stop the process and the route won't proceed to process what's inside of it.
});
},
paymentIntent: function(req, res, next) {
...more stuff here...
},
...more stuff here...
route.js
const Payment = require('../../config/payment');
//paymentMethod
router.post('/checkout', Payment.paymentIntent, Payment.paymentMethod, (req, res)=>{
//I'm confused on how to get the data coming from the middleware...
const id = Payment.paymentMethod(body.data.id);
...more stuff...
var options = {
...stuff..
...stuff..+id,
...stuff...
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body); //this should display the id coming from the middle ware
});
})
All middlewares share the same req and res objects, so you just have to assign your variable there.
payment.js
module.exports = {
paymentMethod: function (req, res, next) {
// ...
request(options, function (error, response, body) {
if (error) throw new Error(error);
req.paymentMethodId = body.data.id; // assign the ID to the req object
return next();
});
},
}
route.js
router.post('/checkout', Payment.paymentIntent, Payment.paymentMethod, (req, res) => {
const id = Payment.paymentMethod(req.paymentMethodId); // get the ID from the req object
// ...
})
Calling next(body.data.id) by convention actually triggers error handling, so the request will go to the error handling middleware (if you have one).
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)
Test
it('should fail trying to GET bookmarks with false user id',async () => {
try {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
} catch (e) {
console.log(e); //it doesn't reach this point
expect(e.httpStatus).to.equal(HttpStatus.UNAUTHORIZED);
}
});
The relevant part of the method under test:
/* GET bookmark of user */
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
userIdTokenValidator.validateUserIdInToken(request);
...
}));
where wrapAsync makes sure the error is passed to the custom error handler:
let wrapAsync = function (fn) {
return function(req, res, next) {
// Make sure to `.catch()` any errors and pass them along to the `next()`
// middleware in the chain, in this case the error handler.
fn(req, res, next).catch(next);
};
}
The validateUserIdInToken method which causes the method under test to throw an exception:
const AppError = require('../models/error');
const HttpStatus = require('http-status-codes');
let validateUserIdInToken = function (request) {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
throw new AppError(HttpStatus.UNAUTHORIZED, 'Unauthorized', ['the userId does not match the subject in the access token']);
}
}
module.exports.validateUserIdInToken = validateUserIdInToken;
and the custom error handler in the root middleware:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
if(err instanceof AppError) { //execution lands here as expected and the test stops...
res.status(err.httpStatus);
return res.send(err);
} else {
res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR);
res.send({
message: err.message,
error: {}
});
}
});
I think you may be approaching this incorrectly. Invalid auth should not raise errors in the app - it's not an error really, is a validation issue.
If the auth fails, simply send the relevant http error code - 401 back to the client.
res.send(HttpStatus.UNAUTHORIZED, 'a message if you want'); // 401
In your route handler:
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
return response.send(HttpStatus.UNAUTHORIZED);
}
...
}));
In your test, check the for status 401:
chai.request(server)
.get('/false_user_id/bookmarks')
.end((err, result) => {
if (err) {
return callback(err);
}
result.should.have.status(401);
});
Thanks to #laggingreflex's comment I missed debugging that the response actually returned with the expected status and error message
The adjusted test case now looks like this:
it('should fail trying to GET bookmarks with false user id',async () => {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
expect(response.status).to.equal(HttpStatus.UNAUTHORIZED);
});
I have myRoute.js with a route (GET) defined and I want to call an api endpoint from another route (api.js), and I'm not sure what the right way to do this is. The api.js route is working properly (image and code below).
api.js
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
works as expected:
myRoute.js
I would like when a user goes to localhost:3000/USER_ID that the route definition gets information from the api. Psuedo code below (someFunction).
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
let fromApi = someFunction(`localhost:3000/getAllGroups/${uid}`); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Not sure if i understand you correct but anyway i will try to help. So you have an api like
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
If you would like to reuse it you can extract a function from the code above like so:
async function getAllGroupsByUserId(uid){
const result = [];
try{
const data = await db.getAllGroups(uid);
for (i in data) {
result.push(data[i].groupname);
};
return result;
}
catch(e) {
return e;
}
}
And then reuse it in your api & anywhere you want:
router.get('/getGroups/:uid', async function(req, res, next) {
const uid = req.params.uid;
const groups = await getAllGroupsByUserId(uid);
res.status(200).send(groups);
})
Same you can do in your another route:
router.get('/:uid', async function(req, res, next) {
const uid = req.params.uid;
const fromApi = await getAllGroupsByUserId(uid); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Seems like pretty clear :)
I would use fetch for this. You can replace someFunction with fetch, and then put the res.render code in a .then(). So, you would get this:
const fetch = require("node-fetch");
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}').then(res => res.json()).then(function(data) {
returned = data.json();
console.log(returned); //expecting array
res.render('./personal/index.jade', {JSON.stringify(returned)});
});
});
A more robust way with error handling would be to write something like this:
const fetch = require("node-fetch");
function handleErrors(response) {
if(!response.ok) {
throw new Error("Request failed " + response.statusText);
}
return response;
}
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}')
.then(handleErrors)
.then(res => res.json())
.then(function(data) {
console.log(data) ; //expecting array
res.render('./personal/index.jade', {JSON.stringify(data)});
})
.catch(function(err) {
// handle the error here
})
});
The ideal way would be to abstract your code into a method so you aren't calling yourself, as The Reason said. However, if you really want to call yourself, this will work.