Express catch-all error handling with custom message or property - javascript

I'm using Express inside Netlify Functions to create a custom API for my front-end Vue app. In that, I'm connecting to a few other third-party API endpoints.
While I've got a custom error-handling middleware working correctly, it still doesn't give me any ability to write a DRY code. Not sure if that's the correct way or if I'm missing something.
For example, I have something like (someRoute.ts):
import type {NextFunction, Response} from 'express'
import type {Request} from 'express-serve-static-core'
import axios from 'axios'
export default function (request : Request, response : Response, next : NextFunction) {
axios({
url: '/api1/'
}).then(() => {
axios({
url: '/api2/'
}).then(() => {
response.status(200).json({
message: 'sequential API call completed'
})
}).catch(next)
}).catch(next)
}
Now, if my /api1/ call fails, I wish to send a different error message than when it fails at /api2/. There are 2 reasons I wish to do this:
If a user reports an error - I wish to be able to locate, where exactly my code failed. I might have 5 or 6 API calls to complete in a single route, so it would be difficult to locate at what stage the API failed. A custom message per error or some kind of a custom property would be helpful.
I'd also like to be able to present a meaningful error message to users, if possible.
To achieve 1, I thought of attaching a custom property to the request:
import type {NextFunction, Response} from 'express'
import type {Request} from 'express-serve-static-core'
import axios from 'axios'
export default function (request : Request, response : Response, next : NextFunction) {
request.stage = 101
axios({
url: '/api1/'
}).then(() => {
request.stage = 102
axios({
url: '/api2/'
}).then(() => {
response.status(200).json({
message: 'sequential API call completed'
})
}).catch(next)
}).catch(next)
}
To achieve 2, the only way I know is using:
import type {Response} from 'express'
import type {Request} from 'express-serve-static-core'
import axios from 'axios'
export default function (request : Request, response : Response) {
axios({
url: '/api1/'
}).then(() => {
axios({
url: '/api2/'
}).then(() => {
response.status(200).json({
message: 'sequential API call completed'
})
}).catch(() => {
throw new Error('Error from API 2')
// I might as well call response.send(500) here
// no point having a custom error handling middleware
// if I have to manually send errors
})
}).catch(() => {
throw new Error('Error from API 1')
})
}
I can possibly use my approach for 1 and in my front-end present error messages based on error codes - but it might soon get messy to handle too many error codes.
If this is not the correct way and if others have a better way of handling errors, please let me know.

Related

onError Authentication with refresh token

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.

NodeJS: #esri/arcgis-rest-request - How to adjust Fetch options?

I am looking to adjust the Fetch request option when making a request with #esri/arcgis-rest-request, but unfortunately I cannot find any documentation related to this.
import fetch from "node-fetch";
import FormData from "isomorphic-form-data";
import arcgisRestRequest from "#esri/arcgis-rest-request";
arcgisRestRequest.setDefaultRequestOptions({ fetch, FormData });
arcgisRestRequest.request("https://www.arcgis.com/sharing/rest/info")
.then(response => console.log(response));
When using the request method I am getting errors regarding the certificate of the NodeJS server:
FetchError: request to https://xxx/server/rest/self?token=xxx=json failed, reason: unable to get local issuer certificate
I would like to pass something like:
const fetchOptions = {
...
agent:new https.Agent({rejectUnauthorized: false}),
...
};
to avoid the certificate error.
How can I accomplish this?
Looking at their code it looks like you should be able to just do
arcgisRestRequest.request(
"https://www.arcgis.com/sharing/rest/info",
{
agent:new https.Agent({rejectUnauthorized: false})
}
)
.then(response => console.log(response));

Alert() pops up twice in axios interceptor (Vue.js)

In my Vue app, I instantiate a single instance of axios and use it across the app for HTTP requests. I have set up a response interceptor which checks if any response from the backend is 401 unauthorized, and if so, shows an alert message. This basic flow has been implemented, but you need to hit "OK" on the alert message twice for it to go away, and I am not sure why.
Axios instance:
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
const axiosInstance: AxiosInstance = axios.create();
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => response,
(error: AxiosError) => {
if(error.response && error.response.status === 401) {
alert('There has been an issue. Please log out and then log in again.');
return Promise.reject(error);
}
}
);
export default axiosInstance;
The request whose response is being intercepted:
import axiosInstance from 'axios-instance';
public async getloggedInUserId() {
await axiosInstance.get('/sessions.json')
.then((response) => {
if(response.data.user_id) {
this.SET_USER_ID(response.data.user_id);
}
});
}
I've read this thread, but my issue seems to be different: Javascript alert shows up twice
I've tried changing the return statement from return Promise.reject(error); to return false; but that did nothing for me.
As Phil suggested in the comment above, looking at at the Network tab in the browser console helped me solve the issue. The console showed how each component in the page was being loaded along with the resulting response code. In short, two processes were actually returning 401, which was the reason why the alert was being called twice.
I have decided to move the code that calls alert from a global axios interceptor (called whenever any process returns 401) to a .catch block inside one specific axios process, so that it only gets called once.
Your promise throws error in axios error interceptor, and error called second times.

Returning Error Values Through Axios/Express To React App

