Error handling middleware thenables nodejs express - express

So I have this error handler middleware
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: "resource not found"
});
};
module.exports = {
ErrorHandler,
handleError
}
calling it in index.js
app.use((err, req, res, next) => {
handleError(err, res);
})
And I want to use it in all my methods, but I cant figure it out how to use it with catch. it does not seem very clear for me. For the 404 error works just fine, but If I'm trying to throw a 500 error, e.g. a ReferenceError I dont know how to use my function.
exports.getAll = function(req, res, next) {
User.find({})
.exec()
.then(users => {
if (users.length === 0) {
throw new ErrorHandler(404, "No users found");
} else {
res.json(users);
next;
}
})
.catch(error => {
res.status(500).send({
message: "Error finding users",
error: error
})
})
};
in .catch I want to use my error handler like I did in .then

in catch block, you can use next to go handle error like with next(err)
.catch(err=>{
let error = new ErrorHandler(500,"Error finding users");
next(error);
})

Related

Express error handler not catching an error properly

I'm working on a small express project to create and check keys. I have 2 custom errors that I throw when needed, InvalidKeyError and NotFoundError. I am using an error handling middleware:
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../errors/custom-error';
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof CustomError) {
console.log(err);
return res.status(err.statusCode).send({ errors: err.serialiseErrors() });
}
res.status(400).send({
errors: [{ message: 'Something went wrong' }]
});
};
When NotFoundError is called I get the following JSON (which is what I want):
{
"errors": [
{
"message": "Page does not exist"
}
]
}
But when I throw InvalidKeyError my app crashes, but it does show me the error message in the console. I want it to throw like my NotFoundError.
My InvalidKeyError and NotFoundError both extend my abstract class CustomError. I throw a NotFoundError in my index.ts file when a route that does not exist is being requested. I call my InvalidKeyError from a router that my index.ts uses.
This is index.ts:
import express from 'express';
import { json } from 'body-parser';
import mongoose from 'mongoose';
import { createAPIKeyRouter } from './routes/create-api-key';
import { checkAPIKeyRouter } from './routes/check-api-key';
import { errorHandler } from './middlewares/error-handler';
import { NotFoundError } from './errors/not-found-error';
const app = express();
app.use(json());
app.use(createAPIKeyRouter);
app.use(checkAPIKeyRouter);
app.all('*', (req, res) => {
throw new NotFoundError();
});
app.use(errorHandler);
const start = async () => {
try {
await mongoose.connect("mongodb://localhost:27017/coupons");
console.log("Connected to MongoDb");
} catch (err) {
console.error(err);
}
app.listen(4000, () => {
console.log("Listening on port 4000");
});
};
start();
and the code for the router that my app uses:
import express from 'express';
import { InvalidKeyError } from '../errors/invalid-api-key-error';
import { APIKey } from '../models/api-key-model';
import { Hash } from '../services/hash';
const router = express.Router();
router.get('/api/checkkey', async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
});
export { router as checkAPIKeyRouter };
You're declaring your route handler async and then throwing inside of it. That means your route handler will return a promise that gets rejected when you throw.
BUT, Express doesn't pay any attention at all to the promise that your route handler returns - in fact, Express doesn't pay any attention to any value returned by your route handler. So, when you throw and cause that returned promise to get rejected, nobody is listening for that rejection.
You will need to either use your own try/catch inside the route handler to catch your throws or you will need to wrap your route handler in a utility function that catches rejections from the promise you're returning.
Here's using your own try/catch:
router.get('/api/checkkey', async (req, res, next) => {
try {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
} catch(err) {
next(err);
}
});
And, here's using your own wrapper function (which you can share with many methods like this:
function asyncHandler(fn) {
return async function(req, res, next) {
try {
await fn(req, res, next);
} catch(err) {
next(err);
}
}
}
router.get('/api/checkkey', asyncHandler(async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
}));
And, here's how you could add methods to Express that are promise-aware: Async/Await in Express Middleware so you can just do this:
// note this is router.getP()
router.getP('/api/checkkey', async (req, res) => {
const suppliedKey = req.get('key');
const suppliedSecret = req.get('secret');
if (!suppliedKey || !suppliedSecret)
throw new InvalidKeyError('Key or secret not passed');
const storedAPIKey = await APIKey.findOne({ key: suppliedKey });
if (!storedAPIKey)
throw new InvalidKeyError('Key does not exist');
if (!Hash.compareKeys(suppliedKey, suppliedSecret, storedAPIKey.salt))
throw new InvalidKeyError('Key and secret do not correspond')
res.send('Hi there');
});

