handling apollo-server errors on client - reactjs

What I'm you doing?:
I would like to thrown an error on apollo-server and process it on client.
Note: I'm using apollo-link-error middleware on apollo-client.
Server:
import { UserInputError } from "apollo-server";
Mutation: {
someMutation : {
try {
// here is some code which failed
} catch (error) {
// base Error class has message property by default
// response just hold some additional informations
throw new UserInputError(error.message, { response });
}
}
}
Client:
simplified implementation of my mutation on client
<Mutation
mutation={CREATE_ORDER}
>
{(createOrder, { loading, error }) => (
....
try {
await createOrder({ variables: {...}});
} catch (createOrderError) {
// here I get Cannot read property 'data' of undefined
console.log(createOrderError);
}
)}
</Mutation>
I receive following error on client (in catch clause in the code above):
TypeError: Cannot read property 'data' of undefined
at Mutation._this.onMutationCompleted (react-apollo.browser.umd.js:631)
at react-apollo.browser.umd.js:586
This error looks like problem with httpLink.
Response: (from network tab in chrome dev tools)
From graphql spec :
If an error was encountered during the execution that prevented a
valid response, the data entry in the response should be null.
So I assume that my response from server is valid. The data object should be null.
What do I expect to happen?:
I would like to access to response from apollo server How could I achieve this?
Thanks for help!

Things to check before accessing the data returned from query or mutation
If loading -> return some loader component
If error is present -> display some error component
If not any of above two conditions matched then for sure you have the data.
Apart from that you need to have
1.On Apollo Client "errorPolicy"
const client = new ApolloClient({
defaultOptions: {
watchQuery: {
errorPolicy: 'all'
},
query: {
errorPolicy: 'all'
},
mutate: {
errorPolicy: 'all'
}
},
link,
cache,
connectToDevTools: true,
})
2.For customising error sent from server -
You can use formatError
const server = new ApolloServer({
...root,
resolverValidationOptions: {
requireResolversForResolveType: false,
},
formatError, <---------- send custom error
formatResponse: (response, query) => formatResponse({ response, query }),
dataSources,
context: async ({ req, res }) => {
const user = req.user;
return { user, req, res };
}
});
e.g
const formatError = (error) => {
const { extensions } = error;
logger.error(error);
const exception = extensions.exception ? extensions.exception : {};
logger.error('\nStackTrace');
logger.error(exception.stacktrace);
exception.stacktrace = null;
const extractedError = extractErrorFromExtention(extensions);
return extractedError || { message: error.message, code: extensions.code, exception };
};

Related

How to handle socket.io authentication errors with nodejs server and reactjs?

We are trying to display an error when the connection has failed during authentication (wrong JWT token).
For that, we have a middleware on the server that verifies the validity of the token and is expected to return an error if the token is invalid :
io.use((socket, next) =>
{
jwt.verify(socket.handshake?.query?.refreshToken, process.env.REF_JWT_SEC, (err, decoded) =>
{
if(decoded?._id && !err)
{
socket.decoded = decoded._id;
return next();
}
else
{
let err = new Error('authentication_error')
err.data = { content : 'refreshToken error!' };
return next(err);
}
});
});
On the client we have multiple socket.on() instances :
const refreshToken = localStorage.getItem('refresh_token');
const socket = io(
"http://localhost:4000",
{
query: { refreshToken },
},
);
...
export const Chat = () => {
...
useEffect(() => {
socket.on("connect_error", err => {
console.log(err); // <-- error we're trying to log
});
socket.on("error", err => {
console.log(err); // <-- error we're trying to log
});
socket.on("message", ({id, text, sender}) => {
console.log('# socket io res :', id, text, sender)
})
return () => {
socket.disconnect()
}
}, []);
...
}
While trying to trigger the error by sending an incorrect token ("ThisIsSomeIncorrectToken"), as expected, sending/recieving messages doesn't work. However, no error is being logged.
In the client's console we can see the requests to socket.io:
Note that somehow the error shows up in the client logs when I save my reactjs code, but not if I refresh completely the browser or re-render the component.

SSR crashing in Next.js on unsuccessful GraphQL request (HTTP code 500) using Apollo Client

