Can not get data from controllers using Express NodeJS - node.js

I need to get data from two controllers, but when I call them, returns this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Plus, I want to know if I must write all the logic inside the try section. This is my code:
[UPDATED from Shariful Islam Mubin's reply]
DataMatching: controller that matches user tags and activities
const { response } = require('express');
const { userData } = require("./userData");
const { getActivities } = require("./activities");
const DataMatching = async (req, res = response) => {
let userTags = [];
let activities = [];
let state = false;
let topActivities= {};
try {
userTags = await userData(req, res);
activities = await getActivities(req, res);
if (userTags.data.tags && activities){
userTags = userTags.data.tags;
state = true;
//doSomething
}
return res.json({
ok: state,
response: topActivities
});
}
catch (error) {
return res.status(500).json({
ok: state,
response: error
});
}
}
module.exports = {
DataMatching
}
userData: controller that gets user data
const { response } = require('express');
const axios = require('axios');
const userData = async (req, res = response) => {
try {
let res_user = await axios.get(somePath, someConfig)
if (res_user.data.success === true) {
return res.json({
ok: true,
response: res_user.data
})
} else {
return res.status(500).json({
ok: false,
error: res_user.data.message
})
}
} catch (error) {
return res.status(500).json({
ok: true,
response: res_user
})
}
}
module.exports = {
userData
}
getActivities: controller that gets activities data
const { response } = require('express');
const Activity = require('../models/activity');
const getActivities = async (req, res = response) => {
const activities = await Activity.find().populate('tags')
try {
return res.json({
ok: true,
activities
});
}
catch (error) {
return res.status(500).json({
ok: false,
error: error
});
}
}

As soon as you call res object, you must have to return it. Because, res or response object sends data to the client. And you cannot return anything after sending a response to the client.
So, you've to write
return res.json({/*.. YOUR_CODE_GOES_HERE ..*/})
and,
return res.status(500).json({/*.. YOUR_CODE_GOES_HERE ..*/})
I also noticed you called other 2 functions which are also responsible for responding to the client. You shouldn't do that.
Try to return response only from the requested controller, which may be DataMatching function in this case.
Youu can wrap all code inside DataMatching function in a try-catch block to handle any exception that occurs in the function.

Related

Express: res.send() returns an empty object

I have a setup that uses a MySQL server node and express, I have created an update statement on a SQL file and got it to work with the node via patch request on the postman.
It seems to work and it updates the server, however, when I try to get the recently updated object or even a success message, I get an empty object or nothing, even though the data has been updated.
Can anyone help me get some sort of response on postman?
update.sql
UPDATE [dbo].[RetailerThemes]
SET [Name] = #name,
[PrimaryColourPalette_main] = #primaryColourPalette_main
WHERE [UniqueThemedPageName] = #UniqueThemedPageName
router.js
router.patch("/update", internal, async (req, res) => {
try {
//do the update
const updateRetailer =
await retailerController.updateRetailerConfigByName(req, res)
console.log(`Update Retailer Routes: ${updateRetailer}`)
res.status(200).send(
{updateRetailer}
);
} catch (error) {
console.log(error);
}
});
controller.js
const updateRetailerConfigByName = async (req, res) => {
try {
// Credentials from Request
let retailername = req.retailername;
// Data from Repository
const thisRetailerConfig = await retailerRep.updateRetailerConfigDetails(
retailername
);
console.log( `thisRetailerConfig: ${thisRetailerConfig}`)
} catch (error) {
console.log(error)
}
};
repo.js
async function updateRetailerConfigDetails(retailername) {
try {
//RetailerTheme
const sqlcommanda = await tsql.loadSql(
"tsql",
//"GetRetailerThemeByThemedPageName.sql"
"UpdateRetailer.sql"
);
let pool = await sql.connect(sqlConfig);
const themes = await pool
.request()
.input("name", sql.VarChar(150), "b") // change 80700 to variable
.input("UniqueThemedPageName", sql.VarChar(150), retailername)
.input("primaryColourPalette_main", sql.VarChar(9), "#c00")
.query(sqlcommanda)
;
if (themes.rowsAffected != 1) {
console.log("Retailer not found for ", retailername, sqlcommanda);
return { isFound: false };
}
const theme = themes.recordset[0];
console.log(`The Theme: ${theme}`)
return theme;
} catch (error) {
console.log(error.message);
return {};
}
}
Here is a screenshot of what I get on the postman, the update works but the response is an empty object.

When using forEach in a Cloud Function, I can't make .sendToDevice() method work

I can send messages to the iOS device using the second function shown below.
I get the document id in the collection name "users" which is at the first level and send the message using the token stored in the tokens subcollection therefore admin.firestore().collection('users').doc(userId).collection('tokens').
I have to change the way the function looks for the user. Rather than relying on the document id of the user, I now need a query in order to find the user. Being a query, unless I'm wrong, I'm forced to use forEach in order to send the message to the user. The function now looks as shown immediately below. In essence, once I know I have the user that needs to receive the message, I'm using the original function format to send the message but the message is never sent. All I see in the logs is Firebase messaging error and I have yet to figure out where the mistake is.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where('token', '==', token).get();
if (query.empty) {
console.log('No matching documents.');
return;
}
query.forEach(doc => {
const tokens = usersRef.doc(doc.id).collection('tokens');
tokens.get()
.then(snapshot => {
const results = [];
snapshot.forEach(doc => {
const fcmToken = doc.data().fcmToken
console.log("fcmToken =>", fcmToken);
results.push(fcmToken);
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
.catch(err => {
console.log("Error getting documents", err);
});
});
});
This is the original function which I used when using the document id.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body
const orderTotal = body.total
const orderId = String(body.id)
const tokenReference = admin.firestore().collection('users').doc(userId).collection('tokens')
const tokenSnapshots = await tokenReference.get()
const results = []
tokenSnapshots.forEach(tokenSnapshot => {
const fcmToken = tokenSnapshot.data().fcmToken
results.push(fcmToken)
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
Screenshot of the error:
The onRequest() function terminates when you return a response. You are using sendToDevice() after response.send(). Also make sure you are handling all the promises correctly. Try refactoring the using async-await syntax as shown below:
exports.sendMessage = functions.https.onRequest(async (res, response) => {
try {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where("token", "==", "TOKEN").get();
if (query.empty) {
console.log("No matching documents.");
return;
}
// Query tokens of all users at once
const tokenSnapshots = await Promise.all(
query.docs.map((user) => usersRef.doc(user.id).collection("tokens").get())
);
// Array of all fcmTokens
const results = tokenSnapshots.reduce((acc, snapshot) => {
acc = [...acc, ...snapshot.docs.map((doc) => doc.data().fcmToken)];
return acc;
}, []);
const payload = { ...FCM_PAYLOAD };
const fcmResponse = await getMessaging().sendToDevice(results, payload);
console.log("Successfully sent message:", fcmResponse);
response.send([results, , payload]);
} catch (error) {
console.log(error);
response.json({ error: "An error occured" });
}
});
Also checkout Terminating HTTP Cloud Functions.
After days of working on this, it turns out there wasn't anything wrong with the function. I don't know how VPN works but the fact that I had it enabled on my iPhone was the reason I wasn't getting the notification.
I paused the VPN and the notification was received.

Unhandled Rejection (TypeError): dispatch is not a function error when pulling data from backend

I am trying to pull data from my Postgresql tables to display as form fields in my React front-end. I am getting the below error message for each of the five fields (I have only included the example code for one as they are all the same).
The error is flagging in the action page and I can't see where I am going wrong as I have used similar elsewhere in my application and it worked correctly (although I am relatively new to JS / React).
error message
1 of 5 errors on the page
Unhandled Rejection (TypeError): dispatch is not a function
pullCurrentFields/<
sustainable-scuba-web-app/src/actions/formData.action.js:23
Front-end
formData.action.js
import {
diveCurrentFields,
diveRegionFields,
diveSpotFields,
diveTypeFields,
diveVisibilityFields
} from "../services/formFields.service";
import { failed_data_load, set_message, data_load_successful } from "./types.action";
export const pullCurrentFields = (dispatch) => {
return diveCurrentFields().then(
(response) => {
return response;
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: failed_data_load,
});
dispatch({
type: set_message,
payload: message,
});
return Promise.reject();
}
);
};
formFields.service.js
import axios from 'axios';
const API_URL = "http://localhost:5002/api/formfields/";
// posts register details to backend
export const diveCurrentFields = (currentList) => {
return axios.get(API_URL + "divecurrentfields", {
//currentList,
});
};
Backend
formFields.routes.js
const { Router } = require("express");
const controller = require("../../controllers/formFields.controller");
module.exports = function(app) {
// return list fields for dive log form
app.get('/api/formfields/divecurrentfields', controller.allCurrentFields());
};
formFields.controller.js
const db = require("../server/models/");
const currents = db.currentLevel;
const diveRegions = db.diveRegion;
const diveTypes = db.diveType;
const visibilityLevels = db.visibilityLevel;
const diveSpots = db.diveSpot;
exports.allCurrentFields = (req, res) => {
currents.findAll({})
.then((currents) => {
const currentList = [];
for (i = 0; i < currents.length; i++) {
currentList.push(currents[i].dataValues);
}
if (!currents) {
return res.status(404).send({ message: "No current levels stored" });
}
res.status(200).send({
data: currentList,
});
})
.catch((err) => {
res.status(500).send({ message: err.message });
});
};

What is causing an Unhandled Promise Rejection: undefined is not an object (evalutating '_context.t0.response.data')?

I keep getting an Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating '_context.t0.response.data'). After doing some digging, it seems like my error is coming from this part of my code. It's the authUser function specifically that seems to be causing the problem
import { addError, removeError } from './error';
import { SET_CURRENT_USER } from '../actionTypes';
import api from '../../services/api';
export const setCurrentUser = user => ({
type: SET_CURRENT_USER,
user
});
export const setToken = token => {
api.setToken(token);
};
export const authUser = (path, data) =>
{
return async dispatch => {
try {
const {token, ...user} = await api.call('post', `auth/${path}`, data);
AsyncStorage.setItem('jwtToken', token);
api.setToken(token);
dispatch(setCurrentUser(user));
dispatch(removeError());
} catch (err) {
const error = err.response.data;
dispatch(addError(error.message));
}
}
};
actionTypes.js
export const SET_CURRENT_USER = 'SET_CURRENT_USER'
api.js
import axios from 'axios';
const host = 'http://localhost:4000/api';
export const setToken = token => {
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
} else {
delete axios.defaults.headers.common['Authorization'];
}
};
export const call = async (method, path, data) => {
const response = await axios[method](`${host}/${path}`, data);
return response.data;
};
export default { setToken, call };
error.js
import {ADD_ERROR, REMOVE_ERROR} from '../actionTypes';
export const addError = error => ({
type: ADD_ERROR,
error
});
export const removeError = () => ({
type: REMOVE_ERROR
});
Error:
Possible Unhandled Promise Rejection (id: 4):
TypeError: undefined is not an object (evaluating '_context.t0.response.data')
_callee$#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:107239:43
tryCatch#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26927:23
invoke#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27103:32
tryCatch#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26927:23
invoke#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27003:30
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27015:21
tryCallOne#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28865:16
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28966:27
_callTimer#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32405:17
_callImmediatesPass#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32441:19
callImmediates#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32659:33
callImmediates#[native code]
__callImmediates#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2719:35
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2505:34
__guard#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2702:15
flushedQueue#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2504:21
flushedQueue#[native code]
callFunctionReturnFlushedQueue#[native code]
Also if it helps, I followed the localhost link and the function that is breaking there, tho I did not write this and cannot change it.
var authUser = function authUser(path, data) {
return function _callee(dispatch) {
var _await$api$call, token, user, error;
return _regenerator.default.async(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return _regenerator.default.awrap(_api.default.call('post', "auth/" + path, data));
case 3:
_await$api$call = _context.sent;
token = _await$api$call.token;
user = (0, _objectWithoutProperties2.default)(_await$api$call, ["token"]);
AsyncStorage.setItem('jwtToken', token);
_api.default.setToken(token);
dispatch(setCurrentUser(user));
dispatch((0, _error.removeError)());
_context.next = 16;
break;
case 12:
_context.prev = 12;
_context.t0 = _context["catch"](0);
error = _context.t0.response.data;
dispatch((0, _error.addError)(error.message));
case 16:
case "end":
return _context.stop();
}
}
}, null, null, [[0, 12]], Promise);
};
};
The error is coming from the catch block of the authUser function:
export const authUser = (path, data) =>
{
return async dispatch => {
try {
// ... Other existing codes
} catch (err) {
const error = err.response.data;
dispatch(addError(error.message));
}
}
};
For errors thrown by axios, err.response won't always be available, at times when there is no response from the server or there was a problem making the request in the first place, err.response would be undefined. In such cases, you need to handle other sources of errors. You should update the catch logic to handle the possible error cases, the code should be something like this:
catch (err) {
if (err.response) {
// There is an error response from the server
// You can anticipate error.response.data here
const error = err.response.data;
dispatch(addError(error.message));
} else if (err.request) {
// The request was made but no response was received
// Error details are stored in err.reqeust
console.log(err.request);
} else {
// Some other errors
console.log('Error', err.message);
}
}
More details on handling axios error here.

