Error: cannot set headers after they are sent to client - node.js

I wrote a simple express app that let user search for a game's name as well as filter according to game's genre and sort by title or ranking. The game data is an array of objects with keys App, Rating, and Genres, etc
However I am getting the console error 'Cannot set headers after being sent to client'.
The request still returns the correct filtered results.
What does it mean?
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const googleApps = require('./google-apps')
const app = express();
app.use(morgan('common'));
app.use(cors());
app.get('/apps', (req, res) => {
let {
search = '', sort, genres
} = req.query;
if (sort) {
if (!['rating', 'app'].includes(sort)) {
return res.status('400')
.send('Sort must be one of "app" or "rating"')
}
}
if (genres) {
if (!['Action', 'Puzzle', 'Strategy', 'Casual', 'Arcade', 'Card'].includes(genres)) {
return res.status('400')
.send('Genres must be one of Action, Puzzle, Strategy, Casual, Arcade, Card')
}
}
let results = googleApps.filter(app =>
app.App
.toLowerCase()
.includes(search.toLowerCase())
)
if (sort) {
if (sort === 'rating') {
sort = 'Rating'
} else {
sort = 'App'
}
results
.sort((a, b) => {
return a[sort] > b[sort] ? 1 : a[sort] < b[sort] ? -1 : 0;
});
}
if (genres) {
let genreResult = results.filter(app =>
app
.Genres
.toLowerCase()
.includes(genres.toLowerCase())
)
res.json(genreResult)
console.log(genreResult)
}
res.json(results)
})
module.exports = app;

This happens when you try sending more than one response back to the client, which would never work, one request, one response, for example
res.send({ data: "I want to send this message" });
res.send({ data: "But I also want to send this message" });
This would never work, because you're attempting to send multiple responses for the same request.
That happened in your code where you tried to send res.json(genreResult) as #ayush-gupta said, you should return reaching that point where you've sent your response.

Related

WA business api nodejs

I have a problem with my nodejs code and the connection to the official whatsapp business api.
The bot connects the webhook correctly, the messages arrive to the server correctly but the code I have implemented to make it respond is not being effective, I checked the code from top to bottom but I can't find the fault.
I leave you the codes so you have more context:
whatsappController.js:
const fs = require("fs");
const myConsole = new console.Console(fs.createWriteStream("./logs.txt"));
const whatsappService = require("../services/whatsappService")
const VerifyToken = (req, res) => {
try {
var accessToken = "456E7GR****************************";
var token = req.query["hub.verify_token"];
var challenge = req.query["hub.challenge"];
if(challenge != null && token != null && token == accessToken){
res.send(challenge);
}
else{
res.status(400).send();
}
} catch(e) {
res.status(400).send();
}
}
const ReceivedMessage = (req, res) => {
try {
var entry = (req.body["entry"])[0];
var changes = (entry["changes"])[0];
var value = changes["value"];
var messageObject = value["messages"];
if(typeof messageObject != "undefined"){
var messages = messageObject[0];
var text = GetTextUser(messages);
var number = messages["from"];
myConsole.log("Message: " + text + " from: " + number);
whatsappService.SendMessageWhatsApp("The user say: " + text, number);
myConsole.log(messages);
myConsole.log(messageObject);
}
res.send("EVENT_RECEIVED");
}catch(e) {
myConsole.log(e);
res.send("EVENT_RECEIVED");
}
}
function GetTextUser(messages){
var text = "";
var typeMessage = messages["type"];
if(typeMessage == "text"){
text = (messages["text"])["body"];
}
else if(typeMessage == "interactive"){
var interactiveObject = messages["interactive"];
var typeInteractive = interactiveObject["type"];
if(typeInteractive == "button_reply"){
text = (interactiveObject["button_reply"])["title"];
}
else if(typeInteractive == "list_reply"){
text = (interactiveObject["list_reply"])["title"];
}else{
myConsole.log("sin mensaje");
}
}else{
myConsole.log("sin mensaje");
}
return text;
}
module.exports = {
VerifyToken,
ReceivedMessage
}
The second file is whatsappService which I make the connection with the api using the token and I also send the format of the message I want to send when I receive a hello for example...
const https = require("https");
function SendMessageWhatsApp(textResponse, number){
const data = JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": number,
"type": "text",
"text": {
"preview_url": false,
"body": textResponse
}
});
const options = {
host:"graph.facebook.com",
path:"/v15.0/1119744*************/messages",
method:"POST",
body:data,
headers: {
"Content-Type":"application/json",
Authorization:"Bearer EAAWNbICfuWEBAK5ObPbD******************************************************"
}
};
const req = https.request(options, res => {
res.on("data", d=> {
process.stdout.write(d);
});
});
req.on("error", error => {
console.error(error);
});
req.write(data);
req.end();
}
module.exports = {
SendMessageWhatsApp
};
Then I declare the routes for the get (to check token) and post (to receive and reply to messages) methods:
const expres = require("express");
const router = expres.Router();
const whatsappController = require("../controllers/whatsappControllers");
router
.get("/", whatsappController.VerifyToken)
.post("/", whatsappController.ReceivedMessage)
module.exports = router;
Last but not least the index file for the code to run correctly:
const express = require("express");
const apiRoute = require("./routes/routes");
const app = express();
const PORT = process.env.PORT || 3000
app.use(express.json());
app.use("/whatsapp", apiRoute);
app.listen(PORT, () => (console.log("El puerto es: " + PORT)));
I should clarify that I did the tests with Postman and they were all successful, it responds and receives messages correctly, finally I did the tests by uploading the bot to the Azure service and it works without problem until it has to answer/replicate the user's message.
The bot is not responding to the user when he talks to it but everything arrives correctly to the server and it processes it with a 200 response. I attach the evidence that there is no problem in the reception.
Finally I must say that in the meta platform I have everything configured as specified by the same platform, I have already configured the api to answer the messages through the webhooks and everything is correct, I just can't get the bot to answer correctly.
The bot is hosted in the Azure service.
Solved: some numbers have a problema with the api of WAB in my country (Argentina) the phone numbers start in +54 9 11. The problem is the 9 in the phone number, and this have a conflict in the servers of meta, Solution quit number 9 to numbers of this country and the message will send to user.

