I have this small piece of code which manually get the value of checked checkboxes and emit through socket io. The thing is, if I comment out payload.permission.subscriptionPlans.push(item.value); it works fine, but if I uncomment it, socket.emit still run and I still get all the data I needed server side without any errors, but it doesnt run the callback function. Here's the code:
let payload = {
permission: {},
};
payload.permission.subscriptionPlans = [];
document.querySelectorAll(".form-add-sub-plan:checked").forEach((item) => {
console.log(item.value); // this still works
payload.permission.subscriptionPlans.push(item.value); // problem here
});
socket.emit("admin/manage-permission/create", payload, (res) => {
let addModal = bootstrap.Modal.getOrCreateInstance(document.getElementById("add-permission-modal"));
addModal.hide();
table.reload();
table.displayMessage(res.payload.type, res.payload.message);
});
// server code
socket.on("admin/manage-permission/create", async(req, done) => {
try {
if (await Permission.exists({
url: req.permission.url
})) {
return done(
new SocketPayload("error", 409, {
type: "danger",
message: "Url already exists",
})
);
}
let newPermission = new Permission(req.permission);
// console.log(newPermission);
await newPermission.save();
return done(
new SocketPayload("success", 200, {
type: "success",
message: "New permission was added",
})
);
} catch (e) {
console.log(e);
return done(
new SocketPayload("error", 500, {
type: "danger",
message: "An error occured",
})
);
}
});
Any ideas why this happens?
Edit: added server code if anyone is wondering
It turned out it's mongoose middleware stopping the socket to return a callback. It took me a while to figured out cuz the server did not throw any error.
Related
I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.
So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {
I'm returning an error response from express like below.
router.post("/", authCheck, authCheckAdmin, (req, res, next) => {
const burger = new Burger({
_id: mongoose.Types.ObjectId(),
...req.body
});
console.log(burger);
Burger.find({ name: req.body.name })
.exec()
.then(result => {
if (!result) {
burger
.save()
.then(result => {
res.status(201).json({
message: "Burger Successully Created",
burger: result
});
})
.catch(error => {
res.status(500).json(new Error('Something went wrong when saving the burger')); // <------
});
} else {
res.status(500).json(new Error('Burger already exist'));
}
});
});
This is the React code
export const addBurger = burgerData => (dispatch, getState) => {
const currentState = getState();
axios
.post("/burgers", burgerData, {
headers: {
Authorization: `Bearer ${currentState.auth.token}`
}
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error.message); <-------- Here
});
};
Below is the console preview
How can I get the message that I have passed inside express? I want to get "Burger already exist" as the error message. Is there another way to do this? Thanks
Server side fix
res.status(500).send({ error: 'Something failed!' })
According to me, error is not defined in the last else of your Burger.find method which is causing this error. This type of error's are mostly server side errors. Hope this work for you.
res.status(500).json(new Error('Burger already exiist')); // <------
if you would look at response in dev tools, you see that in this case payload is empty object. So you need directly tell the server what you need to send back. Try this if you already waiting for message key in react part:
res.status(500).json({message: 'Burger already exist'});
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.
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.