Apollo client does not send headers when used in nodejs environment - node.js

I've got a client-side app that uses apollo/client and I'have a few server-side function where also use the client to do some light queries and mutations.
I've recently implemented jwt integration and now my server-side calls fail because of what I presume is missing headers.
here is my client setup:
const wsLink = new GraphQLWsLink(
createClient({
url: process.env.API_URL_WS,
webSocketImpl: isNode ? ws : null,
connectionParams: async () => {
const { id_token } = await auth.getSession();
return {
headers: {
Authorization: `Bearer ${id_token}`,
},
};
},
})
);
const authLink = setContext(async (_, { headers }) => {
const { id_token } = await auth.getSession();
return {
headers: {
...headers,
Authorization: `Bearer ${id_token}`,
},
};
});
const httpLink = new HttpLink({
uri: process.env.API_URL_HTTP,
});
const splitLink = split(
({ query, ...rest }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
and here is how I am using it in the server-side function to make a mutation
await client
.mutate({
mutation: gql`
mutation InsertSubscriptionId(
$id: uuid!
$stripe_subscription_id: String!
) {
update_user_by_pk(
pk_columns: { id: $id }
_set: { stripe_subscription_id: $stripe_subscription_id }
) {
id
}
}
`,
variables: {
id: session.metadata.hasura_user_id,
stripe_subscription_id: session.subscription,
},
context: {
headers: {
Authorization: req.cookies.access_token,
},
},
})
.then((result) => console.log(result))
.catch(console.log);
this call fails even if I use the users access token or apply an admin token for access to the DB.
As a workaround I've resorted to using fetch for this mutation (which works). Any idea why headers are not respected?
My workaround with admin access secret:
fetch(process.env.API_URL_HTTP, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-hasura-admin-secret": process.env.GATSBY_HASURA_ADMIN_SECRET,
},
body: JSON.stringify({
query: `
mutation InsertSubscriptionId(
$id: uuid!
$stripe_subscription_id: String!
) {
update_user_by_pk(
pk_columns: { id: $id }
_set: { stripe_subscription_id: $stripe_subscription_id }
) {
id
}
}
`,
variables: {
id: session.metadata.hasura_user_id,
stripe_subscription_id: session.subscription,
},
}),
})
.then((res) => res.json())
.then((result) => console.log(result));

In this case the session was lost after a few jumps between my app and payment provider. As user can't be authenticated when session is lost, I've resorted to authenticate the admin user for this ssr function

Related

Periodically fetching the auth token while the async function is running

I have this authFetch function that fetches the token and updates it in mongo and token variable.
const authFetch = async () => {
let authCollection = client.db("WheelPros_data").collection("auth");
TokenFromDB = await authCollection.find({ _id: 1 }).toArray();
({ accessToken: token, time: lastFetchedAuth } = TokenFromDB[0] || [{ accessToken: null, time: 0 }])
// console.log(new Date().getTime() / 1000 - lastFetchedAuth)
let axiosConfig = {
headers: {
"Content-Type": "application/json",
accept: "application/json",
},
};
await axios({
method: "POST",
data: {
userName: process.env.WHEELPROS_USERNAME,
password: process.env.PASSWORD,
},
url: `${process.env.PROD_API_URL}/auth/v1/authorize`,
axiosConfig,
})
.then(async (res) => {
const { accessToken } = res.data;
authCollection.updateOne(
{ _id: 1 },
{
$set: {
accessToken: accessToken,
time: new Date().getTime() / 1000,
Date: new Date(),
},
}, {
upsert: true
}
);
console.log("newAccessToken : ", accessToken)
token = await accessToken
console.log("inside token var = ", token)
console.log("------------------Auth Updated------------------");
})
.catch((err) => {
console.log("AXIOS ERROR: ", err);
});
}
await authFetch()
// console.log("ourside token var = ", token)
setInterval(authFetch, 3590000)
Problem arises when i call another async function to fetch the submodels. After 1 hour the token expires and that is why we have setInterval function to update the token but it does not update the token and i get hit with the 403 error
Below is the modelFetch function
const fetchSubmodels = async () => {
const modelDataFromDB = await collection
.find({ modelData: { $exists: true } })
.toArray();
console.log(modelDataFromDB.length)
modelDataFromDB.forEach(async (modeldata) => {
// await fetchToken().catch(console.error)
let dataSearch = modeldata.modelData.split(';')
await axios
.get(
`${process.env.PROD_API_URL}/${dataSearch[0]}/makes/${dataSearch[1]}/models/${dataSearch[2]}/submodels`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
)
.then(async function ({ data }) {
await collection.updateOne(
{ modelData: modeldata.modelData },
{
$set: {
submodels: data
},
}
);
// handle success
console.log(`pushing the submodels ${data} to ${modeldata.modelData} `)
})
.catch(async function (error) {
// handle error
console.log(error);
})
})
}
await fetchSubmodels()
clearInterval(authFetch)
Any insights would be helpful!
Like array.map, array.forEach does not await the result of the function that it calls. In your case, this means that the axios.get statements for each entry in modelDataFromDB send out all requests synchronously, while token still has its original value. (Therefore they should never lead to 403, if they reach the backend in time.)
What's more, fetchSubmodels does not await anything and therefore resolves after all these requests have been sent out. At that point in time await fetchSubmodels() is completed, therefore clearInterval(authFetch) is executed almost immediately, and the setInterval has no effect.
While this does not yet explain the 403 responses, it is certainly not what you intended.
If you replace
modelDataFromDB.forEach(async (modeldata) => {
...
})
with
for (var modeldata of modelDataFromDB) {
...
}
the requests will be sent consecutively, and fetchSubmodels will await the response of the last request.

