Handling exceptions in Sails.js - javascript

I'm using Sails.js to develop a REST API server.
For ease of use and for abstraction sake I would like to throw exceptions inside of my controllers, e.g.:
// api/controllers/TempController.js
module.exports = {
index: function(request, response) {
throw new NotFoundException('Specific user is not found.');
throw new AccessDeniedException('You have no permissions to access this resource.');
throw new SomeOtherException('Something went wrong.');
}
};
How do I catch those exceptions automatically (on a global level) and transform them into a valid JSON response? e.g.:
{
"success": false,
"exception": {
"type": "NotFoundException",
"message": "Specific user is not found."
}
}
Is it the best possible approach to use built-in serverError response in order to handle such exceptions? Or is it better to create some custom middleware? If so, could you provide a simple example?

The unhandled exceptions are passed to the default response in the api/responses/serverError.js as a first argument data.
Here's an example of how such exceptions can be handled:
var Exception = require('../exceptions/Exception.js');
module.exports = function serverError (data, options) {
var request = this.req;
var response = this.res;
var sails = request._sails;
// Logging error to the console.
if (data !== undefined) {
sails.log.error('Sending 500 ("Server Error") response: \n', String(data));
} else {
sails.log.error('Sending empty 500 ("Server Error") response');
}
response.status(500);
if (data instanceof Exception) {
return response.json({
success: false,
exception: {
type: data.constructor.name,
message: data.message
}
});
} else {
return response.json(data);
}
};
When exception is thrown in the controller:
// api/controllers/TempController.js
var NotFoundException = require('../exceptions/NotFoundException.js');
module.exports = {
index: function(request, response) {
throw new NotFoundException('Specific user is not found.');
}
};
This will output the following JSON:
{
"success": false,
"exception": {
"type": "NotFoundException",
"message": "Specific user is not found."
}
}

Related

I want to throw custom Error with status code using Throw function in response body

If I use Throw new Error("User not found"), then it gives in response
{status:false,message:"User Not Found"}
But with status code 500, And I need Status 400 in Postman
custom Error using throw function
but if we use res.status(500).send({ status: false, message: "User not found" })
then it gives status code 400, And I need Status 400 in Postman . So, I need same status code in postman only.This is the problem. Tyler2P and Abin Bala , I followed your code but I am unable to get desired status code in postman status.
You can create a custom error class as below and throw it with appropriate message and httpCode. You can also add more properties. Then you can catch the custom error object using the catch block and get the required values.
class CustomError extends Error {
name;
httpCode;
message;
constructor(name,httpCode, message){
super(message);
this.name = name;
this.httpCode = httpCode;
this.message = message;
}
}
errorThrowingFunction.js:
//import the custom error class in the module that you //are going to use it.
errorThrowingFunction = () => {
const authToken = myCache.get("token");
if (!authToken) {
throw new CustomError('Error',401,'token missing');
} else {
return authToken;
}
}
index.js:
handler = () => {
try {
errorThrowingFunction();
} catch(error){
const response = {
statusCode: error.httpCode,
body: JSON.Stringify(error.message),
isBase64Encoded: false,
//add other headers
}
return response;
//if you are using this in rest service, the use below line
//return res.status(error.httpCode)send(response);
}
}

TypeError: res.status is not a function

