How do I call two different REST api endpoints simultaneously and display the data from both on one endpoint of my app? - javascript

I am building a simple application for my portfolio using The Movie Database api. In my GET /movie route, I want to get and display the data about the movie, and the names and photos of the cast members, however the data for the movie and the data for the cast members belong two separate endpoints of the api, and I am at a complete loss as to how to access both response data sets under a single endpoint in my app.
I cannot call axios.get() on both endpoints under the /movie route because I will get a "Headers already sent" error, and I have tried to write a function that uses axios.get for 1 endpoint that returns the response and gets called in my GET /movie route, but that causes the entire GET route to return undefined.
Here is my current code for my /movie route that is incorrect, but closely conveys what I am trying to accomplish
const express = require('express');
const router = express.Router();
const axios = require('axios');
const api_key = require('../config/keys').api_key;
const imgURL = "http://image.tmdb.org/t/p/";
const dateFormat = require('../config/dateFormat');
getActors = movie_id => {
axios.get(`https://api.themoviedb.org/3/movie/${movie_id}/credits?api_key=${api_key}&language=en-US`)
.then(res => {
return res.data;
}).catch(err => console.log(err.message));
}
router.get('/:id', (req, res) => {
const id = req.params.id
axios.get(`https://api.themoviedb.org/3/movie/${id}?api_key=${api_key}&language=en-US`)
.then(res => {
const movieData = res.data;
const actors = getActors(id); //calling above function here, but returns undefined and hangs the application
res.render('movie', {movieInfo: movieData, imgURL: imgURL, releaseDate: dateFormat(movieData.release_date), actors: actors})
})
.catch(err => console.log(err.status_message))
});
module.exports = router;
any help is greatly appreciated.

You can use axios.all to concatenate several promises and executing them in parallel. Then, once all the added requests have been finished, you can handle with the then promise the result of all of them. For instance, in your code:
const express = require('express');
const router = express.Router();
const axios = require('axios');
const api_key = require('../config/keys').api_key;
const imgURL = "http://image.tmdb.org/t/p/";
const dateFormat = require('../config/dateFormat');
axios.all([
axios.get(`https://api.themoviedb.org/3/movie/${movie_id}/credits?api_key=${api_key}&language=en-US`),
axios.get(`https://api.themoviedb.org/3/movie/${id}?api_key=${api_key}&language=en-US`)
])
.then(axios.spread((actorsRes, moviesRes) => {
// Your logic with each response
});
module.exports = router;

I see that getActors is currently returning null. Change it to...
const getActors = movie_id => {
return axios.get(`https://api.themoviedb.org/3/movie/${movie_id}/credits?api_key=${api_key}&language=en-US`)
.then(res => {
return res.data;
}).catch(err => console.log(err.message));
}
Another problem is calling of getActors function. It's a function that contains asynchronous function axios.get(). Change that to ..
router.get('/:id', (req, res) => {
let movieData;
const id = req.params.id
axios.get(`https://api.themoviedb.org/3/movie/${id}?api_key=${api_key}&language=en-US`)
.then(res => {
movieData = res.data;
return getActors(id);
})
.then(actors => {
res.render('movie', {movieInfo: movieData, imgURL: imgURL, releaseDate: dateFormat(movieData.release_date), actors: actors})
})
.catch(err => console.log(err.status_message))
});

Related

How to make variables accessible across multiple get requests in express? And is there a better way to code this?

I have the following get request where I call a bunch of data and pass it through to my EJS view.
router.get('/currentrentals', ensureAuthenticated, async (req, res) => {
const companies = await Company.getCompanies();
const barges = await Barge.getBarges();
const parishes = await Parish.getParishes();
const states = await State.getStates();
const pickupdropoff = await PickupDropoff.getPickupDropoff();
var notifications = await Notification.getNotifications();
JSON.stringify(barges);
JSON.stringify(companies);
JSON.stringify(parishes);
JSON.stringify(states);
JSON.stringify(pickupdropoff);
JSON.stringify(notifications);
var notifications = await notifications.sort((a, b) => b.id - a.id).slice(0,3);
res.render('currentrentals', {
name: req.user.name, companies: companies, barges: barges, parishes: parishes, states: states, pickupdropoff : pickupdropoff, notifications : notifications
});
}
);
Two questions:
I have multiple get requests that requires the same information. Is there a way to make this data available across the entirety of my site, so I don't have to rewrite this for each get path?
Is there a more succinct way to write the existing code I have? Perhaps looping through them or something of the sort? Simply for learning purposes.
The code currently works as-is.
Thanks!
If the data is constant, you can try this:
let data = null;
async function getData() {
if (!data) {
data = {
companies: await Company.getCompanies(),
barges: await Barge.getBarges();
parishes: await Parish.getParishes(),
states: await State.getStates(),
pickupdropoff: await PickupDropoff.getPickupDropoff(),
notifications: (await Notification.getNotifications()).sort((a, b) => b.id - a.id).slice(0,3)
};
}
return data;
}
router.get('/currentrentals', ensureAuthenticated, async (req, res) => {
res.render('currentrentals', { name: req.user.name, ...(await getData()) });
}
);
// other routes

How to send url params from react to express api call

So i'm making a fetch request from react to express and the api url requires params but I want the last bit of the url to be dynamic. How would I send a param from react to the api url in express? Heres the code
react
const [dynamic, setDynamic] = useState("somevalue")
useEffect(() => {
async function fetchData() {
const response = await fetch("/web")
const data = await response.json()
console.log(data)
}
fetchData()
}, [dynamic])
express.js
app.get("/web", async (req, res) => {
const response = await fetch("https://alexa.com/api?Action=urlInfo&Url=")
const data = await response.json()
res.json(data)
})
Basically, I would like the dynamic state value to be injected into the end of the URL in express.
Something like
react
const [dynamic, setDynamic] = useState("somevalue")
async function fetchData() {
const response = await fetch("/web" + dynamic)
const data = await response.json()
console.log(data)
}
Then in express when the API is called the URL changes to
const response = await fetch("https://alexa.com/api?Action=urlInfo&Url=somevalue")
How can I go about achieving this?
You define the parameter on the route handler side, setting up a route that matches the desired pattern. For example:
app.get("/foo/bar/:myparam", async (req, res) => {
//... here's your handler code
})
Will match /foo/bar/baz, and req.params.myparam === "baz" inside the handler.
http://expressjs.com/en/guide/routing.html#route-parameters
Using query params is similar, but you want to check the values of the req.query object instead, like:
// client side (React code)
await fetch("/foo/bar?myparam=baz")
...
// In your express handler:
req.query.myparam === "baz"
Use Dynamic value in params of api call.
React code would look like this:
const [dynamic, setDynamic] = useState("somevalue")
useEffect(() => {
async function fetchData() {
const response = await fetch(`/web/${dynamic}`)
const data = await response.json()
console.log(data)
}
fetchData()
}, [dynamic])
In Express.Js code
define the parameter on the route handler side, and use that in fetch api call
app.get("/web/:value", async (req, res) => {
const {value} = req.params;
const response = await fetch(`https://alexa.com/api?Action=urlInfo&Url=${value}`)
const data = response.json()
res.json(data)
})

How do I properly route data through my Node API?

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));

