Nuxt handle fetch errors from Prismic API - error-handling

I'm building a blog with Nuxt to and Prismic as CMS.
my nuxt.config.js looks like this:
mode: 'universal',
modules: ['#nuxtjs/prismic'],
target: 'static',
generate: {
fallback: '404.html',
},
Project is deployed on Netlify with build command "npm run generate"
In pages directory I have dynamic links ( _uid.vue ) where I use the new fetch to get the post according to route.
async fetch() {
const post = await this.$prismic.api.getByUID('blog-post', this.$route.params.uid)
this.post = post
},
This all works! However I want to handle fetch errors and display correspond error page. For example when the post we try to fetch does not exist or now is deleted. I tried as they show from the link I provide above about fetch, but I get error that post is undefined.
async fetch() {
const post = await await this.$prismic.api.getByUID('blog-post', this.$route.params.uid)
if (post.id === this.$route.params.id) {
this.post = post
} else {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404
}
// use throw new Error()
throw new Error('Post not found')
}
}
My project on GitHub

Also I'm not sure using the fetch hook inside a page is considered a best practice, I think you should prefer asyncData with the following pattern (or async/await one):
export default {
asyncData({ params, error }) {
return axios
.get(`https://my-api/posts/${params.id}`)
.then(res => {
return { title: res.data.title }
})
.catch(e => {
error({ statusCode: 404, message: 'Post not found' })
})
}
}
From Nuxt documentation~

Could you not just catch any exceptions like this:
try {
const post = await this.$prismic.api.getByUID('blog-post', this.$route.params.uid);
if (post.id === this.$route.params.id) {
this.post = post;
}
} catch ((error) => {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404;
}
// use throw new Error()
throw new Error('Post not found');
});
Of course you would have to actually check the kind of exception occurred.

Related

axios interceptor blocking api calls in redux saga

I have a react native project in which I'm calling some API's using redux-saga mechanism. Now when I added response interceptor for axios my saga api's are not working anymore. Does any knows how I can fix this?
here is the code for my axios instance class and response interceptor
const getLoggedInUser = async () => {
const savedUser = JSON.parse(
await getDataFromAsyncStorage(APP_CONSTANTS.SAVED_USER)
)
if (savedUser?.user_id != null) {
return savedUser
}
return null
}
const baseapi = axios.create({
baseURL: APP_CONSTANTS.BASE_URL,
headers: {},
})
baseapi.interceptors.request.use(
async (config) => {
const token = await getLoggedInUser()
const userId = token?.user_id
const authToken = token?.token
if (token) {
baseapi.defaults.headers.common['userId'] = token
baseapi.defaults.headers.common['token'] = authToken
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// Response interceptor for API calls
baseapi.interceptors.response.use(
(response) => {
return response
},
async function (error) {
const originalRequest = error.config
if (error.response.status === 403 /* && !originalRequest._retry */) {
return baseapi(originalRequest)
}
return Promise.reject(error)
}
)
This is my saga class code and it fails directly when I add a response interceptor
function* getTopicList(action) {
try {
yield put({type: ACTION_TYPES.START_TOPIC_LIST})
const {payload} = action
const res = yield call(getAllTopicsOfBatch, payload)
if (res?.status == APP_CONSTANTS.SUCCESS_STATUS) {
yield put({
type: ACTION_TYPES.SET_TOPIC_LIST,
payload: {data: res?.data?.topics},
})
} else {
alert('OOPS Something went wrong! Please try again')
yield put({
type: ACTION_TYPES.ERROR_TOPIC_LIST,
payload: 'Something Went Wrong Please Try Again',
})
}
} catch (error) {
console.log('RESPONES error', error)
alert('OOPS Something went wrong! Please try again')
yield put({
type: ACTION_TYPES.ERROR_TOPIC_LIST,
payload: 'Something Went Wrong Please Try Again',
})
}
}
The code looks mostly fine, the only two things I found that are likely causing problems are:
In the request interceptors you are likely wrongly passing the whole token as userId instead of userId
baseapi.defaults.headers.common['userId'] = token // 'token' should be 'userId'
In the response interceptors error handler, you are not guaranteed to have 'response' property on error.
if (error.response.status === 403) // use error?.response
If neither of these things will fix your problem my guess is you have a problem in your endpoint and so you should examine the response errors you get to guide you.

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.

Axios interceptors don't send data to API in production Heroku app

This is part 2 of me debugging my application in production
In part 1, I managed to at least see what was causing my problem and managed to solve that.
When I send a request to my API which is hosted on Heroku using axios interceptor, every single request object looks like this in the API
{ 'object Object': '' }
Before sending out data to the API, I console.log() the transformRequest in axios and I can see that the data I am sending is actually there.
Note: I have tested this process simply using
axios.<HTTP_METHOD>('my/path', myData)
// ACTUAL EXAMPLE
await axios.post(
`${process.env.VUE_APP_BASE_URL}/auth/login`,
userToLogin
);
and everything works and I get data back from the server.
While that is great and all, I would like to abstract my request implementation into a separate class like I did below.
Does anyone know why the interceptor is causing this issue? Am I misusing it?
request.ts
import axios from "axios";
import { Message } from "element-ui";
import logger from "#/plugins/logger";
import { UsersModule } from "#/store/modules/users";
const DEBUG = process.env.NODE_ENV === "development";
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
timeout: 5000,
transformRequest: [function (data) {
console.log('data', data)
return data;
}],
});
service.interceptors.request.use(
config => {
if (DEBUG) {
logger.request({
method: config.method,
url: config.url
});
}
return config;
},
error => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
console.log('axios interception response', response)
return response.data;
},
error => {
const { response } = error;
console.error('axios interception error', error)
if (DEBUG) {
logger.error(response.data.message, response);
}
Message({
message: `Error: ${response.data.message}`,
type: "error",
duration: 5 * 1000
});
return Promise.reject({ ...error });
}
);
export default service;
Login.vue
/**
* Sign user in
*/
async onClickLogin() {
const userToLogin = {
username: this.loginForm.username,
password: this.loginForm.password
};
try {
const res = await UsersModule.LOGIN_USER(userToLogin);
console.log("res", res);
this.onClickLoginSuccess();
} catch (error) {
throw new Error(error);
}
}
UsersModule (VUEX Store)
#Action({ rawError: true })
async [LOGIN_USER](params: UserSubmitLogin) {
const response: any = await login(params);
console.log('response in VUEX', response)
if (typeof response !== "undefined") {
const { accessToken, username, name, uid } = response;
setToken(accessToken);
this.SET_UID(uid);
this.SET_TOKEN(accessToken);
this.SET_USERNAME(username);
this.SET_NAME(name);
}
}
users api class
export const login = async (data: UserSubmitLogin) => {
return await request({
url: "/auth/login",
method: "post",
data
});
};
I'm not sure what you're trying to do with transformRequest but that probably isn't what you want.
A quote from the documentation, https://github.com/axios/axios#request-config:
The last function in the array must return a string or an instance of Buffer, ArrayBuffer, FormData or Stream
If you just return a normal JavaScript object instead it will be mangled in the way you've observed.
transformRequest is responsible for taking the data value and converting it into something that can actually be sent over the wire. The default implementation does quite a lot of work manipulating the data and setting relevant headers, in particular Content-Type. See:
https://github.com/axios/axios/blob/885ada6d9b87801a57fe1d19f57304c315703079/lib/defaults.js#L31
If you specify your own transformRequest then you are replacing that default, so none of that stuff will happen automatically.
Without knowing what you're trying to do it's difficult to advise further but you should probably use a request interceptor rather than transformRequest for whatever it is you're trying to do.

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)

