How to use AsyncData with Promise.all to get data from multiple api's works client side but causes nginx to 504 - javascript

I'm currently converting a vue application to use the NUXT framework. To add support for SSR to my application and have come across the following issue when trying to use asyncData with multiple data sources on a single page.
I'm trying to setup asyncData to get data from 2 separate data sources that are required for the page to work. Now the code works on the client-side when the Promise.all resolves it gets both sets of data. However, on the server-side the promises when console.log the promises are both pending and causes Nginx to timeout and give a 504 bad gateway error.
I have tried to get this to work use async/await and promise.all with no avail. see code samples for both below.
Import functions getData and getJsonFile are both using Axios and returning resolved promises with objects of data.
// Using async/await
export default {
async asyncData(context) {
const nameData = await getData('getInformationByNames', {
names: [context.params.name],
referingPage: `https://local.test.com${context.route.fullPath}`
});
const content = await getJsonFile(
`/data/pages/user/${context.params.id}`
);
return {
names: nameData,
content
};
}
}
// Using Promise.all
export default {
async asyncData(context) {
const [nameData, content] = await Promise.all([
getData('getInformationByNames', {
names: [context.params.name],
referingPage: `https://local.test.com${context.route.fullPath}`
}),
getJsonFile(`/data/pages/user/${context.params.id}`)
]);
return {
names: nameData,
content
};
}
}
// getJsonFile
import axios from 'axios';
import replaceStringTokens from '#/scripts/helpers/replaceStringTokens';
export default function getJsonFile(path, redirect = true) {
const jsonFilePath = `${path}.json`;
return axios.get(jsonFilePath).then((response) => {
if (typeof response.data === 'object') {
return replaceStringTokens(response.data);
}
return false;
});
}
// getData
import axios from 'axios';
import getUserDevice from '#/scripts/helpers/getUserDevice';
// require php-serialize node package to serialize the data like PHP would for the api endpoint.
const Serialize = require('php-serialize');
export default function getData(action, data) {
const dataApiAddress = '/api/getData.php';
const dataToPass = data || {};
// all actions available on the api will need to know the users device so add it to the data.
dataToPass.userDevice = getUserDevice();
// package the data like the api expects to receive it
const serializedAndEncodedData = encodeURIComponent(
Serialize.serialize(dataToPass)
);
const axiosParams = {
action,
data: serializedAndEncodedData
};
return axios
.get(dataApiAddress, {
params: axiosParams
})
.then((response) => {
return response.data.data;
})
.catch((error) => {
console.log(error);
return false;
});
}
I would expect that the promises resolve and both sets of data are returned an available for the page to use on both the client and server-side.
Is there a better way to get multiple sets of data or a way to debug the server-side so that I can see what is causing the promises to not resolve on the server?

Fixed the issue the problem was with some discrepancies in the data being queried on the API. The data in the database was using an uppercase letter at the start that must have been input incorrectly. So this was causing the promise to not resolve due to the API sending a query for the lowercase version and in turn causing Nginx to timeout.

Related

API Call only works on homepage but not subpage?

I'm relatively new working with promises in JS. I got the API call to work on the initial homepage, but I'm having issues when I go to another page that is using the same API call.
In my api.js file I have the following:
const key = apiKey;
const commentsUrl = axios.get(`https://project-1-api.herokuapp.com/comments/?api_key=${key}`);
const showsUrl = axios.get(`https://project-1-api.herokuapp.com/showdates?api_key=${key}`);
async function getData() {
const allApis = [commentsUrl, showsUrl];
try {
const allData = await Promise.allSettled(allApis);
return allData;
} catch (error) {
console.error(error);
}
}
In my index.html
import { getData } from "./api.js";
let data = await getData(); //This works and gathers the data from the API.
In my shows.html
import { getData } from "./api.js";
let showsData = await getData(); //This does not and says that cannot access commentsUrl (api.js) before it is initialized. But it is?
If I comment out the code from "show", the API GET request works fine and the index page loads the API data correctly. Can anyone explain to me what's happening and why I would be getting the uninitialized error?
I also should note that if I split the API calls onto two seperate two js files (one for the index, one for the shows), the API calls works and displays the data as it is intended to.
On the homepage, the code is executing the 2 GET requests on page load/initialization of the JS code. When you navigate away from the homepage, presumably with some sort of client-side routing, the 2 GET requests no longer reference 2 Promises as they have already been executed.
You could instead move the GET requests into your function like:
const key = apiKey;
async function getData() {
try {
const commentsUrl = axios.get(`https://project-1- api.herokuapp.com/comments/?api_key=${key}`);
const showsUrl = axios.get(`https://project-1-api.herokuapp.com/showdates?api_key=${key}`);
const allApis = [commentsUrl, showsUrl];
const allData = await Promise.allSettled(allApis);
return allData;
} catch (error) {
console.error(error);
}
}