I've got a handleSubmit function that fetches data from my backend as part of a larger component. I'd like to send the error information to my redux store and/or local component when the back-end fails, but am unable to do so.
The handleSubmit function looks like this (it's using React hooks, which are wired up correctly. Can post the full component if that is useful):
const handleSubmit = async (e, { dataSource, filter, filterTarget }) => {
e.preventDefault();
setIsLoading(true);
setErrorValue(null);
setError(false);
const token = localStorage.JWT_TOKEN;
const link = filterTarget === "Identifier" ? `http://localhost:8081/api/${dataSource}/${filter}`: `http://localhost:8081/api/${dataSource}?filter=${filter}&filterTarget=${filterTarget}`;
try {
let data = await axios.get(link, { headers: { authorization: token }});
props.setData(data);
setError(false);
setIsLoading(false);
} catch (err){
setErrorValue(err.message);
setError(true);
setIsLoading(false);
};
};
I'm intentionally making bad requests through the form, which will trigger an error in my backend. These are handled through my custom Express middleware function, which looks like this (I'll add more once I get this framework to work):
handleOtherError: (error, req, res, next) => { // Final custom error handler, with no conditions. No need to call next() here.
console.log("This error handler is firing!");
return res.status(500).json({
message: error.message,
type: "ServerError"
});
}
I know that this function is firing because the console.log statement is appearing on my server, and if I change the status code, so does the status code error on the front-end in my Google Chrome console.
In fact, if I go to the network tab, the correct error information appears for my request. Here's the video of me making a bad request:
However, when I try to access the err.message on my front-end, I'm not able to do so. The err.message in my try/catch handler for the handleSubmit function only ever gives me the Request failed with status code XXX
What am I doing wrong?
See https://github.com/axios/axios#handling-errors
You can access the response by using err.response.data.message, not err.message.
Found the answer posted elsewhere: https://github.com/axios/axios/issues/960
Apparently, to get the message, you have to use err.response.data.message
Simply using "err" will only give you a basic string respresentation of the error.

How to call axios/api call in Nativescript application

I'm trying to build a small application on Nativescript-vue where I'm having backend laravel framework for api calls which needs to be called to get relevant data. For example if user wants to login he needs to validate its credentials through api oauth/token so I'm trying to call this through axios here is my code:
My settings.js file contains.
export const authHeader = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
this is being imported inside my axios calls:
const postData = {
grant_type: 'password',
username: user.email,
password: user.password,
client_id: clientId,
client_secret: clientSecret,
scope: '',
provider: provider
}
const authUser = {}
axios.post(authUrl, postData, {headers: authHeader}).then(response => {
console.log('Inside oauth/token url')
if(response.status === 200)
{
console.log('response received')
}
})
.catch((err) => {
if(err.response.status === 401){
reject('Validation error')
}
else
reject('Something went wrong')
})
when I try to build with command tns debug android --bundle I get chrome-devtools which shows me:
Digging more deep into it I can see headers are being passed but those are only provisional:
As you can see I've console.log inside my application which show me:
Even while compiling I get following errors:
Guide me how can I achieve this. Thanks.
Edit:
Similarly I used nativescript's own http documentation something like this:
const httpModule = require("http");
httpModule.request({
url: "http://iguru.local/oauth/token",
method: "POST",
headers: { "Content-Type": "application/json" },
content: JSON.stringify({
grant_type: 'password',
username: this.user.email,
password: this.user.password,
client_id: 'check',
client_secret: 'check',
scope: '',
provider: 'check'
})
}).then((response) => {
// Argument (response) is HttpResponse
console.log('Action called')
if(response.status === 200)
{
console.log('Response recieved')
}
}, (e) => {
console.log('Something went wrong')
});
I'm getting the same result, moreover I tried api from server end ex http://confidenceeducare.com/oauth/token it happens to be same. Normal Vue application calls the api perfectly fine. I guess there is some problem with the nativescript application. Do I need to import something more? I'm stuck with it.
If anybody is thinking my api end point is broken, I tried using the urls mentioned in example i.e. https://httpbin.org/post:
and:
When I checked my api in postman it is working over there, I'm getting at least a response with status code:
Edit 2:
For reference github repository https://github.com/nitish1986/sample_mobile_app
I tested the exact same project you have shared in Github with Android 8.0, it works perfectly fine. The axios call hits the error block as the response status (error.response.status) is 401 and data (error.response.data) returns the exact same response you see from Postman.
If you are using Android 9 or later it might fail as you haven't enabled useCleartextTraffic on your application tag in the AndroidManifest.xml.
<application
android:usesCleartextTraffic="true"
android:name="com.tns.NativeScriptApplication"
With iOS also it would fail as you haven't enabled app transport security. Just to allow all Http communication within your app, you have to add the following key to your info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
In case if you want to allow only specific domains, then use NSExceptionDomains key and list the endpoints.
The problem has to do with how axios is imported. Try:
import axios from 'axios/dist/axios'
this also solves the Module not found: Error: Can't resolve 'net' in... error.
When importing the package normally my requests failed, returning the error
Error in v-on handler (Promise/async): "Error: Request failed with status code null"

Categories

Resources