I am sending an HTTP request from angular to a node/express API. How do I get the actual error message I send from the node/express API
I am handling my exceptions well when they are thrown in the API but the moment I try to read the message in Angular I only get the name of the type of error I throw. For example, if I throw a 409 the error received by angular is just "Conflict" and does not contain the details I send. Please look at my code below.
I am sending my request as below
register(user: UserAccount) {
return this.http
.post(`${config.apiUrl}/users/register`, user)
.pipe(
map((res: HttpResponse<Response>) => {
return res;
}))
.pipe(catchError(err => this.errorHandler.handleError(err)));
}
My handle error is as below:
handleError(error: HttpErrorResponse) {
console.log(error);
if (error) {
let errMessage = '';
try {
errMessage = error.message;
} catch (error) {
errMessage = error.statusText;
}
return throwError(errMessage || error || 'Server error');
}
return throwError(error.error || error || 'Server error');
}
This how I am throwing my error when I occurs in my Node/Express API
registerUser (req, res) {
debug(chalk.blue(`*** insert user`))
userRepo
.create(req.body)
.then(user => {
debug(chalk.green(`*** Insert User ok!`))
res.status(200).json({
status: true,
error: null,
user: user
})
})
.catch(err => {
debug(chalk.red(`*** insertUser error: ${util.inspect(err)}`))
if (err['type'] && err['type'] === '409') {
res.status(409).json({
status: false,
error: err['message'],
user: null
})
} else {
res.status(400).json({
status: false,
error: err,
user: null
})
}
})
}
I want to be able to receive the json object with the information about the error but all I am getting when I access the error item is, for example, in the case of raising a 409, I only get 'Conflict'
The response data in case of error is inside the error property of HttpErrorResponse. So you may need to modify the handleError method as below.
handleError(error: HttpErrorResponse) {
console.log(error);
if (error) {
let errMessage = '';
try {
// error.error holds the json response sent from API.
errMessage = JSON.stringify(error.error);
} catch (error) {
errMessage = error.statusText;
}
return throwError(errMessage || error || 'Server error');
}
return throwError(error.error || error || 'Server error');
}
Server-side express code
registerUser(req, res) {
debug(chalk.blue(`*** insert user`))
userRepo
.create(req.body)
.then(user => {
/**
* SUCCESS TO-DO
* .
* .
* .
* .
*/
return res.send(200).json(user)
})
.catch(err => {
/**
* error handler in server
* FAILURE TO-DO
* .
* .
* .
*/
if (err['type'] && err['type'] === '409') {
res.status(409).json({
status: false,
error: 'some 409 type error',
user: null
})
} else {
res.status(400).json({
status: false,
error: 'some 400 type error',
user: null
})
}
})
}
Your angular code
handleError(error: HttpErrorResponse) {
console.log(error);
if (error) {
let errMessage = '';
try {
errMessage = error.message; // <- here is the problem
} catch (error) {
errMessage = error.statusText;
}
return throwError(errMessage || error || 'Server error');
}
return throwError(error.error || error || 'Server error');
}
Firstly, if I may rename your HttpErrorResponse object as http_error as it is too generic. However, the trick lies in reading from the error key of the http_error object, and not from the message key.
Client-side error handler(angular)
handleError(http_error: HttpErrorResponse) {
// {status: false, error: 'some 409/400 type error', user: null }
const errMessage = http_error.error;
/* TO-DO STUFF */
return throwError(errMessage || 'Server error');
}
Related
I am a webdev student trying to create an Ecommerce -API with Node and Express JS connected with MongoDB but I am getting this return in postman : "Product is not defined" in my Non_Admin Checkout function.
I don't really know what my mistakes are. Can you help me?
Here are my codes:
UserController
module.exports.checkout = async (data) => {
try{
let is_user_updated = await User.findById(data.userId).then((user) => {
user.orders.push({productId: data.productId})
return user.save().then((updated_user, error) => {
if(error){
return false
}
return true
})
})
}catch(err){return err.message}
try{
let is_product_updated = await Product.findById(data.productId).then((product) => {
product.orders.push({userId:data.userId})
return ordered.save().then((updated_product, error) => {
if(error){
return false
}
return true
})
})
}catch(err){return err.message}
if(is_user_updated && is_product_updated){
return {
message: 'User checkout is successful!'
}
}
return {
message: 'Something went wrong.'
}
}
I am recieving this error: Cannot set headers after they are sent to the client
I take that to mean the code is attempting to set the response headers more than once. This is the code, but I cant seem to find out where the headers are being set twice.
async function createTrip(request, response) {
const { vehicleId, driverId, startedAt, expectedReturn } = request.body
let driver, vehicle
//validation
await pool.query(
'SELECT * FROM Vehicle WHERE id = $1',
[vehicleId],
(error, results) => {
if (error) {
throw `ERROR::createTrip threw an error: ${error}`
}
if (results.rowCount === 0) {
return response.send({ status: 404, message: `vehicle with id ${vehicleId} does not exist.` })
} else if (results.rows[0].in_use) {
return response.send(403).send({ status: 403, message: `vehicle with id ${vehicleId} is not available for rental` })
} else {
vehicle = results.rows[0]
}
}
)
await pool.query(
'SELECT * FROM Driver WHERE id = $1',
[driverId],
(error, results) => {
if (error) {
throw new Error(`ERROR::createTrip threw an error: ${error}`)
} else if (results.rowCount === 0) {
return response.status(404).send({ status: 404, message: `driver with id ${driverId} does not exist` })
} else {
driver = results.rows[0]
}
}
)
//event
await pool.query(
'INSERT INTO Trip (status,started_at, expected_return, driver, vehicle) VALUES ($1,$2,$3,$4,$5) RETURNING *',
['active', startedAt, expectedReturn, driverId, vehicleId],
(error, results) => {
if (error) {
throw `ERROR::createTrip threw an error: ${error}`
} else {
return response.status(200).send({ id: results.rows[0].id, status: results.rows[0].status, startedAt, expectedReturn, driver, vehicle })
}
}
)
await pool.query(
'UPDATE Vehicle SET in_use = TRUE WHERE id = $1',
[vehicleId],
(error) =>{
if(error){
throw `ERROR::createTrip threw an error: ${error}`
}
}
)
}
Your createTrip function does four queries. Each of those queries results in a response potentially being called. You should do all the processing from all the queries you need to inside a try/catch block. Then, instead of sending a response when rowCount === 0 for example, throw an error, catch it in the catch block and send the response from there.
I'm validating user input and result in express and am returning 422 if the input is invalid and a 400 if result is empty. The problem is that I can't log the response object when an error occurs.
Nodejs:
if (q === '' || q.length < 3 || q.length > 150) {
console.log('invalid query');
return res.sendStatus(422).send('Search not found');
} else {
try {
// Fetch redis data
const data = await GET_ASYNC('data');
// stuff here
// Result Validation
if (!Array.isArray(data) || !data.length) {
res.status(400).send('Search not found');
} else {
// do stuff
res.status(200).send(data);
}
} catch (err) {
console.error(err);
res.sendStatus(500); // Server error
}
Now my react code:
const searchDb = useCallback(async() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
try {
axios.get(`/api/bestProduct?q=${searchValue}`,
{ cancelToken: source.token })
.then(res => {
console.log(res) // nothing shows up
const data = res.data;
setData(data)
});
} catch (err) {
if (axios.isCancel(err)) {
console.log(err.response); // nothing shows up
} else {
console.log('hello??') // nothing
return setError(`There's been a problem on our end.`)
}
}
}, [])
I've looked at other solution and tried to log the res and res.status but nothing shows up. This is what my console looks like during the error:
I have the following code:
User.getConfByID(userID)
.then((item)=>{
if(item.length == 0){
res.status(400).json({error:"NO_USER_FOUND"})
}else{
if(item[0].token == token){
if((Math.abs(Date.now() - item[0].conf_iat)) > tokenValid){
res.status(401).json({error: "TOKEN_INVALID"})
}else{
return mariaDBTemplates.updateOneRowTemplate("User_confirmation", {confirmed:1}, "user_id", userID)
}
}else{
res.status(401).json({error: "TOKEN_NOT_SAME"})
}
}
})
.then(()=>{
res.status(200).json({success: "CONFIRMED"})
})
.catch((err)=>{
res.status(500).json({error: err.message})
})
You see I have different kinds of error messages with different kinds of status codes. When I run this code, it always gives me this warning:
Error: Can't set headers after they are sent
I think this is because i don't "break" the Promise after sending a response right?. But how can I solve this? Any suggestions?
Cheerio
your problem is with your promise chain. in your first .then, you always set the response with res, but the next .then in the chain tries to set the response again. Note that not returning anything from a promise is the same as return Promise.resolve(undefined);.
here's how I would do it:
User.getConfByID(userID)
.then((item) => {
if(item.length == 0)
return { statusCode: 400, body: { error: "NO_USER_FOUND" } };
else {
if(item[0].token == token) {
if((Math.abs(Date.now() - item[0].conf_iat)) > tokenValid)
return { statusCode: 401, body: { error: "TOKEN_INVALID" } };
else {
//not sure what this returns, but it looks like this is
//what you're trying to return the 200 for
mariaDBTemplates.updateOneRowTemplate("User_confirmation", { confirmed: 1 }, "user_id", userID);
return { statusCode: 200, body: { success: "CONFIRMED" } };
}
} else
return { statusCode: 401, body: { error: "TOKEN_NOT_SAME" } };
}
})
.then((result) => {
res.status(result.statusCode).json(result.body);
})
.catch((err) => {
res.status(500).json({ error: err.message });
});
Also note that returning a value from a promise is the same as returning Promise.resolve(value);, and will continue the promise chain.
Currently I handle my authentication as following :
function login(req, res, next) {
// fetch user from the db
User.findOne(req.body)
.exec() // make query a Promise
.then((user) => {
const token = jwt.sign({ username: user.username }, config.jwtSecret);
return res.json({ token, username: user.username });
})
.catch(() => {
const err = new APIError('Authentication error', httpStatus.UNAUTHORIZED, true);
return Promise.reject(err);
});
}
I am trying to standardize my errors with a common APIError class
import httpStatus from 'http-status';
/**
* #extends Error
*/
class ExtendableError extends Error {
constructor(message, status, isPublic) {
super(message);
this.name = this.constructor.name;
this.message = message;
this.status = status;
this.isPublic = isPublic;
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
Error.captureStackTrace(this, this.constructor.name);
}
}
/**
* Class representing an API error.
* #extends ExtendableError
*/
class APIError extends ExtendableError {
/**
* Creates an API error.
* #param {string} message - Error message.
* #param {number} status - HTTP status code of error.
* #param {boolean} isPublic - Whether the message should be visible to user or not.
*/
constructor(message, status = httpStatus.INTERNAL_SERVER_ERROR, isPublic = false) {
super(message, status, isPublic);
}
}
export default APIError;
How can I test the Promise.reject in my test ?
describe('# POST /api/v1/auth/login', () => {
it('should return Authentication error', () => {
return request(app)
.post('/api/v1/auth/login')
.send(invalidUserCredentials)
// following lines are not valid anymore with Promise.reject ..
.expect(httpStatus.UNAUTHORIZED)
.then((res) => {
expect(res.body.message).to.equal('Authentication error');
});
});
You aren't handling the error/rejection at all. You need to send the error Back. I would recommend to add a error handling middleware at the end of your routes in express and then use next(err) to pass to it.
// at the end of your routes
app.use(function(err, req, res, next) {
// do whatever you want, but at least send status and message:
res.status(err.status).json({
message: err.message,
});
});
now handle the error in your routes through:
.catch(() => {
const err = new APIError('Authentication error', httpStatus.UNAUTHORIZED, true);
return next(err);
});
It's running fine now , I had an issue in my express.js error handling.. as the APIError type checking was always false... extending the es6-error package rather than the Error solve this Babel issue...
APIError.js
import ExtendableError from 'es6-error'; // solve Babel issue w isInstanceOf()
import httpStatus from 'http-status'
class MyExtendableError extends ExtendableError {
constructor(message, status, isPublic) {
super(message);
this.name = this.constructor.name;
this.message = message;
this.status = status;
this.isPublic = isPublic;
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
Error.captureStackTrace(this, this.constructor.name);
}
}
/**
* Class representing an API error.
* #extends MyExtendableError
*/
class APIError extends MyExtendableError {
constructor(message, status = httpStatus.INTERNAL_SERVER_ERROR, isPublic = false) {
super(message, status, isPublic);
}
}
export default APIError;
Express.js
// catch 404 and forward to error handler
/* istanbul ignore next */
app.use((req, res, next) => {
const err = new APIError('API not found', httpStatus.NOT_FOUND);
return next(err);
});
// if error is not an instance Of APIError, convert it.
app.use((err, req, res, next) => {
if (err instanceof expressValidation.ValidationError) {
// validation error contains errors which is an array of error each containing message[]
const unifiedErrorMessage = err.errors.map((error) => {
return error.messages.join('. ');
}).join(' and ');
const error = new APIError(unifiedErrorMessage, err.status, true);
res.status(error.status).json({
message: err.isPublic ? err.message : httpStatus[err.status],
stack: (config.env === 'test' || config.env === 'development' ) ? err.stack : {}
});
} else if (!(err instanceof APIError)) {
const apiError = new APIError(err.message, err.status, err.isPublic);
res.status(apiError.status).json({
message: err.isPublic ? err.message : httpStatus[err.status],
stack: (config.env === 'test' || config.env === 'development' ) ? err.stack : {}
});
}else {
res.status(err.status).json({
message: err.isPublic ? err.message : httpStatus[err.status],
stack: (config.env === 'test' || config.env === 'development' ) ? err.stack : {}
});
}
});
auth.route.js
import express from 'express';
import validate from 'express-validation';
import expressJwt from 'express-jwt';
import paramValidation from '../../../config/param-validation';
import authCtrl from '../controllers/auth.controller';
import config from '../../../config/config';
const router = express.Router();
/** POST /api/auth/login - Returns token if correct username and password is provided */
router.route('/login')
.post(validate(paramValidation.login), authCtrl.login);
auth.controller.js
function login(req, res, next) {
// fetch user from the db
User.findOne(req.body)
.exec() // make query a Promise
.then((user) => {
const token = jwt.sign({ username: user.username }, config.jwtSecret);
return res.json({ token, username: user.username });
})
.catch(() => {
const err = new APIError('Authentication error', httpStatus.UNAUTHORIZED, true);
return next(err);
});
}
auth.test.js
..
it('should return Authentication error', () => {
return request(app)
.post('/api/v1/auth/login')
.send(invalidUserCredentials)
.expect(httpStatus.UNAUTHORIZED)
.then((res) => {
expect(res.body.message).to.equal('Authentication error');
});
});
...