koa2+koa-router+mysql keep returning 'Not Found'

Background
I am using koa2 with some middlewares to build a basic api framework. But when I use "ctx.body" to send response in my router, the client side always receive "Not Found"
My code
./app.js
const Koa = require('koa');
const app = new Koa();
const config = require('./config');
//Middlewares
const loggerAsync = require('./middleware/logger-async')
const bodyParser = require('koa-bodyparser')
const jsonp = require('koa-jsonp')
app.use(loggerAsync())
app.use(bodyParser())
app.use(jsonp());
//Router
const gateway = require('./router/gateway')
app.use(gateway.routes(), gateway.allowedMethods());
app.use(async(ctx, next) => {
await next();
ctx.response.body = {
success: false,
code: config.code_system,
message: 'wrong path'
}
});
app.listen(3000);
./router/gateway.js
/**
* Created by Administrator on 2017/4/11.
*/
const Router = require('koa-router');
const gateway = new Router();
const df = require('../db/data-fetcher');
const config = require('../config');
const moment = require('moment');
const log4js = require('log4js');
// log4js.configure({
// appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
// categories: { default: { appenders: ['cheese'], level: 'error' } }
// });
const logger = log4js.getLogger('cheese');
logger.setLevel('ERROR');
gateway.get('/gateway', async(ctx, next) => {
let time = ctx.query.time;
if (!time) {
ctx.body = {
success: false,
code: config.code_system,
message: 'Please input running times'
}
} else {
try {
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
return ctx.body = {
success: true,
code: config.code_success
}
} catch (error) {
logger.error(error.message);
}
}
});
module.exports = gateway;
Then a db wrapper(mysql)
./db/async-db.js
const mysql = require('mysql');
const config = require('../config');
const pool = mysql.createPool({
host: config.database.HOST,
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE
})
let query = (sql, values) => {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
} else {
connection.query(sql, values, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
module.exports = query
./db/data-fetcher.js
const query = require('./async-db')
async function performQuery(sql) {
let dataList = await query(sql)
return dataList
}
module.exports = performQuery;
My running result
When I launch server on port 3000 then accesss via http://localhost:3000/gateway?time=5, it always returns "Not found". But as I can see I have already used
return ctx.body = {
success: true,
code: config.code_success
}
to send response. I debugged and found that the database processing was done well, the new data was inserted well.
when I remove that db inserting line, it works well and returns success info.
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
Is there anything wrong?
Thanks a lot!
Update 2017/04/27
Now I have found the problem. It's due to my custom middleware
const loggerAsync = require('./middleware/logger-async')
Code are like following -
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
return new Promise( ( resolve, reject ) => {
// 执行中间件的操作
log( ctx )
resolve()
return next()
}).catch(( err ) => {
return next()
})
}
}
I changed it to async/await way then everything is working well.
Could anyone please tell me what's wrong with this middleware?
I guess, your problem is the ./db/data-fetcher.js function. When you are calling
let r = await df(`insert ....`)
your df - function should return a promise.
So try to rewrite your ./db/data-fetcher.js like this (not tested):
const query = require('./async-db')
function performQuery(sql) {
return new Promise((resolve, reject) => {
query(sql).then(
result => {
resolve(result)
}
)
}
}
module.exports = performQuery;
Hope that helps.
correct middleware:
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
log( ctx );
return next()
}
}
reason: when resolve involved; promise chain was completed; response has been sent to client. although middleware remained will involved, but response has gone!
try to understand It seems that if you want to use a common function as middleware, you have to return the next function
nodejs(koa):Can't set headers after they are sent

Resources