How do I get my layout component to remain static in Next13 app folder

I am trying to create a layout component that fetches its own data, I have tried adding the cache: 'force-cache' to the fetch but every time I update my CMS content and refresh my page the new content is loaded. Here is an example of my code:
const getLayoutData = async () => {
const response = await fetch(
`https://cdn.contentful.com/spaces/${
process.env.CONTENTFUL_SPACE_ID
}/environments/${
process.env.CONTENTFUL_ENVIRONMENT || "master"
}/entries/${fieldId}?access_token=${process.env.CONTENTFUL_ACCESS_TOKEN}`,
{
cache: "force-cache",
}
);
const {entryTitle, ...headerData} = await response.json();
return { headerData };
}
export default async function Layout() {
const data = await getLayoutData();
...
You can use the getStaticProps() function to fetch data at build time and make it available to your component as a prop. This way, the data will be pre-rendered on the server and will not change when the user refreshes the page:
import getLayoutData from './getLayoutData';
export async function getStaticProps() {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
Alternatively you could use getServerSideProps(), it runs on the server at request time instead of build time. I would recommend that if you have dynamic data that changes frequently:
import getLayoutData from './getLayoutData';
export async function getServerSideProps(context) {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
By default, Next.js automatically does static fetches. This means that the data will be fetched at build time, cached, and reused on each request. As a developer, you have control over how the static data is cached and revalidated.
Refer to the docs - https://beta.nextjs.org/docs/data-fetching/fundamentals
Also, this will work in production mode. So, make sure you are using next build && next start and not next dev.
In case you are fetching data from same URL anywhere else, the cache might be getting updated. As Next.js also does request deduplication built into the fetch function itself.

Problems accessing data from a GraphQL query in Javascript with Svelte

Below I have my working function using a normal REST response that I want to try to convert into a GraphQL version, it fetches a JSON doc from my Phoenix Server and stores the object values from the JSON doc into an object. The problem is, here I can use await and then assign the new object values from the object within the JSON document, but using GraphQL I cannot access this data to assign it because there is no await function as its just a Query. (From what I know)
async function updatePageWithNewCompany(company){
const res = await fetch(`http://localhost:4000/${company}`);
profile = await res.json();
profile = profile.companyProfile
DashboardStore.update(currentData => {
return {
id: profile.id,
companyName: `${profile.company_name}`,
averageLength: profile.average_length,
}
})
Ultimately I am asking if there is a way to access and assign data from a GraphQL query in JavaScript so I can manipulate it before displaying it in my frontend Svelte app.
Example of current GraphQL query:
import { gql } from '#apollo/client'
import { client } from './apollo';
import { query, setClient } from "svelte-apollo";
setClient(client)
const GET_COMPANY = gql`
query{
companyProfile(){
companyName
averageLength
}
}
`;
const response = query(GET_COMPANY)
...
svelte-apollo queries return
a svelte store of promises that resolve as values come in
as stated in the example query displayed in the documentation.
As such, you can exploit that format directly in the script section. Here is an example:
...
async function getCompany() {
const companyStore = query(GET_COMPANY)
const company = (await $companyStore).data.companyProfile
return company // format will be an object with keys { companyName, averageLength } according to your query
}
...
On a side-note I would recommend always getting the id of objects in your GraphQL queries as it is usually the key used internally by the Apollo client to manage its cache (unless explicitly stated otherwise).
I have found a working solution with the help of #Thomas-Hennes
import { client } from '../apollo.js';
import { onMount } from "svelte";
import { gql } from '#apollo/client'
export const COMPANY_LIST = gql`
query {
listCompanies{
companyName
}
}
`;
async function listCompanies() {
await client.query({query: COMPANY_LIST})
.then(res => {
companyList.update( currentData =>
[...res.data.listCompanies.companyName])})
};
onMount(() =>{
listCompanies()
})
await didn't like being assigned to a variable or having its data manipulated so i used it as a promise instead and manipulated the response.
The below link helped me find the final piece of the puzzle.
https://docs.realm.io/sync/graphql-web-access/using-the-graphql-client

catch all URLs in [...slug] and only create static props for valid addresses in Next.js

I'm using dynamic routes and using fallback:true option to be able to accept newly created pages
first i check if parameters is true then i create related props and show the component with that props.
In console i can also see that next.js create new json file for that related page to not go to server again for the next requests to the same page.
But even I type wrong address next create new json file for that path. It means that next.js create json file for every wrong path request.
How can avoid that vulnerable approach?
export const getStaticProps: GetStaticProps = async ({ params }) => {
if (params.slug[0] === "validurl") {
const { products } = await fetcher(xx);
const { categories } = await fetcher(xx);
return { props: { categories, products } };
} else {
return { props: {} };
}
};
const Home = (props: any) => {
if (!props) {
return <div>404</div>;
}
return (
<MainLayout {...props}>
<FlowItems items={props.products} />
</MainLayout>
);
};
export async function getServerSideProps(context) {
console.log(context.params.slug);
...
You are in Server Side inside this getServerSideProps and passing the context lets you dynamically catch whatever value it takes for whatever request.
Then you can check the data you want to load like:
const slug = context.params.slug;
const data = await fetch(`${host}/endpoint/${slug.join('/')}`);
so the request will be like 'localhost:3000/endpoint/foo/slug/test
Then you can deal with those slugs and it's data in a backend logic (where it should be) in your endpoint (just to clarify this sort of logic it usually belongs to a gateway and not to an endpoint, this is just for educational purposes).
If the endpoint/gateway returns a 404 - Not found you can simply redirect to the 404.js page (which can be static), same for the rest of the possible errors available in your backend.

set data only on first load with nuxt.js

I'm new to nuxt.js so I'm wondering what could be the best way to set up some data via REST api.
I have a store folder like this:
store
-posts.js
-categories.js
-index.js
I've tried to set the data with nuxtServerInit actions in the index.js:
export const actions = {
async nuxtServerInit({ dispatch }) {
await dispatch('categories/setCategories')
await dispatch('posts/loadPosts','all')
}
}
But doesn't works: actions are dispatched (on the server) but data are not set.
So I've tried with fetch but this method is called every time the page where I have to display posts is loaded. Even if, in the general layout, I do this:
<template>
<div>
<Header />
<keep-alive>
<nuxt/>
</keep-alive>
</div>
</template>
So my solution, for now, is to use fetch in this way,
In the page component:
async fetch({store}){
if(store.getters['posts/getPosts'].length === 0 && store.getters['categories/getCategories'].length === 0 ){
await store.dispatch('categories/setCategories')
await store.dispatch('posts/loadPosts','all')
}
}
Also, one thing I noted is that fetch seems not working on the root page component (pages/index.vue)
My solution seems works, but there is maybe another better way to set the data?
There's no out of the box solution for this as it's specific to your requirements/needs. My solution is very similar to yours but instead of checking the size of data array I introduced additional variable loaded in every store module. I only fetch data if loaded is false. This approach is more suitable in apps that have user generated content and require authentication. It will work optimally with SSR and client-side, and it won't try to fetch data on every page visit if user has no data.
You could also simplify your fetch method like this:
async fetch()
{
await this.$store.dispatch('posts/getOnce')
}
Now your posts.js store module will look something like this:
export const state = () => ({
list: [],
loaded: false
})
export const actions = {
async getOnce({ dispatch, state }) {
if (!state.loaded) {
dispatch('posts/get')
}
},
async get({ commit, state }) {
await this.$axios.get(`/posts`)
.then((res) => {
if (res.status === 200) {
commit('set', res.data.posts)
}
})
}
}
export const mutations = {
set(state, posts) {
state.list = posts
state.loaded = true
}
}

Categories

Resources