Well, I'm a little dumpy. I will try to explain my problem as clearly as possible.
I use Apollo client to do my GraphQL queries. I also use NextJS.
I have a page that needs to be rendered on the server side for SEO reasons.
So I have a getProductFromSlug function that allows me to execute my request.
export const getProductFromSlug = async (slug: string) => {
try {
const { data, error } = await apolloClient.query<{
product: Product
}>({
query: GET_PRODUCT_BY_SLUG_QUERY,
variables: {
slug,
},
})
if (error) {
return { errors: [error.message] }
}
if (!('product' in data) || data.product === null) {
return { errors: ['Product with specified url not found'] }
}
return {
data,
}
} catch (error) {
// #ts-ignore
const formattedErrors: ApolloError = isApolloError(error)
? error.graphQLErrors.map((error) => error.message)
: [`Unhandled error : ${error}`]
return {
errors: formattedErrors,
}
}
}
Here's getServerSideProps to pass data to page
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
const requestData = await getProductFromSlug(context.params.slug as string)
return 'errors' in requestData
? { notFound: true, props: requestData }
: { props: requestData }
}
The problem is that when I have a HTTP code 500 from the endpoint, the SSR is crashing and on Vercel, it's causing a serverless crash error.
Error: Response not successful: Received status code 500
This error happened while generating the page. Any console logs will be displayed in the terminal window
If needed, here's my entry point (_app.tsx):
function MyApp(props: AppProps) {
return (
<ApolloProvider client={apolloClient}>
<RecoilRoot>
<RecoilNexus />
<AuthenticationFromStorage />
<Layout>
<props.Component {...props.pageProps} />
</Layout>
</RecoilRoot>
</ApolloProvider>
)
}
You can see my Apollo Client here : https://gist.github.com/SirMishaa/d67e7229307b77b43a0b594d0c9e6943
Stack trace of yarn run dev (next dev -p 3005) :
ServerError: Response not successful: Received status code 500
at Object.throwServerError (C:\Users\misha\Documents\dev\rekk-next\node_modules\#apollo\client\link\utils\utils.cjs:45:17)
at C:\Users\misha\Documents\dev\rekk-next\node_modules\#apollo\client\link\http\http.cjs:31:19
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
error - uncaughtException: ServerError: Response not successful: Received status code 500
error Command failed with exit code 1.
NOTE :
After some try with console.log in try and catch scope, it shows nothing in the Next SSR console, so the internal error of Apollo is not caught for some reason.
I appreciate your help, thank you!
UPDATE
The issue was that PusherLink was not continuing the execution in the chain when there was an error. Adding the error and complete handlers solved the problem.
forward(operation).subscribe({
next: (data) => {
...
this.subscribeToChannel(subscriptionChannel, observer)
},
// these two were missing
error: (error) => observer.error(error),
complete: () => observer.complete(),
})
JIC, I also added a missing condition the code from the other link taken as a reference has
subscribeObservable.subscribe = (observerOrNext, onError, onComplete) => {
if (typeof(observerOrNext) == "function") {
prevSubscribe(observerOrNext, onError, onComplete)
} else {
prevSubscribe(observerOrNext)
}
OLD ANSWER
Your code inside the catch block could be throwing an error, and that's what is breaking. I'm unsure about this condition Array.isArray(apolloError), maybe you meant Array.isArray(apolloError.graphQLErrors)
You could try the isApolloError utility to be a little more clear and get some hints from TS types.
import { isApolloError } from '#apollo/client';
...
const formattedErrors = isApolloError(e)
? e.graphQLErrors.map(error => error.message)
: [`Unhandled error : ${e}`];
return {
errors: formattedErrors,
};

observable catchError after request failure (axios/redux)

INTRO :
I have an app in reactjs, using redux, redux-observable and axios-observable.
I face an issue with HTTP error handling.
Let's take the following Epic :
const loginRequest = (action$, state$) => action$.pipe(
ofType(UsersActions.loginRequest),
switchMap((action: {payload:{email: string, password: string}}) =>
HttpService.PostAsync<any>('token-auth', action.payload).pipe(
map(response => {
// blabla
}),
catchError((error: string) => {
// blabla
})
)
)
);
the HttpService looks like this
public static PostAsync<T>(targetApi: string, data: any, basePath?: string): AxiosObservable<T> {
return Axios.post(this.getBaseUrl(targetApi, basePath), data);
}
So this works correctly, if the post request fail, I get into the catchError, if it doesn't i go into the normal map.
PROBLEM :
I would like to intercept the response, in order to add a global app error handling, I setup the following function :
Axios.interceptors.response.use(response => {
console.log(`[response] --> ${response.status}`)
return response;
}, error => {
console.log(`[error] --> ${error}`)
return throwError(error);
})
I can now see the log, error, or response depending on the HTTP request result. BUT, I will ALWAYS go into the map, and never into the catchError of my Epic.
QUESTION :
How can I interpect the error, but still throw an error to the redux-observable epic ?
throwError only works within RX chain, try use native JS throw
Axios.interceptors.response.use(response => {
console.log(`[response] --> ${response.status}`)
return response;
}, error => {
console.log(`[error] --> ${error}`)
throw error
})

Why graphQLErrors are always empty in react components?

I want to show some errors that comes from graphql server to user.
Have some component with callback that use some mutation
onSave() => {
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
}).catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
}
While I'm able to see the graphQLErrors error with apollo-link-error link.
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors) <--- errors from server
});
Migration to apollo-server instead of express-graphql solve the problem.
Or you can access errors by e.networkError.result.errors
As I understand it, the 'catch' will catch errors if your server returns an error code, but not if it returns your errors in the response, but with a success code. In that case, you just need to get your errors out of the response (keeping your catch too in case the server responds with an error code):
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
})
.then((response) => {
if (response.errors) {
// do something with the errors
} else {
// there wasn't an error
}
})
.catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
Alternatively - if you use the graphql() option from react-apollo, you can specify an onError handler:
export default graphql(CHANGE_ORDER_MUTATION, {
options: (props) => ({
onCompleted: props.onCompleted,
onError: props.onError
}),
})(MyComponent);
references:
https://github.com/apollographql/react-apollo/issues/1828
Apollo client mutation error handling