Calling multiple functions in single express route call?

I created a web scraping function with cheerio.js that I want to get called when the user opens the home page. I got it to work with one function, but I actually have 3 scraping functions that all do slightly different things. How do I add those functions in my route call to the home page along with the first function call? (basically i want all the data to be scraped upon the user opening the home page)
I'm also not very familiar with async/await or promises and got this code to work by following examples online, so if something is wrong there please let me know!
express route call that works (index.js):
const getSUPResults = require("./routes/SUPstores");
app.use(getSUPResults);
app.get("/", async function(req, res, next){
const result = await getSUPResults();
res.render("index", result);
});
scraper function ("./routes/SUPstores")
const express = require("express");
const cheerio = require("cheerio");
const { default: Axios } = require("axios");
const fetchData = async () => {
const result = await Axios.get("http://example.com");
return cheerio.load(result.data);
};
const getSUPResults = async () => {
const $ = await fetchData();
const SUPStoreInfo = [];
$('.row > tbody > tr:contains("SUP")').each(function(i, element){
const SUPResult = {
"environment" : $(this).children().text(),
"store" : $(this).children().next().text(),
"version" : $(this).children().next().next().text()
};
SUPStoreInfo.push(SUPResult);
});
return SUPStoreInfo;
}
module.exports = getSUPResults;
I tried to do something like this, but it only seemed to call the first function (getSUPResults), and ignored the rest. Is there something similar to this I could try?
const getSUPResults = require("./routes/SUPstores");
const getQAResults = require("./routes/QAstores");
const getDEVResults = require("./routesDEVstores");
app.use(getSUPResults);
app.use(getQAResults);
app.use(getDEVResults);
app.get("/", async function(req, res, next){
const SUP = await getSUPResults();
const QA = await getQAResults();
const DEV = await getDEVResults();
const result = [SUP, QA, DEV];
res.render("index", result);
});

Axios and mongoose to build an API

Can I use axios and mongoose to build an API? That's because I am new in the back-end developement, so I tried and I gotta an error like below, I was trying to do an API with axios and mongoose, but I don't know if i can use them together, I was kind to test this code to see if they work, but apparently not. So any suggestion to code better or is that correct what I am doing, is there other way ? I sure there is, but which recommendion you guys can make.
I want to create two separate files, one for controllers like "product.controller" and use my api to do all the requests that my differents controllers could need
Error: socket hang up
at createHangUpError (_http_client.js:323:15)
at Socket.socketOnEnd (_http_client.js:426:23)
at Socket.emit (events.js:203:15)
at endReadableNT (_stream_readable.js:1145:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
product.controller.js
const Product = require('../models/product.model');
const api = require('../api/api');
mongoose = require('mongoose').set('debug', true);
// To create a new Product
exports.create = async (req, res) => {
const product = await api.create('/product', req.body);
res.status(201).send({ message: 'The product has been created successfully !', product });
};
api.js
const axios = require('axios');
const baseURL = require('../config/database');
const list = async(key) =>{
const content = await axios.get(baseURL.local.urlDataBase + key +'.json')
if(content.data){
const objects = Object
.keys(content.data)
.map(key=>{
return{
id: key,
...content.data[key]
}
})
return objects
}
return []
}
const get = async(key, id) => {
const content = await axios.get(`${baseURL.local.urlDataBase}/${key}/${id}.json`)
return {
id: id,
...content.data
}
}
const create = async(key, data) =>{
await axios.post(`${baseURL.local.urlDataBase}/${key}.json`, data)
return true
}
module.exports = {
list, create
}

Categories

Resources