How to change fetch API link for deployment?

This is a method to fetch data from an API. I have used the MERN stack. I hosted this application on Heroku. The problem is that I can't understand how to change the link to fetch the API because on Heroku the app is running at a different port every time.
const SendData = async (e) => {
e.preventDefault();
await fetch('http://localhost:4000/Login',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Email,
Password
})
})
.then(HandleErrs)
.then((res) => {
return res
}).then((res)=>{
const {UserName}=res;
// console.log(UserName);
setCredentials({
UserName,
Email,
Password
})
})
.catch((err) => {
getErr(ERR);
console.log(err)
})
}
Probably this will not give a direct answer to your question, but its a good practice,
What we normally do is create a separate file for api details
for ex:
api.js
//REACT_APP_SERVER_URL is a env variable in .env file
const rootUri = process.env.REACT_APP_SERVER_URL
? process.env.REACT_APP_SERVER_URL
: 'http://localhost:3001';
const apiVersion = 'v1';
const apiBasePath = `${rootUri}/api/${apiVersion}`; //this is the base path
export const userApis = {
all: {
url: `${apiBasePath}/users`,
method: 'GET',
},
update: {
url: `${apiBasePath}/user`,
method: 'POST',
},
};
this is how we use it
const fetchAllUser = () => {
const api = userApis.all;
fetch(api.url, {
method: api.url.method,
body: JSON.stringify(request)
}...

Getting null while fetching data in reactjs

I want to fetch a details of the one student I wrote the method for that . It working with postman but not working when i connect it with frontend
frontend controller for fetching student details
export const getStudent = (studentId, token) => {
return fetch(`${API}/student/${studentId}`, {
method: "GET",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
setting the values
const [student, setStudent] = useState({
fullName: "",
admissionNumber: "",
rollNumber: "",
age: "",
gender: "",
faculty: "",
email: "",
});
const preload = (studentId) => {
getStudent(user._id, token, studentId).then((student) => {
if (student?.error) {
setStudent(console.log(student.error));
} else {
setStudent(student);
}
});
};
useEffect(() => {
preload();
}, []);
backend
exports.getStudent = (req, res) => {
return res.json(req.student);
};
fetching status is ok but response is null
Updated Code
const preload = (studentId) => {
getStudent(studentId, token).then((data) => {
if (data.error) {
setStudent(console.log(data.error));
} else {
setStudent(student);
}
});
};
preload needs only two parameters userid and token
In useEffect it needs to pass the studentId to get the details
useEffect(() => {
preload(match.params.studentId);
}, []);

Why can't I get a usable response from graphql with axios?

I'm successfully authenticating with the server, sending my graphql request and receiving a response. However the response isn't quite right.
This is just a simple nodejs, axios, graphql situation. I can get a correct respose via postman or curl but not with node. I've tried fetch as well to no avail. Am I doing something wrong? This is for the producthunt api, the api explorer is located at here
require('dotenv').config()
const axios = require("axios");
const PH_KEY = process.env.PH_KEY;
const PH_SECRET = process.env.PH_SECRET;
const BASE_URL = process.env.BASE_URL;
const AUTH_URL = process.env.AUTH_URL;
const GRAPHQL_URL = process.env.GRAPHQL_URL;
const PH = axios.create({
baseURL: BASE_URL,
responseType: 'json',
headers: {
// 'Accept': 'application/json, text/plain, gzip, deflate, */*',
'Content-Type': 'application/json'
},
})
let TOKEN = 'Bearer '
const getAuth = async () => {
return await PH({
url: AUTH_URL,
method: "post",
data: {
client_id: `${PH_KEY}`,
client_secret: `${PH_SECRET}`,
grant_type: "client_credentials"
}
})
.then((res) => {
console.log('auth status ', res.status)
return res.data.access_token
})
.then((res) => {
TOKEN += res
console.log(TOKEN)
})
.catch((err) => {
console.log(err)
})
}
const getHunt = async () => {
await getAuth()
PH.defaults.headers.common['Authorization'] = TOKEN
return await PH({
url: GRAPHQL_URL,
method: 'post',
data:
{
query: `
query posts {
posts(order: RANKING, first: 1) {
edges {
node {
name
votesCount
user {
name
}
createdAt
}
}
}
}
`
}
})
.then((res) => {return res})
.then((res) => {
console.log(res.data)
return res.data
})
.catch((err) => {
console.log(err)
})
}
const Main = async () => {
await getHunt()
}
Main()
This is the output I receive in node:
[Running] node "/Users/fireinjun/Work/YAC/yac-ph-tophuntfunction/app.js"
auth status 200
Bearer #################################################
{ data: { posts: { edges: [Array] } } }
Here's what I'm expecting:
{
"data": {
"posts": {
"edges": [
{
"node": {
"name": "Trends by The Hustle",
"votesCount": 125,
"user": {
"name": "Jack Smith"
},
"createdAt": "2019-06-04T07:01:00Z"
}
}
]
}
}
}
I was accessing the data incorrectly! Apparently I needed to see the res.data.data.posts.edges[0]

Is any library of nodejs work with GitHub API v4 exist?

Thank you guys cause reading my question. The title is exactly what i wanna known.
Hope it do not cost your time much.
Some of the most common graphql client are graphql.js and apollo-client. You can also use the popular request module. The graphql API is a single POST endpoint on https://api.github.com/graphql with a JSON body consisting of the query fields and the variables fields (if you have variables in the query)
Using graphql.js
const graphql = require('graphql.js');
var graph = graphql("https://api.github.com/graphql", {
headers: {
"Authorization": "Bearer <Your Token>",
'User-Agent': 'My Application'
},
asJSON: true
});
graph(`
query repo($name: String!, $owner: String!){
repository(name:$name, owner:$owner){
createdAt
}
}
`)({
name: "linux",
owner: "torvalds"
}).then(function(response) {
console.log(JSON.stringify(response, null, 2));
}).catch(function(error) {
console.log(error);
});
Using apollo-client
fetch = require('node-fetch');
const ApolloClient = require('apollo-client').ApolloClient;
const HttpLink = require('apollo-link-http').HttpLink;
const setContext = require('apollo-link-context').setContext;
const InMemoryCache = require('apollo-cache-inmemory').InMemoryCache;
const gql = require('graphql-tag');
const token = "<Your Token>";
const authLink = setContext((_, {
headers
}) => {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
});
const client = new ApolloClient({
link: authLink.concat(new HttpLink({
uri: 'https://api.github.com/graphql'
})),
cache: new InMemoryCache()
});
client.query({
query: gql `
query repo($name: String!, $owner: String!){
repository(name:$name, owner:$owner){
createdAt
}
}
`,
variables: {
name: "linux",
owner: "torvalds"
}
})
.then(resp => console.log(JSON.stringify(resp.data, null, 2)))
.catch(error => console.error(error));
Using request
const request = require('request');
request({
method: 'post',
body: {
query: `
query repo($name: String!, $owner: String!){
repository(name:$name, owner:$owner){
createdAt
}
} `,
variables: {
name: "linux",
owner: "torvalds"
}
},
json: true,
url: 'https://api.github.com/graphql',
headers: {
Authorization: 'Bearer <Your Token>',
'User-Agent': 'My Application'
}
}, function(error, response, body) {
if (error) {
console.error(error);
throw error;
}
console.log(JSON.stringify(body, null, 2));
});

Resources