I'm making a function that permits me to upload a picture to imgur in my express api (nodejs),
i'm encoutering an error when calling a function returning a promise:
TypeError: res.status is not a function
at uploadpicture.then
This is my code:
Where error is raised:
router.post('/upload', (req, res, next)=> {
var busboy = new Busboy({headers: req.headers});
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
if(fieldname == 'image') {
// the buffer
file.fileRead = [];
file.on('data', function(data) {
// add to the buffer as data comes in
this.fileRead.push(data);
});
file.on('end', function() {
// create a new stream with our buffered data
var finalBuffer = Buffer.concat(this.fileRead);
upload = uploadpicture(finalBuffer).then((res)=>{ //success request
console.log(res);
res.status(200).json({success: true, message: "Successfully uploaded !", url: res.data.link});
},(err)=>{ //error
res.status(500).json({success: false, message: "Error happenned while uploading !"});
}).catch((error)=>{
console.log(error);
res.status(500).json({success: false, message: "Error happenned while uploading !"});
});
})
}
});
busboy.on('finish', function() {
//busboy finished
});
req.pipe(busboy);
});
And the function :
function uploadpicture(stream){ //get picture stream
return new Promise((resolve, reject)=>{
var options = {
uri: 'https://api.imgur.com/3/image',
method: 'POST',
headers: {
//'Authorization': 'Client-ID ' + config.client_id_imgur // put client id here
},
formData: {
image: stream,
type: 'file'
},
auth: {
bearer: config.access_token_imgur,
}
};
request(options)
.then((parsedBody)=> {
resolve(parsedBody);
})
.catch((err)=> {
console.log(err);
reject(err.toString())
});
});
}
The code works perfectly, but i don't know why suddendly this error happened,
i tried to :
change arrow functions to function(){}
Add next to the route parameters
Nothing worked, Thanks for your help
The accepted answer directly addresses the OP's problem, but I post another solution since you can also encounter this error in other places.
When you have:
api.use((error: ErrorRequestHandler, request: ExpressRequest, response: ExpressResponse) => {
response.status(500).end() // response.status is not a function
})
Because the error handling route must accept 4 arguments for express to identify it as an error middleware.
api.use((error: ErrorRequestHandler, request: ExpressRequest, response: ExpressResponse, next: NextFunction) => {
response.status(500).end()
})
Just adding the next function (or whatever argument you're missing) will fix it.
https://github.com/visionmedia/supertest/issues/416#issuecomment-514508137
At this point:
upload = uploadpicture(finalBuffer).then((res)=>{ //success request
the resis the result of promise uploadpicture function (that is the parsedBody), not the res from the express route. So indeed, it has no status function. Try change the then callback name like:
upload = uploadpicture(finalBuffer).then((otherName)=>{ //success request
You are getting this error:
TypeError: res.status is not a function
Because the order should be (err, res, req, next) not (req, res, err, next),
example below
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode)
res.json({
message : err.message,
stack :process.env.NODE_ENV === 'production' ? null : err.stack,
})
}
Order of parameters really matters i had error in below code
const getImagesByBrand = async (res) => {
try {
const images = await Image.find();
res.status(200).json(images);
} catch (error) {
res.status(500).json(error);
}
};
I was not giving req as parameter and that was the reason for error i just add req,res and it worked
If you are using the async/await method:
const notifications = await notifications.aggregate({...})
if(notifications){
return res.status(200).json({ data: notifications })
}else{
return res.status(404).json({ message: 'No notifications found'})
}
Make sure that you are including your return statements. Not including a return statement will cause this. Something else that I was doing is I had JSON instead of json, which will most definitely throw an error.

How to handle error and send response in GraphQL

I was starting with GraphQL and I was unable to comprehend how we can throw errors in GraphQL
I went through a couple of articles on the web but almost all of them use Apollo and the code-structure looks very different than how I work.
Consider this piece of code, here where I am making a mutation, now how can send a response message with error and change headers status message in case of error?
AddNewPersonalInfo: {
type: userDashboardType,
args: {
parameter: {
type: userCreationlInputType
}
},
resolve: async (parent, args, context) => {
args.parameter.userId = context.req.headers.userId
//Check if user info already exsist
const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
if (checkIfUserInformationExsist[0]) {
const error = {
code: 403,
message: 'User info Already exsist'
}
throw new Error(error)
} else {
try {
const addLinkedinUser = await insertIntheTable('personal', payload)
return true
} catch (err) {
console.error(err)
throw new Error(err)
}
}
}
}
What I have faced in one of my projects, it is hard to set the status code of the response. So, I made some custom error response to identify correct statusCode using express-graphql
Below is the example (What I have used in one of my projects):
--------app.js file--------
const graphqlHTTP = require('express-graphql')
app.use('/graphql', (req, res) => {
graphqlHTTP({
schema: GraphQLSchema, //A GraphQLSchema instance from GraphQL.js. A schema must be provided.
graphiql: true,
context: { req },
formatError: (err) => {
const error = getErrorCode(err.message)
return ({ message: error.message, statusCode: error.statusCode })
}
})(req, res)
})
--------getErrorCode function implementation--------
const { errorType } = require('../constants')
const getErrorCode = errorName => {
return errorType[errorName]
}
module.exports = getErrorCode
--------Constant.js file--------
exports.errorName = {
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
SERVER_ERROR: 'SERVER_ERROR'
}
exports.errorType = {
USER_ALREADY_EXISTS: {
message: 'User is already exists.',
statusCode: 403
},
SERVER_ERROR: {
message: 'Server error.',
statusCode: 500
}
}
Now, we are ready to use our setup.
From your query or mutation, you need to require constant file and return custom error:
const { errorName } = require('../constant')
AddNewPersonalInfo: {
type: userDashboardType,
args: {
parameter: {
type: userCreationlInputType
}
},
resolve: async (parent, args, context) => {
args.parameter.userId = context.req.headers.userId
//Check if user info already exsist
const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
if (checkIfUserInformationExsist[0]) {
const error = {
code: 403,
message: 'User info Already exsist'
}
throw new Error(errorName.USER_ALREADY_EXISTS) // Here you can use error from constatnt file
} else {
try {
const addLinkedinUser = await insertIntheTable('personal', payload)
return true
} catch (err) {
console.error(err)
throw new Error(errorName.SERVER_ERROR) // Here you can use error from constatnt file
}
}
}
}
--------Error response--------
{
error: [{
"statusCode": 403,
"message": "User is already exists."
}],
data: null
}
We just need to write custom error handling from FS side too.
Note:- formatError: is deprecated and replaced by customFormatErrorFn. It will be removed in version 1.0.0. You can refer customFormatErrorFn.
graphql should be an application level layer that shouldn't (see last paragraph why shouldn't and not doesn't) require http to work. Although in 99% of cases it runs on top of http, because of how convenient it is to do so, graphql is itself a layer 7 protocol.
What does that mean in your case? Well, it means you should not mix concepts from HTTP/REST with concepts from graphql and focus on the latter. The headers error code is a HTTP/REST concept, graphql sends errors in the errors field of the response and the nodejs implementation already catches all your errors and adds them to the list. The HTTP status will be always 200, and your clients shouldn't care and consume your graphql api and not a mix of REST with graphql.
That being said, there are couple of things that REST over HTTP does better. So people, including the developers of Apollo, kinda mixed concepts too, mainly because the graphql standard is not complete (aka, it doesn't have a standard/rule for solving all the problems you might encounter while building an API), so people improvised. I wouldn't recommend graphql yet for any serious project.
Reference
You can specify an error function inside graphqlHTTP like this:
app.use("/graphql", graphqlHTTP({
schema,
graphiql: true,
customFormatErrorFn: err => {
try {
err.details = JSON.parse(err.message);
err.message = Array.isArray(err.details.error) ? err.details.error.join(",") : err.details.error;
return err;
} catch {
return err;
}
}
}));
where err.message might contain a JSON object or a string.
you can use those function to generate specific client and server error functions:
const clientError = error => new Error(JSON.stringify({
success: false,
code: 400,
error
}));
const serverError = ({ name, message, stack }) => new Error(JSON.stringify({
success: false,
error: "Server Error",
code: 500,
name,
message,
stack
}));
const userValidationError = err => {
if (err.name === "ValidationError") return clientError(Object.values(err.errors).map(({ message }) => message));
return serverError(err);
}
module.exports = {
clientError,
serverError,
userValidationError
};
userValidationError function is useful if you have a mongodb validation error.
so that you would use it inside resolve function like this:
try {
const createdDocument = await MongooseDoc.create(data);
return createdDocument;
} catch (err) {
throw userValidationError(err);
}
the response would be
{
"errors": [
{
"message": "error details 1,error details 2",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"document"
],
"details": {
"success": false,
"code": 400,
"error": [
"error details 1",
"error details 2"
]
}
}
],
"data": {
"document": null
}
}
if you want to throw a clientError you throw it outside try catch.
Hopefully this code helps someone send dynamic error messages in graphql.

Unknown authentication strategy: hapi-auth-bearer-simple

I am trying to use hapi-auth-bearer-simple module to enable bearer token on my app. However, I am getting the error shown in the title.
I am trying to implement this module to enable token authorisation in my app But I am getting error mentioned below
e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\node_modules\hoek\lib\index.js:723
I have a route file
module.exports = [
{
method: 'GET',
path: '/api/{_id?}',
handler: function (request, reply) {
Controller.control.get(request.params, function (err, success) {
console.log(request.params);
if (err) {
reply(unifunc.sendError(err));
} else {
reply(unifunc.sendSuccess(SuccessMsg,success)).code(200);
}
});
},
config: {
description: 'desc',
tags: ['api', 'oV'],
validate: {
headers: unifunc.authorizationHeaderObj,
params: {
o_id: Joi.string().required().trim(),
_id: Joi.string().optional().trim()
},
failAction: unifunc.failActionFunction
},
auth: {
strategy: 'bearer',
scope: ['admin', 'user-{params.id}']
},
plugins: {
'hapi-swagger': {
responseMessages: msgs
}](url)
and a controller file in which I mentioned strategy
var bearerSimple= require('hapi-auth-bearer-simple')
authorization = Authorization.auth; // This plugin has the logic to validate the token and return the error in case it fails and I am passing accesstoken as parameter in a function in that file
var getV = function(server, params, callbackRoute){
server.register(
[{
register: bearerSimple
}], function(err){
if(err){
console.log("Failed to log the plugin",err);
throw err;
}
server.auth.strategy('bearer', 'bearerAuth', {
authorization : authorization
});
});
console.log(params);
async.series([
function(cb){}
]}
complete error message is:
Error: Unknown authentication strategy: bearer in path: /api/orders/{order_id}/vehicles/{_id?}
at Object.exports.assert (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\node_modules\hoek\lib\index.js:723:11)
at e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\auth.js:152:14
at Array.forEach (native)
at internals.Auth._setupRoute (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\auth.js:149:24)
at new module.exports.internals.Route (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\route.js:142:47)
at internals.Connection._addRoute (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\connection.js:375:17)
at internals.Connection._route (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\connection.js:367:18)
at wrappedRoute [as _route] (e:\python_training\Training\Node\Test\Project\Backend\node_modules\newrelic\lib\instrumentation\hapi.js:222:29)
at internals.Plugin._apply (e:\python_training\Training\Node\Test\Project\Backend\node_modules\hapi\lib\plugin.js:460:14)
at internals.Plugin.route
Is there any way I can resolve this issue?
Edit:
I modified server.js file and removed the strategy from controller file
I placed strategy in server.js
var validationFunction = Authorization.auth;
console.log(validationFunction);
server.register(
[{
register: bearerSimple
}], function(err){
if(err){
console.log("Failed to log the plugin",err);
throw err;
}
server.auth.strategy('bearer', 'bearerAuth', {
validationFunction : validationFunction
});
});
and in Authorization file looks like this
function rauth(accessToken, cb) {
var criteria = {accessToken: accessToken};
var projection = {};
var options = {limit: 1};
Service.AdminService.getadmin(criteria, projection, options, function (err, data) {
if (err) {
cb(err);
} else if (data && data.length > 0 && data[0]._id) {
console.log(data);
console.log(data.length);
adminId = data[0]._id;
cb()
} else {
cb(UniversalFunctions.CONFIG.APP_CONSTANTS.STATUS_MSG.ERROR.INVALID_ACCESS_TOKEN);
}
});
Now I am getting this error:
Error: options.validateFunc must be a valid function in bearerAuthentication scheme
I have been breaking my head over this problem from days. Could anyone suggest what could be the problem here?
The only problem I found was with the parameters of callback function passed in validateFunction but I can't remove the parameters as those parameters are being defined in another function called getadmin. Could anyone suggest a workaround for this?
Solved in this issue https://github.com/Salesflare/hapi-auth-bearer-simple/issues/69.
The problems were a typo and needed to pass more info back on a successful authorization.

JavaScript Object parameters changing when reading them over network

For some reason that I cannot seem to wrap my brain around, whenever I send an Object over the network, its parameters change from when I send the Object over the network from the server to when I read it back on the client.
Here's the scenario:
I'm testing an API to delete your user account from the database. You send over your authToken and username parameters in a specially formatted Object, the server queries the database, and if found, your user account is deleted. When everything works perfectly, your account, the server sends this string back to you:
{ header: { type: 'success' }, body: {} }
That's known as a plain SuccessEnvelope. However, when I read the returned string from the server, I get this:
{ header: { type: 'error' }, body: {} }
Now, it seems that the type parameter is being changed somewhere, but I have no idea where! Below you'll find the relevant code which handles all of these requests:
server.js
server = net.createServer(function(socket) {
socket.on("data", function(data) {
try {
// Accept Envelope from client
var input = new Envelope(data.toString());
if (input.verify()) { // Make sure client Envelope has correct data
switch (input.header.type) { // Route commands based on Envelope "type"
case "user":
userHandler.parseEnvelope(input, function(err, res) {
if (err) {
socket.write(errors.parseError(err).toString());
} else {
socket.write(res.toString());
}
});
break;
default:
socket.write(new Envelope().toString());
break;
}
} else {
socket.write(errors.parseError(
new errors.MissingHeaderDataError()).toString());
}
} catch (err) {
socket.write(errors.parseError(err).toString());
}
});
});
user-handler.js
// removing some of the unnecessary stuff
case "delete":
// Make sure user provided all necessary information.
if (!header.token || !body.username) {
return callback(new errors.MissingEnvelopeDataError("Missing the 'token' and/or
'username' parameter(s) in header and/or body, respectively"));
} else {
// Delete the user's account. So sad to see them go... :'(
User.findOne({"token": header.token, "username": body.username}, "+token +_id",
function(err, user) {
if (err) {
return callback(err);
} else {
user.remove(function(err) {
if (err) {
return callback(err);
} else {
// Everything went according to plan. Return a SuccessEnvelope to the user.
callback(new SuccessEnvelope());
}
});
}
});
}
break;
Can anyone see what I did wrong? Any help would be fantastic. Thanks in advance!
EDIT: It's probably also a good idea to include my test cases...
deleteUser = {
"header": {
"type": "user",
"method": "delete",
"token": ""
},
"body": {
"username": ""
}
}
describe("Delete user", function() {
it("should return a MissingEnvelopeDataError when deleting a user without a token or
username", function(done) {
connection.write(JSON.stringify(deleteUser));
connection.on("data", function(res) {
res = JSON.parse(res.toString());
res.header.should.have.property("type", "error");
res.header.should.have.property("errorType", "MissingEnvelopeDataError");
done();
});
});
it("should return a SuccessEnvelope when deleting a user", function(done) {
deleteUser.header.token = user.token;
deleteUser.body.username = user.username;
connection.write(JSON.stringify(deleteUser));
connection.on("data", function(res) {
res = JSON.parse(res.toString());
console.dir(res);
res.header.should.have.property("type", "success");
// res.body.should.be.empty
(function() {
if (Object.keys(res.body).length == 0) {
return true;
} else {
return false;
}
})();
done();
});
});
});
The user variable you see here is just a simple object, which stores all of the user info.
As usual, I'm an idiot and don't follow my own API.
The callback function that's being passed takes two parameters: err and response. I was forgetting to pass null as my error, so the code was doing exactly what it should have been doing. DOH! Thanks for the responses, everyone!

Categories