Getting Express error via localhost, Error Cannot GET / or GET /sms

I'm a huge sneaker head and getting my feet wet in node and express and I'm trying to create an app using Twilio to text me when a certain shoe decreases in price and the shoe is triggered by replying "Track (SKU)" to add it to the database. I have it completely written and when I input "node index.js", it connects to my localhost just fine, but when I input http://localhost:3000/sms, I get Cannot GET /sms. I've been trying to look for a fix by trying to find some videos or if someone has done a similar project to model off of, but no luck. Is anyone familiar with creating Twilio apps with express servers able to help me out? My current index.js is below.
const SneaksAPI = require('sneaks-api');
const sneaks = new SneaksAPI();
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: false }));
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const Subscribers = [
{
"number": 2025550121,
"styleID": "FY2903",
"lastResellPrice": 475
},
{
"number": 1234567890,
"styleID": "DD0587-600",
"lastResellPrice": 285
}
];
app.post('/sms', function (req, res) {
const twiml = new MessagingResponse();
const SMS = twiml.message();
const recievedSMS = req.body.Body.toLowerCase().trim();
const firstWord = recievedSMS.split(" ")[0];
if (firstWord == 'track'){
const styleID = recievedSMS.split(" ")[1] || null;
if(styleID){
const sneaker = sneaksApiFunctionWrapper(styleID);
if(!sneaker){
SMS.body("Sneaker could not be found");
} else {
const sub = {
"number": req.body.From,
"styleID": sneaker.styleID,
"lastResellPrice": sneaker.price
};
Subscribers.push(sub);
SMS.media(sneaker.image);
SMS.body(`Current lowest price for ${sneaker.name} is $${String(sneaker.price)} at ${sneaker.site}: ${sneaker.url}\nYou will be notified when the price drops. Reply STOP to opt-out of alerts.`);
}
}
} else {
SMS.body('To start tracking a sneaker, text \"track\" followed by the sneaker ID.');
}
res.writeHead(200, {'Content-Type': 'text/xml'});
res.end(twiml.toString());
});
function sneaksApiFunctionWrapper(styleID) {
return new Promise((resolve, reject) => {
sneaks.getProductPrices(styleID, function(err, product){
const lowestResellSite = Object.keys(product.lowestResellPrice).reduce((a, b) => product.lowestResellPrice[a] > product.lowestResellPrice[b] ? a : b);
const sneaker = {
name: product.shoeName,
image: product.thumbnail,
site: lowestResellSite,
price: product.lowestResellPrice[lowestResellSite],
url: product.resellLinks[lowestResellSite],
styleID: product.styleID
};
resolve(sneaker);
}, (errorResponse) => {
reject(errorResponse);
});
});
}
app.listen(3000, () => {
console.log('Express server listening on port 3000');
});```
You are using a POST request via app.post(), which is different from a GET request. You'll need to submit data via a form or apps like Postman to properly access the POST request.
When should I use GET or POST method? What's the difference between them? This gives a solid response.

How to make a GET Request for a unique register with AXIOS and NodeJS/Express

I'm trying to make GET request to external API (Rick and Morty API). The objective is setting a GET request for unique character, for example "Character with id=3". At the moment my endpoint is:
Routes file:
import CharacterController from '../controllers/character_controller'
const routes = app.Router()
routes.get('/:id', new CharacterController().get)
export default routes
Controller file:
async get (req, res) {
try {
const { id } = req.params
const oneChar = await axios.get(`https://rickandmortyapi.com/api/character/${id}`)
const filteredOneChar = oneChar.data.results.map((item) => {
return {
name: item.name,
status: item.status,
species: item.species,
origin: item.origin.name
}
})
console.log(filteredOneChar)
return super.Success(res, { message: 'Successfully GET Char request response', data: filteredOneChar })
} catch (err) {
console.log(err)
}
}
The purpose of map function is to retrieve only specific Character data fields.
But the code above doesn't work. Please let me know any suggestions, thanks!
First of all I don't know why your controller is a class. Revert that and export your function like so:
const axios = require('axios');
// getCharacter is more descriptive than "get" I would suggest naming
// your functions with more descriptive text
exports.getCharacter = async (req, res) => {
Then in your routes file you can easily import it and attach it to your route handler:
const { getCharacter } = require('../controllers/character_controller');
index.get('/:id', getCharacter);
Your routes imports also seem off, why are you creating a new Router from app? You should be calling:
const express = require('express');
const routes = express.Router();
next go back to your controller. Your logic was all off, if you checked the api you would notice that the character/:id endpoint responds with 1 character so .results doesnt exist. The following will give you what you're looking for.
exports.getCharacter = async (req, res) => {
try {
const { id } = req.params;
const oneChar = await axios.get(
`https://rickandmortyapi.com/api/character/${id}`
);
console.log(oneChar.data);
// return name, status, species, and origin keys from oneChar
const { name, status, species, origin } = oneChar.data;
const filteredData = Object.assign({}, { name, status, species, origin });
res.send(filteredData);
} catch (err) {
return res.status(400).json({ message: err.message });
}
};

nodejs async/await nested API progress

I have an API that searches for the user-provided term, returns an array of results, then fires off async requests for each of the results and gets results for each of these second batch of requests. I'd like the API to report progress as it happens rather than just the final result. So, if I do the following request, I should get updates like so
$ curl 'http://server/?q=foobar'
searching for ${q}…
found 76… now getting images…
found 30 images… done
{
result
}
Most of relevant code is shown below. Fwiw, I am using hapijs for my application.
let imagesOfRecords = {};
const getImages = async function (q) {
console.log(`searching for ${q}…`);
const uri = `http://remoteserver/?q=${q}`;
const {res, payload} = await Wreck.get(uri);
const result = JSON.parse(payload.toString()).hits;
const numOfFoundRecords = result.total;
if (result.total) {
console.log(`found ${result.total}… now getting images…`);
const foundRecords = result.hits.map(getBuckets);
Promise.all(foundRecords).then(function() {
console.log(`found ${Object.keys(imagesOfRecords).length} images… done`);
reply(imagesOfRecords).headers = res.headers;
}).catch(error => {
console.log(error)
});
}
else {
console.log('nothing found');
reply(0).headers = res.headers;
}
};
const getBuckets = async function(record) {
const { res, payload } = await Wreck.get(record.links.self);
const bucket = JSON.parse(payload.toString()).links.bucket;
await getImageFiles(bucket, record.links.self);
};
const getImageFiles = async function(uri, record) {
const { res, payload } = await Wreck.get(uri);
const contents = JSON.parse(payload.toString()).contents;
imagesOfRecords[record] = contents.map(function(el) {
return el.links.self;
});
};
Once I can implement this, my next task would be to implement this progressive update in a web app that uses the above API.
To show result with each step of your requests for backend you can use EventEmitter, which will emit event on each progress step. You can read about events here.
Simple implementation:
const events = require('events');
const eventEmitter = new events.EventEmitter();
//your request code
Promise.all(foundRecords).then(function() {
console.log(`found ${Object.keys(imagesOfRecords).length} images… done`);
eventEmitter.emit('progress');
reply(imagesOfRecords).headers = res.headers;
})
const eventReaction = (e) => {
// do something with event, console log for example.
}
eventEmitter.on('progress', eventReaction);
More examples you can find here and here.
To show events to client you can use library socket.io. I think you can find pretty straightforward explanations how socket.io works in documentation.
If you want to send events between servers or processes and want to go little further, you can read more about 0MQ (zero mq) and it's node implementation

Use Express Router to match a route

I'm trying to consolidate a bunch of route usage throughout my Express API, and I'm hoping there's a way I can do something like this:
const app = express()
const get = {
fetchByHostname({
name
}) {
return `hey ${name}`
}
}
const map = {
'/public/hostname/:hostname': get.fetchByHostname
}
app.use((req, res, next) => {
const url = req.originalUrl
const args = { ...req.body, ...req.query }
const method = map[url] // this won't work
const result = method(args)
return res.json({
data: result
})
})
I'm trying to avoid passing round the req and res objects and just handle the response to the client in one place. Is there an Express/Node/.js module or way to match the URL, like my map object above?
I really don't understand what you are trying to achieve, but from what i can see, your fectchByHostname({name})should be fetchByHostname(name) and you might be able to return hey $name. You should be sure you are using ES6 because with you args. Else you have to define the as in es5 args = {body: req.body, query: req.query};. Hope it helps.

Resources