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)
})
Related
I'm using Got to make requests to a Strapi API from Node, like so:
res.setHeader('Content-Type', 'application/json')
try {
const request = req.query.request
const decodedRequest = Buffer.from(request, 'base64').toString()
const api = process.env.API_URL ? process.env.API_URL.replace(/\/$/, '') : ''
const url = `${api}${decodedRequest}`
const response = await got.get(url)
const body = await got.get(url).json()
const headers = JSON.parse(JSON.stringify(response.headers))
res.status(200).json({
headers: headers,
data: body
})
} catch (e) {
res.status(500).json({})
}
This works but note that I have the request twice because if I do:
res.setHeader('Content-Type', 'application/json')
try {
const request = req.query.request
const decodedRequest = Buffer.from(request, 'base64').toString()
const api = process.env.API_URL ? process.env.API_URL.replace(/\/$/, '') : ''
const url = `${api}${decodedRequest}`
const response = await got.get(url)
const body = response.json()
const headers = JSON.parse(JSON.stringify(response.headers))
res.status(200).json({
headers: headers,
data: body
})
} catch (e) {
res.status(500).json({
error: e
})
}
it Just crashes and the e from the catch returns an empty error so I have no idea what's going on
I need the headers because the pagination info from Strapi is returned there:
This works because of the value you're awaiting. The conventional example:
const body = await got.get("...").json();
is equivalent to:
const res = got.get("...");
const body = await res.json();
// ^ note
but not:
const res = await got.get("...");
// ^ note
const body = res.json();
From the Promise API docs:
The main Got function returns a
Promise.
Although in order to support cancelation,
PCancelable is used
instead of pure Promise.
The json method is attached to this PCancelable object, not the value it resolves to. If you try to call it on the response, therefore, you get TypeError: res.json is not a function.
What you want is something like:
const res = await got.get("...");
const body = JSON.parse(res.body);
const headers = res.headers;
// ...
That said, if you're doing this for pagination reasons you could also look into their API for that.
SO I been learning NextJS API routes and NodeJS recently, While I do know how to create dynamic routes now, I have some issues with a few things -
This is my api/matches.js file.
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default async function handler(req, res) {
const response = await fetch(`https://exampleapi.com/?&token=# `)
const jsonData = await response.json();
res.status(200).json(jsonData);
}
Now, I have another dynamic route for this API which fetches the match by a match slug, So this file was called /api/matches/[...matchslug].js
export default async function handler(req, res) {
const page = req.query.matchslug
const response = await fetch(`https://examleapi.com/?search[slug]=${page}&token=# `)
const jsonData = await response.json();
While this dynamic route fetches the result of one so if I went matches/exampelmatch, I do get the results for the examplematch, However I'm looking to somehow implement it in a way that
matches/examplematch1/examplematch2
Returns the data from the examplematch1 & examplematch2.
I'm not sure if building something like is possible, But very interested.
Thank you for your time and patience.
In your /api/matches/[...matchslug].js example, the value of matchslug will always be an array.
Instead of passing the page variable directly into fetch, you can map over the values in the matchslug array and use Promise.all to request each resource.
It would look something like this (I haven't tested this):
export default async function handler(req, res) {
const promiseArray = req.query.matchslug.map(async (slug) => {
const response = await fetch(`https://examleapi.com/?search[slug]=${slug}&token=# `)
const jsonData = await response.json();
return jsonData
})
const result = await Promise.all(promiseArray)
res.status(200).json(result);
}
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
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));
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))
});