GraphQL / Apollo - Can't get NetworkError - express

I'm unable to get any network error with my Apollo Client, only GraphQL errors.
I have the client set up like so:
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const client = new ApolloClient({
link: ApolloLink.from([
errorLink,
new HttpLink({
uri: 'http://localhost:4000/graphql'
}),
]),
});
On the server i'm making a request which i parse like this:
.then(res => {
if (res.ok) {
return { blah: res.json(), count };
}
throw new Error();
})
.catch(error => {
throw new AuthenticationError('Must Authenticate');
});
The AuthenicationError just bubbles up to the Client as a GraphQL Error ([GraphQL error]: Message: Must Authenticate,, i basically just want to be able to get the HTTP Status Code from the server request, on the client.
Thanks

Your client side implementation looks correct.
I guess you are using express with some graphl middleware to handle the requests. The main thing is that you would need to handle the authentication process before the graphql middleware comes in to play.
So the authentication is a graphql mutation which you handle directly. In my case it looked kind of like this:
...
const app = express();
app.use(
"/graphql",
bodyParser.json(),
(request, response, next) => authenticationMiddleware(request) // authentication is handled before graphql
.then(() => next())
.catch(error => next(error)),
graphqlExpress(request => {
return {
schema: executable.schema,
context: {
viewer: request.headers.viewer
},
};
}),
);
...
app.listen(...)
Another possibility would be to add the status code to the error response in the graphql errors.
But that depends on the npm package you are using.
e.g. formatError in apollo-server-graphql
https://www.apollographql.com/docs/apollo-server/api/apollo-server.html#constructor-options-lt-ApolloServer-gt

Related

react native iOS Apollo - TypeError: Network request failed - URI accessible from outside the app

I've got a graphql URI that I need to query from my react-native App. This URI is public and I've got access to its schema/structure when I simply type the URI in my browser.
As soon as I try to query it from my code, I get the [TypeError: Network request failed] error (logs are created in the function that builds my ApolloClient).
I've checked the URI a million time, it's the same as the one I put in my browser, and the one I've used in the past to successfully query the DB.
This is the client-building function:
export function initServices({
uri,
authToken,
mockMeanDelay = 400,
mock = false,
mockScenarios = [],
}: Options): Services {
let mockRemoteController = null;
let linkToOutsideWorld: ApolloLink;
const messageBus = createMessageBus();
const terminatingLink = createUploadLink({
uri: CORRECT_URI_HERE,
})
const authLink = setContext(async (_, { headers }) => {
const token = await authToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
linkToOutsideWorld = from([authLink, withCustomScalars(), terminatingLink]);
const errorReportingLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
// eslint-disable-next-line no-console
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const link = ApolloLink.from([errorReportingLink, linkToOutsideWorld]);
const fragmentMatcher = new IntrospectionFragmentMatcher({
// #ts-ignore
introspectionQueryResultData: introspectionResult,
});
const apolloClient = new ApolloClient({
link,
defaultOptions: {
watchQuery: {
// We prefer using a `cache-and-network` policy so that screens
// are always in sync with backend
// Otherwise, the default policy would not fetch the server
// data from server if the result of query is already in cache
fetchPolicy: 'cache-and-network',
},
},
cache: new InMemoryCache({
cacheRedirects: {
Query: {
// #ts-ignore issue in typing of cacheRedirects
userById: (_, { userId }: QueryUserByIdArgs, { getCacheKey }) =>
getCacheKey({ __typename: 'User', id: userId }),
// #ts-ignore issue in typing of cacheRedirects
gatheringSpaceById: (
_,
{ gatheringSpaceId }: QueryGatheringSpaceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringSpace',
id: gatheringSpaceId,
}),
// #ts-ignore issue in typing of cacheRedirects
gatheringInstanceById: (
_,
{ gatheringInstanceId }: QueryGatheringInstanceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringInstance',
id: gatheringInstanceId,
}),
},
},
fragmentMatcher,
}),
});
return { apolloClient, messageBus, mockRemoteController };
}
When I replace the URI with another publicly available one, it seems to work so my guess is that there's an issue with the back-end side. But how is it possible that I have full access to the schema and queries with my browser?
Any tips to help debugging are welcome too!
Thanks for your help!

Call Nextjs API from within Netlify function

I got a serverless Netlify function like this:
exports.handler = async function(event, context) {
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World"})
};
}
When called by this url <site-name>/.netlify/functions/helloworld
I do get the message {"message":"Hello World"}
I also got a pages/api/mailingList.js Nextjs API endpoint:
const axios = require('axios');
export default async function handler(req, res) {
//console.log(req.query.mail);
if (req.method === "PUT") {
axios
.put(
"https://api.sendgrid.com/v3/marketing/contacts",
{
contacts: [{ email: `${req.query.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_LIST_ID],
},
{
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
},
}
)
.then((result) => {
res.status(200).send({
message:
"Your email has been successfully added to the mailing list. Welcome 👋",
});
})
.catch((err) => {
res.status(500).send({
message:
"Oups, there was a problem with your subscription, please try again or contact us",
});
console.error(err);
});
}
}
This mailing list API endpoint, do work when using curl from the terminal with PUT as the method:
curl -X PUT -d mail=helloworld#gmail.com https://netlify.app/api/mailingList
The API endpoint also work from the URL (/api/mailingList?mail=helloworld#gmail.com) when removing the if (req.method === "PUT") { part from the mailingList.js
However, I am NOT able to get the API endpoint to be called from within the Netlify function.
(Preferably the mailingList API should be possible to call multiple times with different mailing list IDs from the Netlify function helloworld.js based on different logic /api/mailingList?mail=helloworld#gmail.com&listid=xxx)
To get the API endpoint to be called at all, from the function, I have tried adding a axios call from the helloworld.js to mailingList.js like this
const axios = require('axios');
exports.handler = async function(event, context) {
const mail = "helloworld#gmail.com";
// add to mailinglist
axios
.put("/api/mailingList?mail="+mail)
.then((result) => {
if (result.status === 200) {
toast.success(result.data.message);
}
})
.catch((err) => {
console.log(err);
});
}
This result in the following error from the browser: error decoding lambda response: invalid status code returned from lambda: 0
(I do not get any error msg from the Netlify log, either helloworld.js or mailingList.js)
Clearly, there is something wrong with how I call the mailigList.js from helloworld.js. Would greatly appreciate if some one could give me some advice and show me what I am doing wrong.
How can I call the API endpoint (mailigList.js) from within the Netlify function helloworld.js? (Preferably multiple times with different mailing list IDs: /api/mailingList?mail=helloworld#gmail.com&listid=xxx)
Found the solution in this article: https://travishorn.com/netlify-lambda-functions-from-scratch-1186f61c659e
const axios = require('axios');
const mail = "helloworld#gmail.com";
exports.handler = (event, context, callback) => {
axios.put("https://<domain>.netlify.app/api/mailingList?mail="+mail)
.then((res) => {
callback(null, {
statusCode: 200,
body: res.data.title,
});
})
.catch((err) => {
callback(err);
});
};

Local Netlify function server gives strange response instead of FaunaDB data

I am trying to build a simple web-app with Vue and a FaunaDB. When trying to fetch data from the DB i get the following error:
localhost/:1 Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
When i print the response from the Netlify function server this is what i get:
Here is the code from the vue-page that tries to get the data:
created() {
EventService.readAll()
.then(response => {
this.events = response.data
})
}
This is the EventService Modul:
const readAllDates = () => {
console.log("hey")
return fetch('/.netlify/functions/read-all-dates').then((response) => {
console.log(response)
return response.json()
})
}
export default {
readAll: readAllDates
}
and this is my read-all-dates.js:
import faunadb from 'faunadb'
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SECRET
})
exports.handler = (event, context, callback) => {
console.log("Function `read-all-dates` invoked")
return client.query(q.Paginate(q.Match(q.Ref("indexes/all_dates"))))
.then((response) => {
const dateRefs = response.data
console.log("Todo refs", dateRefs)
console.log(`${dateRefs.length} todos found`)
const getAllDateDataQuery = dateRefs.map((ref) => {
return q.Get(ref)
})
// then query the refs
return client.query(getAllDateDataQuery).then((ret) => {
return callback(null, {
statusCode: 200,
body: JSON.stringify(ret)
})
})
}).catch((error) => {
console.log("error", error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
What am i doing wrong?
Turns out it was vue-router which stopped the netlify proxy from directing the request to the right endoint. There seems no good way around this in dev:
https://forum.vuejs.org/t/devserver-proxy-not-working-when-using-router-on-history-mode/54720
The error you posted is one that it's worth getting familiar with!
localhost/:1 Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
Effectively this is caused when you try to parse something as JSON, but it's not actually JSON. The < is a tell-tale sign that the request response was probably HTML instead of JSON. The next step in debugging is to look at the XHR request itself in the browser debugging "Network" panel.
In my experience, one of the most common reasons for this error is a routing problem, which is triggering a 404 response route serving HTML instead of your expected function handler.

How to setup authentication with graphql, and passport but still use Playground

After adding authentication to our backend Graphql server the "Schema" and "Docs" are no longer visible in the Graphql Playground. Executing queries when adding a token to the "HTTP HEADERS" in the Playground does work correctly when authenticated and not when a user isn't authenticated, so that's ok.
We disabled the built-in Playground from Apollo-server and used the middleware graphql-playground-middleware-express to be able to use a different URL and bypass authentication. We can now browse to the Playground and use it but we can't read the "Schema" or "Docs" there.
Trying to enable introspection didn't fix this. Would it be better to call passport.authenticate() in the Context of apollo-server? There's also a tool called passport-graphql but it works with local strategy and might not solve the problem. I've also tried setting the token in the header before calling the Playground route, but that didn't work.
We're a bit lost at this. Thank you for any insights you could give us.
The relevant code:
// index/ts
import passport from 'passport'
import expressPlayground from 'graphql-playground-middleware-express'
const app = express()
app.use(cors({ origin: true }))
app.get('/playground', expressPlayground({ endpoint: '/graphql' }))
app.use(passport.initialize())
passport.use(bearerStrategy)
app.use(
passport.authenticate('oauth-bearer', { session: false }),
(req, _res, next) => { next() }
)
;(async () => {
await createConnections()
const server = await new ApolloServer({
schema: await getSchema(),
context: ({ req }) => ({ getUser: () => req.user, }),
introspection: false,
playground: false,
})
server.applyMiddleware({ app, cors: false })
app.listen({ port: ENVIRONMENT.port }, () => { console.log(`Server ready`) })
})()
// passport.ts
import { IBearerStrategyOptionWithRequest, BearerStrategy, ITokenPayload } from passport-azure-ad'
import { Account } from '#it-portal/entity/Account'
export const bearerStrategy = new BearerStrategy( config,
async (token: ITokenPayload, done: CallableFunction) => {
try {
if (!token.oid) throw 'token oid missing'
const knownAccount = await Account.findOne({ accountIdentifier: token.oid })
if (knownAccount) return done(null, knownAccount, token)
const account = new Account()
account.accountIdentifier = token.oid
account.name = token.name
account.userName = (token as any).preferred_username
const newAccount = await account.save()
return done(null, newAccount, token)
} catch (error) {
console.error(`Failed adding the user to the request object: ${error}`)
}
}
)
I figured it out thanks to this SO answer. The key was not to use passport as middleware on Express but rather use it in the Graphql Context.
In the example code below you can see the Promise getUser, which does the passport authentication, being used in the Context of ApolloServer. This way the Playground can still be reached and the "Schema" end "Docs" are still accessible when run in dev mode.
This is also the preferred way according to the Apollo docs section "Putting user info on the context".
// apollo.ts
passport.use(bearerStrategy)
const getUser = (req: Express.Request, res: Express.Response) =>
new Promise((resolve, reject) => {
passport.authenticate('oauth-bearer', { session: false }, (err, user) => {
if (err) reject(err)
resolve(user)
})(req, res)
})
const playgroundEnabled = ENVIRONMENT.mode !== 'production'
export const getApolloServer = async () => {
return new ApolloServer({
schema,
context: async ({ req, res }) => {
const user = await getUser(req, res)
if (!user) throw new AuthenticationError('No user logged in')
console.log('User found', user)
return { user }
},
introspection: playgroundEnabled,
playground: playgroundEnabled,
})
}
The best thing is that you only need two functions for this to work: passport.use(BearerStrategy) and passport.authenticate(). This is because sessions are not used so we don't need to add it as Express middleware.
// index/ts
const app = express()
app.use(cors({ origin: true }))
;(async () => {
await createConnections()
const server = await getApolloServer()
server.applyMiddleware({ app, cors: false })
app.listen({ port: ENVIRONMENT.port }, () => { console.log(`Server ready`) })
})()
I hope this helps others with the same issues.

How to send a request from Nuxt.js client over Nuxt.js server and receive the response back to the client

I'm developing a Vue.js application which has only frontend (no server) and send a lot of requests to different APIs. The originally quite simple app became more complex. And there are problems with some APIs, because browsers do not accept the responses due to CORS. That is why I'm trying to test, if I can migrate the app to Nuxt.js.
My approach is as follows (inspired by this comment), but I expect, that there is probably a better way to send the requests from the client over the server.
pages/test-page.vue
methods: {
async sendRequest(testData) {
const response = await axios.post('api', testData)
// Here can I use the response on the page.
}
}
nuxt.config.js
serverMiddleware: [
{ path: '/api', handler: '~/server-middleware/postRequestHandler.js' }
],
server-middleware/postRequestHandler.js
import axios from 'axios'
const configs = require('../store/config.js')
module.exports = function(req, res, next) {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
if (req.hasOwnProperty('originalUrl') && req.originalUrl === '/api') {
const parsedBody = JSON.parse(body)
// Send the request from the server.
const response = await axios.post(
configs.state().testUrl,
body
)
req.body = response
}
next()
})
}
middleware/test.js (see: API: The Context)
export default function(context) {
// Universal keys
const { store } = context
// Server-side
if (process.server) {
const { req } = context
store.body = req.body
}
}
pages/api.vue
<template>
{{ body }}
</template>
<script>
export default {
middleware: 'test',
computed: {
body() {
return this.$store.body
}
}
}
</script>
When the user makes an action on the page "test", which will initiate the method "sendRequest()", then the request "axios.post('api', testData)" will result in a response, which contains the HTML code of the page "api". I can then extract the JSON "body" from the HTML.
I find the final step as suboptimal, but I have no idea, how can I send just the JSON and not the whole page. But I suppose, that there must be a much better way to get the data to the client.
There are two possible solutions:
Proxy (see: https://nuxtjs.org/faq/http-proxy)
API (see: https://medium.com/#johnryancottam/running-nuxt-in-parallel-with-express-ffbd1feef83c)
Ad 1. Proxy
The configuration of the proxy can look like this:
nuxt.config.js
module.exports = {
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
proxy: {
'/proxy/packagist-search/': {
target: 'https://packagist.org',
pathRewrite: {
'^/proxy/packagist-search/': '/search.json?q='
},
changeOrigin: true
}
},
...
}
The request over proxy can look like this:
axios
.get('/proxy/packagist-search/' + this.search.phpLibrary.searchPhrase)
.then((response) => {
console.log(
'Could get the values packagist.org',
response.data
)
}
})
.catch((e) => {
console.log(
'Could not get the values from packagist.org',
e
)
})
Ad 2. API
Select Express as the project’s server-side framework, when creating the new Nuxt.js app.
server/index.js
...
app.post('/api/confluence', confluence.send)
app.use(nuxt.render)
...
server/confluence.js (simplified)
const axios = require('axios')
const config = require('../nuxt.config.js')
exports.send = function(req, res) {
let body = ''
let page = {}
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
const parsedBody = JSON.parse(body)
try {
page = await axios.get(
config.api.confluence.url.api + ...,
config.api.confluence.auth
)
} catch (e) {
console.log('ERROR: ', e)
}
}
res.json({
page
})
}
The request over API can look like this:
this.$axios
.post('api/confluence', postData)
.then((response) => {
console.log('Wiki response: ', response.data)
})
.catch((e) => {
console.log('Could not update the wiki page. ', e)
})
Now with nuxtjs3 :
nuxtjs3 rc release
you have fetch or useFetch no need to import axios or other libs, what is great, automatic parsing of body, automatic detection of head
fetching data
you have middleware and server api on same application, you can add headers on queries, hide for example token etc
server layer
a quick example here in vue file i call server api :
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
.then( (response) => ({
status: response.status,
}) )
.catch( (error) => ({
status: error?.response?.status || 500,
}) );
it will call a method on my server, to init the server on root directory i created a folder name server then api, and a file name newsletter.ts (i use typescript)
then in this file :
export default defineEventHandler(async (event) => {
const {REST_API, MAILINGLIST_UNID, MAILINGLIST_TOKEN} = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + REST_API);
console.log("token" + MAILINGLIST_TOKEN);
console.log("mailing list unid" + MAILINGLIST_UNID);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": MAILINGLIST_UNID
}]
};
const {status} = await $fetch.raw(REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: MAILINGLIST_TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
event.res.statusCode = status;
return "";
})
What are the benefits ?
REST_API,MAILING_LIST_UNID, MAILING_LIST_TOKEN are not exposed on
client and even file newsletter.ts is not available on debug browser.
You can add log only on server side You event not expose api url to avoid some attacks
You don't have to create a new backend just to hide some criticals token or datas
then it is up to you to choose middleware route or server api. You don't have to import new libs, h3 is embedded via nitro with nuxtjs3 and fetch with vuejs3
for proxy you have also sendProxy offered by h3 : sendProxy H3
When you build in dev server and client build in same time(and nothing to implement or configure in config file), and with build to o, just don deploy your project in static way (but i think you can deploy front in static and server in node i don't know)