Apollo GraphQL Subscriptions

I'm having trouble with GraphQL subscriptions in Apollo. I want to subscribe to added "perspectives" on topics (basically added comments on posts), and I'm pretty sure I have the server set up correctly. The client is what's giving me trouble. (If this question looks familiar, I asked it before and thought I got an answer, but no go). Here is my subscription schema:
type Subscription {
perspectiveAdded: Perspective
}
schema {
query: RootQuery
mutation: Mutation
subscription: Subscription
}
My subscription resolver:
Subscription: {
perspectiveAdded(perspective) {
return perspective;
}
}
My subscriptionManager:
const pubsub = new PubSub();
const subscriptionManager = new SubscriptionManager({
schema,
pubsub,
setupFunctions: {
perspectiveAdded: (options, args) => {
perspectiveAdded: {
filter: (topic) => {
return topic
}
}
},
}
});
export { subscriptionManager, pubsub };
The last part of my addPerspective mutation that is (the event trigger for the subscription):
//...
return perspective.save((error, perspective) => {
if(error){
console.log(error);
}
//Publish it to Subscription channel
pubsub.publish('perspectiveAdded', perspective);
});
And then I've wired up the actual server to support subscriptions:
const PORT = process.env.PORT || 4000;
const server = createServer(app);
server.listen(PORT, ()=>{
new SubscriptionServer(
{
subscriptionManager: subscriptionManager,
onConnect: (connectionParams, webSocket) => {
console.log('Websocket connection established Lord Commander');
},
onSubscribe: (message, params, webSocket) => {
console.log("The client has been subscribed, Lord Commander", message, params);
},
onUnsubsribe: (webSocket) => {
console.log("Now unsubscribed, Lord Commander");
},
onDisconnect: (webSocket) => {
console.log('Now disconnected, Lord Commander');
}
},
{
server: server,
path: '/subscriptions',
});
console.log('Server is hot my Lord Commander!');
});
I've wired up the client correctly as well, because in my terminal I see the "Websocket connection established" message. The part I'm stumped about is how to actually call the subscription. According to the Apollo blog, I should be able to test the subscription in GraphiQL (since I'm using an apollo server, now graphql-server-express), but it says "Resolve function for \"Subscription.perspectiveAdded\" returned undefined".
For my component, I've tried to wire up 'subscribeToMore' but in the browser console, I'm getting an error object that says "Invalid params returned from onSubscribe! return values must be an object!" I'm not sure which object it is referring to.
Here's my subscription query called perspectiveSubscription:
export default gql`
subscription {
perspectiveAdded {
id
content
}
}
`;
And the wired up component:
constructor(props){
super(props);
this.state = {};
this.subscription = null;
}
componentWillReceiveProps(nextProps) {
if (!this.subscription && !nextProps.data.loading) {
let { subscribeToMore } = this.props.data
this.subscription = subscribeToMore(
{
document: perspectiveSubscription,
updateQuery: (previousResult, { subscriptionData }) => {
if(!subscriptionData.data){
console.log('no new subscription data');
return previousResult;
}
const newPerspective = subscriptionData.data.perspectiveAdded;
console.log(newPerspective);
return Object.assign({}, previousResult, newPerspective);
}
}
)
}
From here, I get a message in my terminal saying the client has been subscribed, but still I get the error object mentioned above. I've been pulling my hair out about this for days - do you guys see what I am missing here? Specifically, any ideas on the client side? Thanks everyone!
It seems like the server side is not correct, because the subscription is added and graphiql also does not deliver a correct result.
One thing that i suggest is that you check the channel definition:
const pubsub = new PubSub();
const subscriptionManager = new SubscriptionManager({
schema,
pubsub,
setupFunctions: {
perspectiveAdded: (options, args) => {
perspectiveAdded: {
filter: (perspective) => {
console.log(perspective); // check if object is correct
return true; // return true and not the object as long as you do not want to filter
}
}
},
}
});
export { subscriptionManager, pubsub };
And also check if the perspective object is saved and defined before the pubsub call.
And i think you also want to add a comment id for which the subscription should be working. On my side it looks more or less like in this post

Resources