I'm building an appplication using react at the front and express at the back in order to avoid cors issues. From the back I get the Json of my API from "Zoho Creator". Here an example.
As you can see I get a Json object, but in the image value, the URL appear without https://zoho.creator.eu... and when I try to request them from my frontend I can't render them. and if I add https://... at the beginning manually I get an error 401. I also tried to add the https route from the backend but is the same thing.
Here my backend using express
PD: I'm a rookie I know, please don't judge me.
const express = require("express")
const app = express()
const fetch = require("node-fetch")
const bodyParser = require("body-parser");
const cors = require("cors")
const PORT = process.env.PORT || 4000;
app.use(bodyParser.json());
const urlPost = ("https://accounts.zoho.eu/oauth/v2/token?refresh_token=1000.3dbdad6937dc0800c4dcc662cd14d173.86efb18e337989bebb3ff4c05582c94c&client_id=1000.NQL17JHK3Y62Y178TO0E3FQC6MBQJV&client_secret=5d04ad135862e7313377484af55efa1f41c1f49a39&grant_type=refresh_token")
const urlGet = "https://creator.zoho.eu/api/v2/hostienda1/Product-Catalog/report/Product_Details";
app.use(cors())
app.get
const peticion = fetch(urlPost,{
method: 'POST',
redirect: 'follow'
});
peticion
.then((ans)=>{return ans.json()})
.then((resp)=>{
const reslt = resp.access_token;
return app.get("*", async (req,res)=>{
const response = await fetch(urlGet,{
method: "GET",
headers:{
'Authorization':`Zoho-oauthtoken ${reslt}`,
}})
const result = await response.json()
const test = result.data
test.map(function(product){
if (true){
product.Product_Images[0] = "https://creator.zoho.eu" + product.Product_Images[0].display_value
return product.Product_Images[0]
}
})
res.json(test)
})
})
app.listen(PORT, () => {console.log(`Listening on port ${PORT}`)})`
I hope to render my images from my frontend app.
I assume that the download link for the image also requires authentication, in other words, the download requires a request like
GET https://creator.zoho.eu/api/v2/hostienda1/Product-Catalog/report/Product_Details/...jpg
Authorization: Zoho-oauthtoken ...
Such a request cannot be made your frontend, because it does not know the Zoho-oauthtoken.
This means that you must make this request in your backend. Rewrite your middleware so that it retrieves the download link for one image only (currently you return test, which contains many images). Then use the following code to access the image at that download link and return it to the frontend:
var img = await fetch("https://creator.zoho.eu/api/v2/...", // the download link
{headers: {authorization: `Zoho-oauthtoken ${reslt}`}}
);
res.set("content-type", img.headers.get("content-type"));
stream.Readable.fromWeb(img.body).pipe(res);
Related
I'm trying to setup a route for downlading videos for my Vue app backed by an Express server. For some reason, first request that is sent to backend is working as expected and it results in successful file download; however, the subsequent requests fail with Network Error, and I only get a brief error message that looks like this http://localhost:8080/download/videos/1667163624289.mp4 net::ERR_FAILED 200 (OK).
What could be the issue here?
I have an Express.js server (localhost:8000) setup with cors like below:
const express = require("express");
const app = express();
const port = 8000;
const cors = require("cors");
app.use(cors());
app.get("/download/:kind/:fileName",
async (req, res, next) => {
const file = `${__dirname}/public/files/${req.params.kind}/${req.params.fileName}`;
res.download(file);
});
app.listen(port, () => {
});
And my Vue (localhost:8080) component sends that request looks like this:
downloadVideo(fileName) {
const fileName = fileDir.split('/').pop();
const downloadUrl = `/download/videos/${fileName}`;
axios({
method: "get",
url: downloadUrl,
responseType: 'blob',
})
.then((response)=> {
// create file link in browser's memory
const href = URL.createObjectURL(response.data); // data is already a blob
// create "a" element with href to file & click
const link = document.createElement('a');
link.href = href;
link.setAttribute('download', 'my_video.mp4');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(href);
})
.catch((err) => {
// HANDLE ERROR HERE
})
},
I also have a vue config setup to proxy the requests to 8000:
// vue.config.js
module.exports = {
devServer: {
proxy: 'http://localhost:8000',
disableHostCheck: true
},
outputDir: '../backend/public', // build will output to this folder
assetsDir: '' // relative to the output folder
}
Instead of manually setting up the route for downloading files, you can directly set the static files directory as a controller and remove the app.get controller for downloading.
app.use(express.static("public/files"))
Then, on the client side, instead of downloading the file using JS, converting it into a data url, and then downloading it, you can do the following:
downloadVideo(fileName) {
// whatever you want to do to the file name
const parsedFileName = fileName
const link = document.createElement('a');
link.href = parsedFileName;
link.setAttribute('download', 'my-video.mp4'); // or pdf
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
Here is a sample working example
Im not really sure why the first request is going through. But the error looks like a CORS problem.
Basically, your frontend and backend run on different ports, which are treated like a different server altogether by the CORS checks.
I took the following config from the cors package docs
var express = require('express')
var cors = require('cors')
var app = express()
var corsOptions = {
origin: 'http://localhost:8080',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
/*
Or use this, if you want to bypass cors checks - not a safe practice
var corsOptions = {
origin: '*',
optionsSuccessStatus: 200
}
*/
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only localhost:8080'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
The 200 ERR is funny, because normally 200 means OK. But in this case 200 refers to the Preflight request and ERR to the fact that the Ports are different.
There is a good video about cors on youtube
I am testing a GET & POST methods using ThunderClient extension and NodeJs, but I am unable to post the data ...it is showing cannot post data followed by relative path , (Get method is working perfectly) :
Here is my Index.js code:
const express = require('express')
const bodyParser = require('body-parser');
const Blockchain = require('./Blockchain');
const blockchain = new Blockchain();
const app = express();
app.use(bodyParser.json());
app.get('/api/block' , (req,res)=>{
res.json(blockchain);
})
app.post('/api/mine' , (req,res)=>{
const {data} = req.body;
blockchain.addBlock({data});
res.redirect('/api/block');
})
const PORT = 3000;
app.listen(PORT , ()=>{
console.log("app is listening");
})
The default status code of the redirect is 302 which doesn't change
the request method.
Instead it stays as POST
To redirect as a GET method, you have to use 303 status code.
Also see
res.redirect
Temporary redirects
I have a system in place where I have a nodejs app:
app.post('/action', (req, res) => {
...
const option = req.body.option
...
switch (option) {
case 'getinfo':
objectToSend = {"foo": "bar"}
// i tried using
res.json(objectToSend)
// and
res.send(JSON.stringify(objectToSend))
// but neither got me anywhere
break
}
And a website that sends a post request using fetch like this (infoModal is the function I use to display data) (I got the action function sent on discord and have been using it since then, but ive never had to do anything with the response)
let action = async (i) => {
res = await fetch("/action", {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(i)
})
return await res.json
}
action({
option: 'getinfo'
}).then(j => {
infoModal(j.foo,'ok')
})
I can't really fix either the backend or frontend since both have to work for me to confirm it works...
EDIT:
These are my requires, uses and sets:
require('dotenv').config()
...
const express = require('express')
const path = require('path')
var bodyParser = require('body-parser')
let ejs = require('ejs')
const fs = require('fs')
var cookieParser = require('cookie-parser')
var colors = require('colors')
const app = express()
app.use(bodyParser.json())
app.use(cookieParser())
app.set('views', path.join(__dirname, 'frontend'))
app.set('view engine', 'ejs')
One obvious mistake is not executing the jeson() method of the Fetch response. And, although harmless, the second await statement is not really necessary - the async functions anyway wrap what is returned in a promise.
return res.json();
If that doesn't work -
See what your developer console says. It should give you lot of information about the request. If there is an error, follow the info (response code, any error message etc) and try to determine the problem.
Use a rest client such as POSTMAN to verify your backend first. When you know that it can respond well to a proper request, you can try your front-end with confidence and get more understanding on how the response should be handled.
I have a backend for the frontend framework for my app. On local, I am able to hit API URL from React to Node, but when I am deploying my app to prod, it is throwing me 404. I am currently using netlify to deploy my changes and checking them. Below is the code I am using in local to hit the API,
React-
const response = await fetch('http://localhost:5000/fetchData');
Node-
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const port = 5000;
const app = express();
app.use(cors());
app.get('/fetchData', async (req, res) => {
const apiResponse = await axios.get(
'https://jsonplaceholder.typicode.com/posts'
);
res.send(apiResponse.data);
});
app.listen(port, () => {
console.log('Server is up');
});
While building this code for prod, I am removing http://localhost:5000 from the URL on React side and hitting the endpoint like this,
const response = await fetch('/fetchData');
But I am getting 404, could someone help where I am going wrong and how to hit the URL properly?
Your issue is that by using
const response = await fetch('/fetchData');
You are requesting using the frontend domain/ip.
So if the frontend domain was example.com, you are trying to access https://example.com/fetchData, I'm guessing you need to access https://api.example.com/fetchData. I'd recommend using an axios instance to add the base URL once only.
I cant figure why the cors express middleware wont work. cors, express, and ejs are all saved in package.json. The app works fine if I add corsanywhere proxy on the front end but id like to work around this on the server side. any help much appreciated I've been stuck on this.
the api is in the get View/index path
the error is:
Access to fetch at 'https://api.darksky.net/forecast/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
const express = require('express');
const app = express();
const ejs = require('ejs');
const cors = require('cors');
const PORT = process.env.PORT || 3000;
// app.use((req, res, next) => {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'Origin', 'X-Requested-With')
// next();
// });
app.use(cors());
app.use(express.static(__dirname + '/Public'));
app.set('view engine', 'ejs');
app.get('/', cors(), (req, res) => {
res.render(__dirname + '/Views/index')
});
app.listen(PORT, () => {
console.log(`server is listening on ${PORT}`)
});
client side:
it works with the ${proxy} in there but id like to get rid of that
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
long = position.coords.longitude;
lat = position.coords.latitude;
var proxy = 'https://cors-anywhere.herokuapp.com/'
var api = `${proxy}https://api.darksky.net/forecast/042750f3abefefdfe2c9d43cf33ce576/${lat},${long}`;
fetch(api)
.then(response => {
return response.json();
})
.then(data => {
let {temperature, summary, icon,} = data.currently;
temperatureDegree.textContent = Math.floor(temperature);
temperatureDescription.textContent = summary;
locationTimezone.textContent = data.timezone;
setIcons(icon, document.querySelector('.icon'
w
``````
So, if you're trying to access some other service https://api.darksky.net/forecast/ (that you don't control) from your web page, then there is nothing you can do to make CORs work for that. It's up to the api.darksky.net server to decide if CORs is allowed or not. You can't change that.
You could make a request from your web page to your server to ask it to get some data from api.darksky.net for you and then return it back to your webpage (working as a simple proxy). Your server is not subject to any CORs limitations when accessing api.darksky.net. Only browsers are limited by CORs.
And, as you've found, you can also use a proxy service that enables CORs and fetches data for you.
Let's suppose you want to proxy the parts of the darksky API, you could do something simple like this:
const express = require('express');
const app = express();
const request = require('request');
const apiRouter = express.Router();
// maps /api/forecast/whatever to http://api.darksky.net/forecast/developerKey/whatever
// and pipes the response back
const apiKey = "yourAPIKeyHere";
apiRouter.get("/*", (req, res, next) => {
// parse out action and params
// from an incoming URL of /api/forecast/42.3601,-71.0589
// the /api will be the root of the router (so not in the URL here)
// "forecast" will be the action
// "42.3601,-71.0589" will be the params
let parts = req.path.slice(1).split("/"); // split into path segments, skipping leading /
let action = parts[0]; // take first path segment as the action
let params = parts.slice(1).join("/"); // take everything else for params
request({
uri: `https://api.darksky.net/${action}/${apiKey}/${params}`,
method: "get"
}).pipe(res);
});
app.use("/api", apiRouter);
app.listen(80);
Now, when you send this server, this request:
/api/forecast/42.3601,-71.0589
it will request:
https://api.darksky.net/forecast/yourAPIKeyHere/42.3601,-71.0589
and pipe the result back to the caller. I ran this test app and it worked for me. While I didn't see anything other than forecast URLs in the darksky.net API, it would work for anything of the format /api/someAction/someParams.
Note, you probably do NOT want to enable CORS on your server because you don't want other people's web pages to be able to use your proxy. And, since you're just sending requests to your own server now, you don't need CORS to be able to do that.