Catch React Native fetch error without halting the app with red screen

I am making a React Native function that pulls the HTML of a webpage. It works fine if the URL exists and I receive a 200 status code. However, when I put a wrong url in there (something that would receive a 404 error), it displays a red screen that says "Network request failed." I'd like to catch the error without the whole app halting and display an alert to the user. How can I go about doing that?
fetchEvents() {
fetch('http://www.wrongurl.com', {
method: 'GET',
redirect: 'follow'
})
.then(function(response) {
if (response.status == 200) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else throw new Error('HTTP response status not code 200 as expected.');
})
.catch(function(error) {
console.error(error);
return error;
});
}
This is how I solved this, making graceful errors that don't crash the app using promises:
In my API service class:
fetchEvents() {
let thisCurrentAPIService = this;
return new Promise(
function (resolve, reject) {
fetch('http://www.wrongurl.com');
.then(
function(response) {
if (response.ok) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else {
reject(new Error(`Unable to retrieve events.\nInvalid response received - (${response.status}).`));
}
}
)
.catch(
function(error) {
reject(new Error(`Unable to retrieve events.\n${error.message}`));
}
);
}
);
}
Then I call it from my React Component. If I receive an error, I create the alert there.
this.state.apiService.fetchEvents()
.then(
function (value) {
console.log('Contents: ' + value);
},
function (reason) {
Alert.alert(`${reason.message}`);
});
Hilarious, three years almost and no proper answer still.
However, console.error(error) is what actually causing the app to throw a red screen.
You can use Alert component from react-native.
fetchEvents() {
fetch('http://www.wrongurl.com', {
method: 'GET',
redirect: 'follow'
})
.then(function(response) {
if (response.status == 200) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else throw new Error('HTTP response status not code 200 as expected.');
})
.catch(function(error) {
Alert.alert(error); // Using this line
});
}
But I prefer using toast like on Android than alert.
console.warn('This is my error');
If this is simply for dev it might help. It explicitly uses the little warning toast to provide whatever feedback you need. Note: this is definitely not for production use.
Add following in app index.js file
console.reportErrorsAsExceptions = false;