Make cache persist even if Node process dies - javascript

My backend, Nodejs, has to make some API GET request calls to external services to retrieve data and serve as a webpage. These API calls are being cached.
Since I'm using Heroku, this data is being removed every time the apps enter to hibernate state. Is there any cache library that persists? Currently I'm using lru-cache and axios-extensions.
const axios = require('axios');
const { cacheAdapterEnhancer } = require('axios-extensions');
const LRUCache = require("lru-cache")
const options = {
defaultCache: new LRUCache({ maxAge: 60 * 60 * 1000, max: 100 })
}
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000,
adapter: cacheAdapterEnhancer(axios.defaults.adapter)
}
)
getData: async () => {
try {
const response = await http.get(url, config)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}

As suggested by #giankotarola in comments, I needed to use cache DB to solve the problem. Finally I managed to make it work using Redis! My final code:
const axios = require('axios')
const redis = require('redis')
// create and connect redis client to local instance.
const client = redis.createClient()
// Print redis errors to the console
client.on('error', (err) => {
console.log("Error " + err)
});
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000
}
)
let config = {
headers: {
Authorization: `Bearer ${API_KEY}`,
}
}
let url = 'https://example.com'
module.exports={
getData: async () => {
try {
// Try to get response from Redis store
const response = await client.get(url)
return responseJSON = JSON.parse(response)
} catch (err) {
try {
const response = await http.get(url, config)
const data = response.data
// Save response in Redis store
client.setex(url, 60 * 60 * 1000, JSON.stringify(data));
return data
} catch (error) {
console.log(error)
}
}
}
}

Related

How can I persist auth state in a nodejs app

So, I am learning NodeJs by creating this backend that fetches some data from a third-party API, the API requires auth. I couldn't figure out how to avoid sending an auth request to the third-party API whenever I wanted to fetch data from it. is there any way I could store the auth state in the app?
const axios = require("axios");
const AUTH_URL = process.env.AUTH_URL;
const REPORT_BASE_URL = process.env.REPORT_BASE_URL;
const X_API_KEY = process.env.X_API_KEY;
const getCompanies = async (req, res) => {
let idToken;
// auth
const authPayload = JSON.stringify({
// ...
});
const config = {
method: "post",
// ...
};
try {
const { data } = await axios(config);
idToken = data.idToken; // set idToken necessary for fetching companies
} catch (error) {
console.log(error);
}
// get company by full text query
const { full_text_query } = req.query;
if (!full_text_query)
return res.send("No full_text_query parameter provided");
try {
const { data } = await axios.get(
`${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`,
{
headers: {
"x-api-key": X_API_KEY,
Accept: "application/json",
authorization: idToken,
},
}
);
res.status(200).json(data);
} catch (error) {
console.log(error);
}
};
module.exports = {
getCompanies,
};
You can break out a function like fetchIdToken and store a Promise that resolves with the idToken in memory.
let idTokenPromise;
async function fetchIdToken () {
if (idTokenPromise) return idTokenPromise;
return idTokenPromise = new Promise(async (resolve) => {
...
resolve(data.idToken);
})
}
You can then use await fetchIdToken() at the start of getCompanies.
You can also just store the idToken in memory. This is slightly simpler, but does mean that you can have a race-condition when multiple getCompanies requests happen at the same time:
let idToken;
async function fetchIdToken () {
if (idToken) return idToken;
...
idToken = data.idToken;
return idToken;
}

MERN pagination and filtering in same controller

How to do pagination and filtering at the backend in the same controller?
Filter service:-
const filterPosts = async (filterData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
data: {
filterData,
},
};
const response = await axios.get(API_URL, config);
return response.data;
}
Route:-
router.route("/").get(protect, getPosts);
Controller:-
I cant seem to send the filterData from service to this controller, how to achieve it?
const getPosts = asyncHandler(async (req, res) => {
console.log("filter data:- ", req.body); // This Part is undefined.
//pagination
const PAGE_SIZE = 6;
const PAGE = parseInt(req.query.page || "0");
const total = await Post.countDocuments({ user: req.user.id });
const AllPosts = await Post.find({ user: req.user.id })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * PAGE)
.sort({ createdAt: -1 });
const totalPages = Math.ceil(total / PAGE_SIZE);
res.status(200).json({ totalPages, Allposts });
});
console.log("filter data:- ", req.body); // This Part is undefined.
You can send this data using config.params in Get request or use Post request if you want to use config.data.
Sometimes XHR / Fetch do not allow payload in Get request.
As per this axios doc, sending data as request body is only applicable for request methods PUT, POST, DELETE , and PATCH.
You can also refer to this issue for reference : Link
const filterPosts = async (filterData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
filterData,
},
};
const response = await axios.get(API_URL, config);
return response.data;
}

Getting undefined in API call via Fetch - Express Nuxt

