Earlier, I was able to pipe the response of another api call to Next.js api response as follows
export default async function (req, res) {
// prevent same site/ obfuscate original API
// some logic here
fetch(req.body.url).then(r => {
r.body.pipe(res);
}).catch(err => {
console.log(err);
res.status(500).send("Invalid Url");
})
}
It worked fine. But now the response.body from fetch API does not have pipe method. Rather, it has pipeTo and pipeThrough methods. And the Next.js res:NextApiResponse, is not assignable to WritableStream.
I also tried creating blob (await r.blob()) and using res.send(blob) and res.send(blob.strem()). It seems to work at first but the data received by front end is not proper (Basically fetch().then((res) => res.blob()).then((blob) => URL.createObjectURL(blob))) will give corrupt result).
Related
In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.
I am building an application that needs a currency converter API. I am having trouble with returning the data to the front end. (My console.log is showing the desired data to be returned, however I keep getting TypeError: res.json is not a function)
Please see the code below
App.post('/rates', async (res) => {
console.log('route was hit')
const response = await axios.get(`https://v6.exchangerate-api.com/v6/${process.env.API_KEY}/latest/CAD`)
console.log(response)
res.json(response)
})
The callback of post takes two arguments, first is request and second one is response. Your code should look like:
App.post('/rates', async (request, res) => {...
I have a node.js server that downloads images from my s3 bucket and serves the images to my React client. I want to manage the case when the wrong file key is sent to the S3 client.
In order to do so, I am intentionally sending a wrong key from React. I am expecting to receive an error that I could catch and render my view accordingly.
Unfortunately, the s3 API is not returning an error, so all the points in my code where I intend to catch the error are being passed and I cant render a view when I get the image and another view when I get an error.
My code looks like so:
//s3Connect.js
download: async (fileKey)=>{
const downloadParams={
Key:fileKey, //this is a wrong key like 123 or anything
Bucket:bucketName
}
const data = s3.getSignedUrlPromise('getObject', downloadParams);
return data
}
//adminPanel.js
//I call the above function below
getBackgroundCheck:async (req,res)=>{
const readStream = await s3.download(req.params.key).then(url=> {
console.log(url)
res.send({url})
})
.catch(error=>{
console.log(error)
res.send('the error is',error) //since I sent a wrong key I expect to catch an error here
} )
}
Now in the client side. I use React and the fetch method to retrieve the image
const getBackgroundFile = async (e) => {
try {
e.preventDefault()
const response = await fetch(`http://localhost:3003/adminPanel/getbackgroundcheck/${id}`)
console.log('this is the response ',response)
const parseResponse = await response.json()
console.log('this is the parseResponse', parseResponse)
setImage(parseResponse)
setShowBackImage(true)
}
catch (error) {
console.log('this is the error',error) //again I expect to see this in console
}
}
Finally:
What do I get with the console.logs from the above function
this is the response Response {type: 'cors', url: 'http://localhost:3003/adminPanel/getbackgroundcheck/nll%C3%B1', redirected: false, status: 200, ok: true, …}
So as you can see I get a 200 status. So how can I manage an error if I get a 200 ok status when I know that the response is failed because my server could not find the image in the s3 bucket and serve it to my client.
Creating a pre-signed URL is an operation that cannot fail, in normal operation. There's no actual API call behind the creation of a pre-signed URL - it's a simple local signing exercise in the SDK. It will happily generate pre-signed URLs for objects that don't exist, for example.
If you wan't to detect vending URLs for non-existent S3 objects, you'll have to do that some other way. For example, by proxying the downloads or pushing responsibility to the client.
I am starting to move the logic away from the routes in the express application, into a service provider. One of these routes deals with streams, not only that, it also requires some more logic to take place once the stream is finished. Here is an example of the express route.
router.get("/file-service/public/download/:id", async(req, res) => {
try {
const ID = req.params.id;
FileProvider.publicDownload(ID, (err, {file, stream}) => {
if (err) {
console.log(err.message, err.exception);
return res.status(err.code).send();
} else {
res.set('Content-Type', 'binary/octet-stream');
res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"');
res.set('Content-Length', file.metadata.size);
stream.pipe(res).on("finish", () => {
FileProvider.removePublicOneTimeLink(file);
});
}
})
} catch (e) {
console.log(e);
res.status(500).send(e);
}
})
And here is one of the functions inside the service provider.
this.publicDownload = async(ID, cb) => {
const bucket = new mongoose.mongo.GridFSBucket(conn.db, {
chunkSizeBytes: 1024 * 255,
})
let file = await conn.db.collection("fs.files")
.findOne({"_id": ObjectID(ID)})
if (!file|| !file.metadata.link) {
return cb({
message: "File Not Public/Not Found",
code: 401,
exception: undefined
})
} else {
const password = process.env.KEY;
const IV = file.metadata.IV.buffer
const readStream = bucket.openDownloadStream(ObjectID(ID))
readStream.on("error", (e) => {
console.log("File service public download stream error", e);
})
const CIPHER_KEY = crypto.createHash('sha256').update(password).digest()
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV);
decipher.on("error", (e) => {
console.log("File service public download decipher error", e);
})
cb(null, {
file,
stream: readStream.pipe(decipher)
})
}
}
Because it is not wise to pass res or req into the service provider (I'm guessing because of unit test).I have to return the stream inside the callback, from there I pipe that stream into the response, and also add an on finish event to remove a one-time download link for a file. Is there any way to move more of this logic into the service provider without passing res/req into it? Or am I going at this all wrong?
Is there any way to move more of this logic into the service provider without passing res/req into it?
As we've discussed in comments, you have a download operation that is part business logic and part web logic. Because you're streaming the response with custom headers, it's not as simple as "business logic get me the data and I'll manage the response completely on my own" like many classic database operations are.
If you are going to keep them completely separate while letting the download process encapsulate as much as it can, you would have to create a higher bandwidth interface between your service provider and the Express code that knows about the res object than just the one callback you have now.
Right now, you only have one operation supported and that's to pass the piped stream. But, the download code really wants to specify the content-type and size information (that's where it's known inside the download code) and it wants to know when the write stream is done so it can do its cleanup logic. And, something you don't show is proper error handling if there's an error while streaming the data to the client (with proper cleanup in that case too).
If you want to move more code into the downloader, you'd have to essentially make a little interface that allows the service code to drive more than one operation on the response, but without having an actual response object. That interface doesn't have to be a full response stream. It could just have methods on it for getting notified when the stream is done, starting the streaming, setting headers, etc...
As I've said in the comments, you will have to decide if that actually makes the code simpler or not. Design guidelines are not absolute. They are things to consider when making design choices. They shouldn't drive you in a direction that gives you code that is significantly more complicated than if made different design choices.
I am creating a flight search app that makes external api calls. The data will be reorganized and returned in search results component.
On submit, the data is sent to express and takes about 10 seconds or more to complete all the api calls.
I think I need a loader at some point for during the delay of api calls, but also I am unsure of how to send/render the data.
As it stands, I have two pages home.js- '/' where i make the search and is sent to the server side, and prices.js- '/search' which when loaded fetches the data from the json file. but i do not have them connected
Both files work but I need to connect them. When I press submit, the user inputs are sent to server and the api calls are made but in order to see the results i have to manually refresh localhost:3000/search.
In express app after all the api calls, I tried res.redirect method, however the error given was setting the headers after sent to the client.
In react, I tried after submitting, to redirect to the search page. However I could not get it to redirect and also as soon as the /search page is called, it fetches the data from the file. This will happen before the api has finished writing to file and therefore the previous search results will render.
--in app.js
setTimeout(() => {
Promise.all([promise, promise2]).then(values => {
return res.redirect('http://localhost:3000/search');
});
}, 25000);
I had to wrap the api calls in promises so it will only redirect after all is written to file.
(in react prices.js)
componentDidMount() {
fetch('/search')
.then(res => {
return res.json()
})
.then(res => {
console.log(res.toString());
this.setState({flightData: res});
})
.catch(error => console.log(error));
}
home.js
home.js
```
onChange = (e) => {
this.setState({
originOne: e.target.value, originTwo: e.target.value});
};
onSubmit = (e) => {
e.preventDefault();
const { originOne, originTwo ,redirectToResult} = this.state;
};
```
app.js - I have all the functions calling each other in a waterfall style ( probably not the best way I know)
app.post('/', function getOrigins(req,res) {
var OrigOne;
var OrigTwo;
....
function IataCodeSearchOrg1(res, OrigOne, OrigTwo) {
...
findPrices(res,A,B)
}
function findPrices(res, A, B) {
promise = new Promise(function (resolve) {
...
}
}
All the methods are called within eachother. The api calls are in a loop and after each iteration they are written to the json file.
All these functions are in the app.post method and i tried to res.redirect but it did not work.
EDIT:
You can't redirect server-side from an XHR request. You would need to redirect client-side.
e.g.
componentDidMount() {
fetch('/search')
.then(res => res.json())
...
.then(() => window.location = '/myredirecturl')
}