Cant catch axios error in promise, response works fine

I am trying to catch an error whilst the user tries to access a page without an authentication token.
axios.js?v=012beb2f:840 POST http://localhost:3001/api/get-user 422 (Unprocessable Entity)
Uncaught (in promise) AxiosError {message: 'Request failed with status code 422', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
router.beforeEach((to, from, next) => {
const store = useUserStore()
if(to.meta.requiresAuth)
{
try
{
const response = axios.post('/api/get-user', {}, {
headers: {
Authorization: `Bearer ${store.user.token}`
}
})
.then(response => {
console.log(response)
next()
})
}
catch(error)
{
console.log(error)
next('/login')
}
}
else
{
next()
}
})
Thats the code that makes the request to the server. If the token is correct it works fine. However incorrect token throws the error mentioned above. I would like it to redirect to /login page if token is incorrect.
This is the code on server side.
router.post('/get-user', signupValidation, (req, res, next) => {
if(
!req.headers.authorization ||
!req.headers.authorization.startsWith('Bearer') ||
!req.headers.authorization.split(' ')[1]
){
return res.status(422).json({
message: "Please provide the token",
});
}
const theToken = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(theToken, 'the-super-strong-secrect');
db.query('SELECT * FROM users where id=?', decoded.id, function (error, results, fields) {
if (error) throw error;
return res.send({ error: false, data: results[0], message: 'Fetch Successfully.' });
});
});
Change the synchronous try/catch...
try
{
somePromise.then(...)
}
catch(error)
{
console.log(error)
next('/login')
}
...to instead use the catch() provided by the promise:
const headers = { Authorization: `Bearer ${store.user.token}` };
axios.post('/api/get-user', {}, { headers })
.then(response => {
console.log(response)
next()
})
.catch(error => {
console.log(error)
next('/login')
}}
Note, also, that the OP code incorrectly assigned the axios.post promise to an unused variable called "response".
Alternatively, use the synchronous try/catch style with async/await:
router.beforeEach(async (to, from, next) => {
const store = useUserStore()
if(to.meta.requiresAuth)
{
try
{
const headers = { Authorization: `Bearer ${store.user.token}` };
const response = await axios.post('/api/get-user', {}, { headers });
console.log(response);
next();
}
catch(error)
{
console.log(error)
next('/login')
}
}
else
{
next()
}
})

Possible Unhandled Promise Rejection warning when axios call fails

Every time if there is a 504 or 404 from an API call that I make I get the warning:
Possible Unhandled Promise Rejection (id: 12):
ReferenceError: Can't find variable: rej
Here is how I've coded:
function myFunction() {
const fullURL = 'URL_THAT_I_HIT'
const data = { "SOME_ID": ID_VALUE, }
return new Promise((res, rej) => {
try {
axios.post(fullURL, data).then((response) => {
res(response.data)
}).catch((error) => {
rej(error)
alert(error)
})
} catch (error) {
rej(error)
alert(error)
}
})
}
As per my understanding, I'm handling the rej and I've even double wrapped it so that I can throw rejection. Here is my code below where I'm calling myFunction
function fetchMyFunctionCall() {
if (res === undefined) {
alert("The call rejected")
} else {
console.log(res)
}
}
It shows the error message but I still see the warning. So, how do I handle the rejection properly in fetchMyFunctionCall?
I wasn't able to repro your error for the undefined variable but I think you can simplify your code like below:
function myFunction() {
const fullURL = 'URL_THAT_I_HIT'
const data = {
"SOME_ID": ID_VALUE,
}
return axios.post(fullURL, data).then((response) => {
return response.data
});
}
myFunction().then((data) => {
console.log(data)
}).catch((error) => {
console.log(error)
})
Axios itself returns a promise so no need of returning one explicitly.

Express can't set headers after they are sent to the client

I have the following code:
router.post('/:email/addWorkflow', async function (req, res, next) {
const params = req.params;
const workflow = req.body;
const email = params.email;
User.findOne({ email: email }, function (err, user) {
if (err) {
res.status(500).send({
error: 'Error while querying database'
});
} else if (user) {
const workflows = user.workflows;
workflows.forEach(wf => {
if (wf) {
if (wf.workflowId === workflow.workflowId) {
res.status(409).send({
error: 'Workflow with that id already exists'
});
}
}
});
workflows.push(workflow);
User.updateOne({ email: email }, { $set: { workflows: workflows } }, { upsert: false }, function (err) {
if (err) {
res.status(500).send({
message: 'Error while updating database'
});
} else {
res.status(200).send({
message: 'Wf added successfully'
});
}
});
} else {
res.status(404).send({
message: 'No such user'
});
}
});
});
After I make a post with an already existing workflowId, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:485:11)
..........
at /home/petar/Documents/jsProjects/p/backend/routes/users.js:50:29
at CoreDocumentArray.forEach (<anonymous>)
at /home/petar/Documents/jsProjects/p/backend/routes/users.js:47:17
at /home/petar/Documents/jsProjects/p/backend/node_modules/mongoose/lib/model.js:4915:16
at /home/petar/Documents/jsProjects/p/backend/node_modules/mongoose/lib/model.js:4915:16
at /home/petar/Documents/jsProjects/linear-mixed-models/backend/node_modules/mongoose/lib/query.js:4380:11
[... lines matching original stack trace ...]
at processTicksAndRejections (internal/process/task_queues.js:76:11) {
code: 'ERR_HTTP_HEADERS_SENT'
Any ideas? I looked at other posts for the same error. I understand that it happens if I try to send response 2 time: res.send({...}) and res.send({...}). However, this does not happen in my case. Thanks in advance
I am not completely sure what line the error message is indicating, but the following loop is the only place I can think of a multiple response on your code
workflows.forEach(wf => {
//foreach is looping
if (wf) {
if (wf.workflowId === workflow.workflowId) {
res.status(409).send({
error: 'Workflow with that id already exists'
});
//but I don't think this guy will stop looping after the first "send()"
}
}
});

How can I prevent express js server from crashing when error occurs?

If I send a valid fetch api query to this Express server it works fine. If I send an invalid query the server crashes (ReferenceError: next is not defined). How can I change this so that if an error occurs;
the server does not crash
the client receives an error message from the server
Express server.js:
// Add a new test-resource-a
app.post('/test-resource-a', (request, response) => {
pool.query('INSERT INTO my_table SET ?', request.body, (error, result) => {
if (error) {
next(err);
}
response.status(201).send(`test-resource-a added with id: ${result.insertId}`);
});
});
//An error handling middleware
app.use(function (err, req, res, next) {
res.status(500);
res.send("Oops, something went wrong.")
});
This error is mean the next method is not define.
In your case, I think you don't need the next method.
// Add a new test-resource-a
app.post('/test-resource-a', (request, response) => {
pool.query('INSERT INTO my_table SET ?', request.body, (error, result) => {
if (error) {
response.status(400).send(err);
} else {
response.status(201).send(`test-resource-a added with id: ${result.insertId}`);
}
});
});
//An error handling middleware
app.use(function (err, req, res, next) {
res.status(500);
res.send("Oops, something went wrong.")
});