I am extremely stuck on a task using Express, API, Fetch.
I am using Nuxt + Shopify API endpoints to grab data such as orders like below
This is my express API Endpoint.
The results should return an array of objects ( orders )
const bodyParser = require('body-parser')
const app = require('express')()
const axios = require('axios')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.post('/getJSON', async (req, res) => {
const { customerID } = req.body
const id = Buffer.from(customerID, 'base64')
.toString('binary')
.split('gid://shopify/Customer/')
.pop()
console.log('id is', id)
const endpoint = `https://test.myshopify.com/admin/api/2020-07/customers/${id}/orders.json`
try {
const response = await axios.get(endpoint, {
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': '*****************'
}
})
res.status(200).json(response.data.orders)
} catch (error) {
res.status(500).send(error)
}
})
module.exports = app
Now, in my Nuxt store.js, I am using fetch to make a post requests to that endpoint above.
async function apiPost(endpoint, { data }) {
await fetch(`/api${endpoint}/getJSON`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then(async (res) => {
const contentType = res.headers.get('content-type')
if (contentType.startsWith('text/html')) {
return res.text()
}
if (contentType.startsWith('application/json')) {
await res.json()
}
})
}
When I console.log res.json(), it is a promise
Since it is a promise, I wanted to see what the results were anyways.
res.json().then((resp) => {
console.log('resp is', resp)
})
Turns out the rest is an array of objects.
However, I do not know how to return this object correctly, as the "ordersResponse" below is always undefined !
Below is a function calling the apiPost passing in the 'endpoint'. However, the orderResponse is undefined. I have tried solutions, but all of them end up as orderResponse being undefined.
async fetchOrders({ state, dispatch, commit }, payload) {
try {
const ordersResponse = await apiPost('/customer-orders', {
data: { customerID: state.customer.id }
})
console.log('ordersResponse', ordersResponse) // **undefined**
} catch (error) {
console.error(error)
throw error
}
},
Any help is appreciated
It looks like the apiPost function needs to make a return from within the 'application/json' if block.
if (contentType.startsWith('application/json')) {
return res.json()
}
You should then receive data back when calling it for the ordersResponse variable.

Handling query in React and Express

Somewhere in my React application I used REST API to send request to the server. In my URL I want to use query (in the postIconsTransition method), but when I send a request to the server, server tells me could not found this URL (I build this error in my server). If I use this URL without any query the request in the postIconsTransition method works fine. postId and authContext.userId work fine, can anyone tell me what's wrong with my code?
In my component where I send request:
const likeHandler = async () => {
setLike(prevState => !prevState);
if (!like) {
try {
await postIconsTransition(props.postId, "inc");
} catch (error) {}
} else {
try {
await postIconsTransition(props.postId, "dec");
} catch (error) {}
}
};
In useHttp.js component:
const postIconsTransition = async (postId, addtionAddress) => {
return await transitionData(
`http://localhost:5000/post/${postId}/${authContext.userId}?t=${addtionAddress}`,
"POST",
null,
{ Authorization: `Bearer ${authContext.token}` }
);
};
transitionData method:
const transitionData = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const abortController = new AbortController();
activeHttpRequest.current.push(abortController);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: abortController.signal
});
const responseData = await response.json();
activeHttpRequest.current = activeHttpRequest.current.filter(
reqCtrl => reqCtrl !== abortController
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (error) {
modalContext.err(error);
setIsLoading(false);
throw error;
}
},
[modalContext.err]
);
In Express:
router.post(
"/:postId/:userId?t=inc",
tokenChecker,
postController.updateLikesComments
);
router.post(
"/:postId/:userId?t=dec",
tokenChecker,
postController.updateLikesComments
);
All of them work fine but when I use query in my URL, it's not working any more.
You don't specify query parameters in express routes like that. Just send them. Express can read it.
router.post(
"/:postId/:userId",
tokenChecker,
postController.updateLikesComments
);
// Notice that you don't need the other one.
and in your controller check the parameter
// controller's code
const t = req.query.t;
if (t === 'inc') {
// do what you want here
}
if (t === 'dec') {
// do what you want here
}

Using result of one function as a variable in another - node.js

I'm writing a node.js script to generate a GitHub installation access token. Here's what I've got:
const axios = require("axios");
var fs = require('fs');
var jwt = require("jsonwebtoken");
var gitInstallationAccessToken = {
genJWTToken: function(callback) {
var private_key = fs.readFileSync("/path/to/my/pemfile.pem");
const now = Math.round(Date.now() / 1000);
const payload = {
iat : now,
exp : now + (10 * 60),
iss : 7233
};
const token = jwt.sign(payload, private_key, { algorithm: 'RS256' })
callback(token);
},
genInstallationAccessToken: function(token, callback) {
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
var instance = axios({
method: "post",
url: "https://api.github.com/installations/:installation_id/access_tokens",
headers: {
"Accept" : "application/vnd.github.machine-man-preview+json",
"Authorization" : `Bearer ${jwt}`
}
})
.then(function(response) {
console.log("Response: ",response.data);
callback(response);
})
.catch(function(error) {
console.warn("Unable to authenticate");
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response) {
console.warn(`Status ${error.response.status}`);
console.warn(`${error.response.data.message}`);
}
});
}
}
module.exports = gitInstallationAccessToken;
gitInstallationAccessToken.genInstallationAccessToken(function(response) {
console.log("response: ", response)
});
My JWT token is getting generated by genJWTToken. I can see that if I add a console.log("Token: ", token) before the callback in genJWTToken.
I now need to use that token in genInstallationAccessToken but I'm clearly calling it wrong. As the following returns undefined:
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
How do I fix this?
I think you should consider refactoring this and use chained promises it will be easier to understand and control..
Something like this:
function getToken() {
return new Promise(function(resolve, reject) {
resolve('token')
})
}
function chainPromise() {
var token
getToken().then((response) => {
token = response
console.log(token)
}).then(() => {
console.log('I am here and also see: ', token)
})
}
chainPromise()
You should then be able to track down the path of your token quite easily

Categories

Resources