I am currently doing a GET request that uses a bunch of methods that I wrote to query the database and display them. This works great, but I want to make it a POST request method so that the methods do not have to depend on req.query, deal with a json body instead of a string of URL params which would would facilitate the url string not to have anything else except the endpoints and make it as dynamic as possible. Any idea how to do this?
This is my controller method:
exports.getBooks = async (req, res, next) => {
const bookList = new BookList(Book.find(), req.query)
.filter()
.sort()
.paginate();
const books = await bookList.query;
res.status(200)
.json({
books,
});
};
This is the BookList class that has all the methods:
class BookList {
constructor(query, queryString) {
this.query = query;
this.queryString = queryString;
}
filter() {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(el => delete queryObj[el]);
let queryStr = JSON.stringify(queryObj);
this.query = this.query.find(JSON.parse(queryStr));
return this;
}
sort() {
if (this.queryString.sort) {
const sortBy = this.queryString.sort.split(',').join(' ');
this.query = this.query.sort(sortBy);
} else {
this.query = this.query.sort('-createdAt');
}
return this;
}
paginate() {
const page = Number(this.queryString.page) || 1;
const limit = Number(this.queryString.limit) || 100;
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
}
module.exports = BookList;
This is what worked for me:
exports.getBooks = async (req, res, next) => {
let bookBody = req.body
const bookList = new BookList(Book.find(), req.query)
.filter(bookBody, req)
.sort()
.paginate();
const books = await bookList.query;
res.status(200)
.json({
books,
});
};
filter(bookBody, req) {
const filterBooks = bookBody.filter
const bookId = req.params.bookId
let requiredFilter
if (filterBooks) {
requiredFilter = {bookStatus: filterBooks.bookStatus, bookId};
} else {
requiredFilter = { bookId}
}
this.query = this.query.find(requiredFilter)
return this;
}
If you need to convert it to POST with json body then you will use something like below
var bodyParser = require('body-parser')
// parse application/json
app.use(bodyParser.json())
exports.getBooks = async (req, res, next) => {
const bookList = new BookList(Book.find(), req.body)
.filter()
.sort()
.paginate();
const allReports = await bookList.query;
res.status(200)
.json({
books,
});
};
In this I assumed that parameters name will still be the same. Also the two lines for json parser needs to be in the file where you initialise the express server
When using an asynchronous javascript XML (AJAX) request to send and receive back data from the server, it is possible to specify either a "POST" or a "GET" request. Any number of items may be sent or received via a single such request, as is shown in the sample script below. In your case, you would tailor it to have the script send your JSON data.
function AJAXroutine(val1,val2) { /* NAME IT WHATEVER YOU LIKE */
var formAction=document.getElementById("MyForm").getAttribute('action');
function Q(){
var K=new XMLHttpRequest();
var frm=new FormData();
/* CAN HAVE ANY NUMBER OF VALUES SENT BACK TO SERVER */
frm.append('V1',val1);
frm.append('V2',val2);
frm.append('V3',document.getElementById('another_elmt').value);
frm.append('V4',document.getElementById('another_one').value);
K.onreadystatechange=function(){
if(this.readyState==4&&this.status==200){
/* IF MULTIPLE VALUES ARE RETURNED */
var data=this.responseText.split("|");
document.getElementById('my_elmt').innerHTML=data[0];
document.getElementById('my_text').value=data[1];
/* ETC. */
/* IF JUST ONE VALUE IS RETURNED */
document.getElementById('my_elmt').value=this.responseText;
};
};
/* CAN OPTIONALLY CHANGE 'POST' TO 'GET' */
K.open('post',formAction);
K.send(frm)
}
Q();
}
This is a very basic template for an AJAX request and can be easily adapted to suit one's individual requirements.
Related
I'm trying to respond in case a fetch for an item doesn't return something from another method that doesn't have the express response. I call this method from another that if it has the express response:
const updateItem = async (req, res = response ) => {
const id = req.params.id;
const idItem = req.params.idItem;
await itemExists( id, idItem);
...
And in the itemExists() function I search for the item in mongo and if it doesn't exist I want to send it as a response but I don't know how to do it without using Express response:
const itemExists = async ( id, idItem ) => {
const item = await PettyCashItems.findOne({ _id: id, "items._id": idItem });
if (!item) {
return ......
}
}
Thanks.
As already mentioned by one of the comments, there's no way to use response object without having access to it.
However, what you want can be achieved in two ways:
1- Easy way - return a value from itemExists function and send the response according to returned value from itemExists
const updateItem = async (req, res = response ) => {
const id = req.params.id;
const idItem = req.params.idItem;
const exists = await itemExists( id, idItem);
if (exists) {
res.send('YES')
return;
}
res.send('NO');
}
2- Better way - Setup error handling for your express application so all thrown error are caught and response is sent based on the thrown error, then you can simply
class BaseHTTPException extends Error {
constructor(statusCode) {
super();
this.statusCode = statusCode;
}
}
class ItemDoesNotExistException extends BaseHTTPException {
constructor() {
super(400)
}
}
throw new ItemDoesNotExistException()
in your itemExists function.
Further reading: https://expressjs.com/en/guide/error-handling.html
I have the following files:
My routes - where the orders_count route lives:
routes/index.js
const express = require('express');
const router = express.Router();
const transactionsController = require('../controllers/transactionsController');
const ordersController = require('../controllers/ordersController');
const ordersCountController = require('../controllers/ordersCountController');
router.get('/transactions', transactionsController);
router.get('/orders', ordersController);
router.get('/orders_count', ordersCountController);
module.exports = router;
I then have my orders count controller living in the controllers directory:
controllers/ordersCountController.js
const ordersCountService = require('../services/ordersCountService');
const ordersCountController = (req, res) => {
ordersCountService((error, data) => {
if (error) {
return res.send({ error });
}
res.send({ data })
});
};
module.exports = ordersCountController;
My controller then calls my order count service which fetches data from another API.
services/ordersService.js
const fetch = require('node-fetch');
// connect to api and make initial call
const ordersCountService = (req, res) => {
const url = ...;
const settings = { method: 'Get'};
fetch(url, settings)
.then(res => {
if (res.ok) {
res.json().then((data) => {
return data;
});
} else {
throw 'Unable to retrieve data';
}
}).catch(error => {
console.log(error);
});
}
module.exports = ordersCountService;
I'm trying to return the JSON response. I initially had it setup with requests but looking at the NPM site, it appears that it's depreciated so have been digging through how to use node-fetch.
I have tried both 'return data' and res.send({data}), but neither are solving the problem.
I am still new to this so I am likely missing something very obvious, but how come I am not sending the JSON back through so that it displays at the /api/orders_count endpoint?
I keep thinking I messed something up in my controller but have been looking at it for so long and can't seem to figure it out.
Any help would be greatly appreciated and if there is anything I can add for clarity, please don't hesitate to ask.
Best.
please learn promises and await syntax. life will be easier.
never throw a string. always prefer a real error object, like that : throw new Error('xxx'); that way you will always get a stack. its way easier to debug.
avoid the callback hell : http://callbackhell.com/
you need to decide if you want to catch the error in the controller or in the service. no need to do in both.
in the controller you call the service that way :
ordersCountService((error, data) => {
but you declare it like that :
const ordersCountService = (req, res) => {
which is not compatible. it should look like this if you work with callback style :
const ordersCountService = (callback) => {
...
if (error) return callback(error)
...
callback(null, gooddata);
here is an example to flatten your ordersCountService function to await syntax, which allows the "return data" you were trying to do :
const fetch = require('node-fetch');
// connect to api and make initial call
const ordersCountService = async (req, res) => {
const url = ...;
const settings = { method: 'Get'};
try {
const res = await fetch(url, settings);
if (!res.ok) throw new Error('Unable to retrieve data');
return await res.json();
} catch(error) {
console.log(error);
}
}
module.exports = ordersCountService;
in fact i would prefer to error handle in the controller. then this woud be sufficient as a service
const fetch = require('node-fetch');
// connect to api and make initial call
const ordersCountService = async () => {
const url = ...;
const settings = { method: 'Get'};
const res = await fetch(url, settings);
if (!res.ok) throw new Error('Unable to retrieve data');
return await res.json();
}
module.exports = ordersCountService;
then you can call this funtion like this :
try {
const data = await ordersCountService(req, res);
} catch(err) {
console.log(err);
}
//or
ordersCountService(req, res).then((data) => console.log(data)).catch((err) => console.error(err));
so I am building a website and using nodejs to do a bunch of api calls and populate all the info. I have a homepage which has a sidebar where people can sort by 'categories'. but when I run my code instead of the categories being displayed in the html I get [object Object]. I have tried a whole bunch of things but still it only returns [object Object]. here's the code:
index.js
const express = require('express')
const router = new express.Router();
const categoriesFunction = require('../controllers/categories')
// get index page
router.get('', async (req, res) => {
let requestString = '/apps'
allApps = await openChannelRequest(requestString, req, res)
await res.render('index', {
categories: categoriesFunction,
// this is where I try to add the categories to the homepage
})
})
here's the controller where I'm grabbing all categories data and storing it. I'm pretty sure I can do this in the index.js page but a few weeks ago when I started this I made controllers for some reason. if that is not the best way to do this please let me know.
categories.js
const express = require('express')
const app = express()
var request = require('request');
var categoricalNames = []
var options = {
'method': 'GET',
'url': 'a working url',
'headers': {
'Authorization': 'working authorization'
},
'contentType': 'application/json'
};
var categoriesFunction = async() => request(options, function (error, response) {
return new Promise(function(resolve, reject){
console.log('inside categories function')
var categoryName = ''
if(error || !response)
{
// report error
reject(new Error('Try Again'));
}
else
{
//process response
var body = JSON.parse(response.body);
var jsonResponse = body.values
jsonResponse.forEach(function(obj) {
// console.log(obj.label)
categoryName = obj.label
// JSON.stringify(categoryName)
categoricalNames.push(categoryName)
});
categoricalNames.push({categoryName});
// console.log(categoricalNames)
// report success
JSON.stringify(categoricalNames)
resolve(categoricalNames);
}
})
});
module.exports.getPlaceDetails = categoriesFunction;
for awhile I thought my code wasn't working but console.logs in my categoriesFunction function reveal that the array is populating correctly. it's just not being pushed to the index correctly. and trying that method on the index.js page does not do anything for me. still just get [object Object]. not really sure why. Can anyone explain this to me?
Thanks!
I figured out what I needed to do. basically needed to write a cleaner Promise and make sure not to return the array but to resolve the array. and also there needed to be a variable in index.js that awaits the results of categoriesFunction()
here's what index.js looks like now
const express = require('express')
const router = new express.Router();
const categoriesFunction = require('../controllers/categories')
// get index page
router.get('', async (req, res) => {
let requestString = '/apps'
allApps = await openChannelRequest(requestString, req, res)
const thing = await categoriesFunction()
// this is important cause the method needs to be called before its inserted
await res.render('index', {
categories: thing,
// this is where I try to add the categories to the homepage
})
})
and here's what the categories.js file looks like
let categoriesFunction = function() {
return new Promise(resolve =>
request(options,
(error, response) => {
var categoryName = ''
var body = JSON.parse(response.body);
var jsonResponse = body.values
jsonResponse.forEach(function(obj) {
categoryName = obj.label
JSON.stringify(categoryName)
categoricalNames.push(categoryName)
});
console.log(categoricalNames)
resolve(categoricalNames)
// now that this is resolved it can be returned
}
))
}
I'm getting an empty response ({}), whereas my expected response is of format:
{
locationResponse: "location foo",
forecastResponse: "forecast bar"
}
In my index file I have:
const {getCity} = require('./routes/city');
const {getForecasts} = require('./routes/forecast');
app.get('/forecasts', function (req, res) {
var location = getCity(req, res);
var forecast = getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});
Inside forecast file I have the following, and a similar one is in city file:
module.exports = {
getForecasts: (req, res) => {
var result = //mySQL DB calls and processing
console.log("Inside getForecasts");
console.log(result); //actual result printed
return "Forecast";
}
UPDATE: So I added some logs right before each call's return statements and figured out that the logs are printed in the following order, which means, it is not working as expected because I have not considered the fact that they are asynchronous calls.
Inside index.js
undefined
undefined
Inside getForecasts
{result}
The problem here is that in your ./routes/forecast/ getForecasts method, you're telling the response to send, with the data "Forecast". You should only ever use res.send once per request, as this will resolve the response and return to the client.
Instead, your getForecasts method should just return whatever data you need, and your index file should handle the response. If you need getForecasts to handle a response too, perhaps because you're sending requests directly to a forecasts endpoint that doesn't require location data, then you can refactor your code so that both index and forecasts make a call to get the data you need. For example:
/* index.js */
const {getCity} = require('./data/city');
const {getForecasts} = require('./data/forecast');
app.get('/forecasts', function (req, res) {
var location = getCity();
var forecast = getForecasts();
res.send({locationResponse: location, forecastResponse: forecast});
});
/* data/forecast.js */
module.exports = {
getForecasts: () => {
return "Forecast";
}
};
/* data/city.js */
module.exports = {
getCity: () => {
return "City";
}
};
Then you can also have:
/* routes/forecast.js */
const {getForecasts} = require('../data/forecast');
module.exports = {
getForecasts: (req, res) => {
res.send(getForecasts());
}
};
The above may be overcomplicating things, but I made the assumption that if you're using a routes directory, you probably want route handlers to be stored there. Hope this helps.
Seems both of your getCity() and getForecasts() functions are async. These asynchronous functions return a promise rather actual response.
So you can use simple asysn/await or Promise.all in JS to solve the issue.
Option 1: Use await for the promise to resolve before logging the message to the console:
app.get('/forecasts', async function (req, res) {
var location = await getCity(req, res);
var forecast = await getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});
Option 2: Use Promise.all() to wait for all the promises to have fulfilled.
app.get('/forecasts', function (req, res) {
var list = await Promise.all([getCity(req, res), getForecasts(req, res)]);
//these are logged as undefined
console.log("Inside index.js");
console.log(list[0]);
console.log(list[1]);
res.send({locationResponse: list[0], forecastResponse: list[1]});
});
You can make use of async/await syntax.
app.get('/forecasts', async function (req, res) {
var location = await getCity(req, res);
var forecast = await getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});
I've read through the Firebase Cloud Functions reference, guides and sample code to try and determine why my function is triggered twice, but am yet to find a successful resolution. I've also trialed Firebase-Queue as a work-around, however its latest update suggests Cloud Functions is the way to go.
In short, I'm retrieving notices from an external API using request-promise, checking those notices against ones I already have in my database, and when a new notice is identified, posting it to said database. The corresponding venue is then updated with a reference to the new notice. Code is as follows:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request');
const rp = require('request-promise');
admin.initializeApp(functions.config().firebase);
const db = admin.database();
const venues = db.ref("/venues/");
exports.getNotices = functions.https.onRequest((req, res) => {
var options = {
uri: 'https://xxxxx.xxxxx',
qs: {
format: 'json',
type: 'venue',
...
},
json: true
};
rp(options).then(data => {
processNotices(data);
console.log(`venues received: ${data.length}`);
res.status(200).send('OK');
})
.catch(error => {
console.log(`Caught Error: ${error}`);
res.status(`${error.statusCode}`).send(`Error: ${error.statusCode}`);
});
});
function processNotices(data) {
venues.once("value").then(snapshot => {
snapshot.forEach(childSnapshot => {
var existingKey = childSnapshot.val().key;
for (var i = 0; i < data.length; i++) {
var notice = data[i];
var noticeKey = notice.key;
if (noticeKey !== existingKey) {
console.log(`New notice identified: ${noticeKey}`)
postNotice(notice);
}
}
return true;
});
});
}
function postNotice(notice) {
var ref = venues.push();
var key = ref.key;
var loc = notice.location;
return ref.set(notice).then(() => {
console.log('notice posted...');
updateVenue(key, loc);
});
}
function updateVenue(key, location) {
var updates = {};
updates[key] = "true";
var venueNoticesRef = db.ref("/venues/" + location + "/notices/");
return venueNoticesRef.update(updates).then(() => {
console.log(`${location} successfully updated with ${key}`);
});
}
Any suggestions as to how to rectify the double-triggering would be greatly appreciated. Thanks in advance!
Problem solved - some misinformation from the Firebase Console Logs (repeating entries), coupled with nested for loops in the wrong order were responsible for the apparent double triggering.