I cannot use fetchSync on a api route. What do I need to change in my Shopify settings - shopify

I cannot fetch from my Storefront api with the queryShop method that comes with a api route. The route itself is working fine. I can use fetchSync (a fetch function from the hydrogen framework) to fetch from a specific route. But whenever I use the queryShop to fetch something from my Storefront it thows this error below
Error processing route: http://localhost:3000/api/test
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:14062:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async eval (/node_modules/#shopify/hydrogen/dist/esnext/utilities/fetch.js:15:26)
at async queryShop (/node_modules/#shopify/hydrogen/dist/esnext/utilities/apiRoutes.js:111:16)
at async Object.api [as resource] (/src/routes/api/test.server.ts:4:15)
at async Module.renderApiRoute (/node_modules/#shopify/hydrogen/dist/esnext/utilities/apiRoutes.js:119:20)
at async processRequest (/node_modules/#shopify/hydrogen/dist/esnext/entry-server.js?v=f2dfe530:184:25)
at async handleRequest (/node_modules/#shopify/hydrogen/dist/esnext/entry-server.js?v=f2dfe530:163:20)
I know that the problem is inside the settings of my Shopify store. Because when I swap out the config of the store to the hydrogen preview store it will work.
export default defineConfig({
shopify: {
storeDomain: "hydrogen-preview.myshopify.com",
storefrontToken: "3b580e70970c4528da70c98e097c2fa0",
storefrontApiVersion: "2022-07",
},
})
// components/Test.client.tsx
import {
fetchSync
} from "#shopify/hydrogen"
const Test = () => {
const response = fetchSync("/api/test")
console.log(response)
return (
<div>Test</div>
)
}
export default Test
import { HydrogenApiRouteOptions, HydrogenRequest } from "#shopify/hydrogen";
export async function api(request: HydrogenRequest, {queryShop}: HydrogenApiRouteOptions) {
console.log(await queryShop({ query: `query ShopName { shop { name } }`,}))
return "test"
}
I Expected the shop name result form the queryShop method.

Related

Nuxt ServerMiddleware Returning HTML Doc

I am building out a webpage which needs to make a call to the Google Geocoder api.
In order to hide the api key from public view, I am trying to set up server middleware to act as a REST api endpoint.
I have checked through all of the documentation and copied all of it, but the response is always the same. I receive the entirety of the html body back from the axios request rather than anything else I send back via express.
In my component I have the following code:
computed: {
normalizedAddress() {
return `${this.member.address.street} ${this.member.address.city}, ${this.member.address.state} ${this.member.address.zip}`.replace(
/\s/g,
'+'
)
}
},
methods: {
async getLocation() {
try {
const res = await axios.get(
`/api/geocode/${this.normalizedAddress}`
)
console.log(res)
} catch (err) {
console.log(err)
}
}
},
In nuxt.config.js I have this setup
serverMiddleware: ['~/api/geocode.js'],
In the root of my project I have an api folder with geocode.js stored there.
geocode.js is below
import express from 'express';
import axios from "axios";
let GEO_API = "MY_API_KEY"
const app = express();
app.use(express.json());
app.get("/", async (req, res) => {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${req.params.address}&key=${GEO_API}`
try {
const code = await axios.get(uri);
if (code.status !== "OK") {
return res.status(500).send(code.status)
}
return res.status(200).send(code);
} catch (err) {
return res.status(500).send(err);
}
});
export default {
path: "/api/geocode/:address",
handler: app
}
Again. The response always has the entire html document from the website sent back (some 100 pages of code).
Even when I set the response to fixed text, that is not sent.
The only detail I can think of that might be interrupting it is that I have my own custom routing setup using the #nuxtjs/router build module in use.

Enzyme integration testing: axios.get call not being executed in redux-saga

I am trying to setup tests for some an action creator that is triggering a redux saga.
My saga retrieves a word from a local flask server (will always return the same word) and then displays that word. This is not my real-life case but I tried to start with something easy...
My action creator and saga work as expected when I trigger them with a button in my react app (the word is retrieved from the server, stored in my redux store and the displayed with a selector in my react component), but I cannot get the test to succeed.
I would like to test only the redux part, not the actual rendered react component (not sure if that is part of my problem or not)
I use Enzyme for tests, my store is created correctly and can dispatch the action. I can also see that my saga is being called with the console logs:
My test code:
import { Store } from 'redux';
import { RootState } from '../root.reducer';
import { storeFactory } from '../../../test/testUtils';
import { getSecretWord } from './secret-word.actions';
describe('getSecretWord action creator', () => {
let store: Store<RootState>;
beforeEach(() => {
store = storeFactory();
});
test('add response word to state', () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});
});
and my saga function:
export function* getSecretWordSaga(action: getSecretWordAction): Generator<ForkEffect | CallEffect | PutEffect, void, unknown>
{
try {
console.log('getSecretWordSaga() saga started');
console.log('before axios query call:');
const response:any = yield call(api.get, '/api/word');
// const response = {data: { word: 'party'}, status:200}
console.log('axios query returned: ');
console.log(response);
yield put(setSecretWord(response.data.word));
console.log('getSecretWordSaga() saga finsshed');
} catch (err) {
console.log('error occured:');
console.log(err);
console.log('getSecretWordSaga() saga finsshed with errors');
}
}
export function* getSecretWordSagaStart(): Generator<
ForkEffect<never>,
void,
unknown
> {
yield takeLatest(SecretWordActionTypes.GET_SECRET_WORD, getSecretWordSaga);
}
The axios api is very basic and it includes two interceptors for logging purposes:
import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:5000',
responseType: 'json',
});
api.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
api.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
I can see in the logs (in "npm test") that I get log for the line "before axios query call:' and one console.log for the request interceptor (everything looks fine there), but no more logs afterwards (neither success nor error)
If I comment out the "yield call.." and hardcode the response (like in the commented out line below), my saga runs through the end and my test succeeds.
Why is the yield Call(api.get, '/api/word') not being executed (and I don't get any error message)?
The code is my opinion correct as it is running fine when executed in react. My flask server is obviously also running and I can see in the flask app than no call to the api are being made by the running tests.
I obviously plan to mock that api call but was also running into some problems there, that's why I first wanted to get the real api call working.
After trying many different ways for adding a timeout, setting the testing function to async and adding a setTimeout in a promise did work.
It's not ideal as I have to set the timeout to a specific value, but I could not figure out a better way to get it working.
test("add response word to state", async () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
await new Promise(res => setTimeout(res, 1000));
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});

Nuxt.js - The best place for API calls

I'm new to Vue.js Nuxt and all front-end stuff.
I have a question about API calls. I'm not sure what is the right way, the best practice here.
I have a store. In that store, I have actions that are calling my API and sets state eg.
async fetchArticle({ state, commit }, uuid) {
const response = await this.$axios.get(`articles/${uuid}/`)
commit('SET_ARTICLE', response.data)
},
And that is fine it is working for one component.
But what if I want to just fetch the article and not changing the state.
To be DRY first thing that comes to my mind is to create the service layer that is fetching the data and is used where it is needed.
Is it the right approach? Where can I find some real-world examples that I can take inspiration from?
Using the repository pattern to abstract your API is definitely a good idea! Whether you use the #nuxtjs/axios module or the #nuxt/http module, you can pass either instance to your repository class/function. Below a real world example of an abstracted "repository.js" file.
export default $axios => resource => ({
index() {
return $axios.$get(`/${resource}`)
},
create(payload) {
return $axios.$post(`/${resource}`, payload)
},
show(id) {
return $axios.$get(`/${resource}/${id}`)
},
update(payload, id) {
return $axios.$put(`/${resource}/${id}`, payload)
},
delete(id) {
return $axios.$delete(`/${resource}/${id}`)
}
})
You can then create a plugin to initialize all different kinds of repositories for your endpoints:
import createRepository from '~/path/to/repository.js'
export default (ctx, inject) => {
const repositoryWithAxios = createRepository(ctx.$axios)
const repositories = {
posts: repositoryWithAxios('posts'),
users: repositoryWithAxios('users')
//...
}
inject('repositories', repositories)
}
Further read: Organize and decouple your API calls in Nuxt.js
I will an example of a service layer implementation for my portfolio to create my dashboard that shows some statics about my github and stackoverflow profiles, to do this i created a folder called services inside the project root :
pages
services
|_AxiosConfig.js
|_GitHubService.js
|_StackoverflowService.js
...
in the AxiosConfig.js file i put i created an axios instance with its configuration :
import axios from 'axios';
const clientAPI = url =>
axios.create({
baseURL: url,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
export default clientAPI;
then in my GitHubService.js i imported that axios instance called clientAPI which i used to my requests :
import clientAPI from './AxiosConfig';
const baseURL = 'https://api.github.com';
export default {
getUser(name) {
return clientAPI(baseURL).get('/users/' + name);
},
getRepos(name){
return clientAPI(baseURL).get('/users/' + name+'/repos');
},
getEvents(name,page){
return clientAPI(baseURL).get('/users/' + name+'/events?per_page=100&page='+page);
},
getLastYearCommits(name,repo){
return clientAPI(baseURL).get('/repos/' + name+'/'+repo+'/stats/commit_activity');
}
};
then in my page i used asyncData hook to fetch my data :
import GitHubService from '../../services/GitHubService'
export default {
...
async asyncData({ error }) {
try {
const { data } = await GitHubService.getUser("boussadjra");
const resRepos = await GitHubService.getRepos("boussadjra");
return {
user: data,
repos: resRepos.data
};
} catch (e) {
error({
statusCode: 503,
message: "We cannot find the user"
});
}
}
I wanted to use axios in my service/service.js file, so instead of passing axios, I accessed it directly like this:
export default {
async fetchArticle() {
let response = await $nuxt.$axios.$get('/api-url')
return response
},
}
In Nuxt, if you want to just get the data without keeping it in your store, you could use the asyncData function, which asynchronously loads data (from API calls and the like) and pushes it into the component's data object before rendering.

Can I call storyapi from vuex?

I'm using storyblok-nuxt module. I plugged it in nuxt.cofig.js and it works fine in page when I call it directly in the asyncData method as such:
asyncData({ app }) {
return app.$storyapi.get("cdn/stories/articles", {
version: "draft"
})
In order to call it from vuex I'm importing it:
import storyapi from 'storyapi'
But Nuxt gives me an error:
Cannot find module 'storyapi'
Can I use this module in vuex, and if yes - what's solution?
Using storyapi with Nuxt is very easy. In your asyncData you can dispatch your action like:
asyncData ({ store }) {
store.dispatch('loadSettings', {version: "draft"})
}
And in your store actions, you can go for this.$storyapi directly. There is no need to import anything. Nuxt take cares of everything for you:
export const actions = {
loadSettings({commit}, context) {
return this.$storyapi.get("cdn/stories/articles", {
version: context.version
}).then((res) => {
// execute your action and set data
commit('setSettings', res.data)
})
}
}
For more info:
How to use the nuxt context in an vuex store?

How to structure api calls in Vue.js?

I'm currently working on a new Vue.js application. It depends heavily on api calls to my backend database.
For a lot of things I use Vuex stores because it manages shared data between my components. When looking at other Vue projects on github I see a special vuex directory with files that handles all the actions, states and so on. So when a component has to call the API, it includes the actions file from the vuex directory.
But, for messages for example, I don't want to use Vuex because those data is only important for one specific view. I want to use the component specific data here. But here is my problem: I still need to query my api. But I shouldn't include the Vuex actions file. So in that way I should create a new actions file. This way I have a specific file with api actions for vuex and for single components.
How should I structure this? Creating a new directory 'api' that handles actions for both vuex data and component-specific data? Or separate it?
I am using axios as HTTP client for making api calls, I have created a gateways folder in my src folder and I have put files for each backend, creating axios instances, like following
myApi.js
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:3000/api/v1',
timeout: 5000,
headers: {
'X-Auth-Token': 'f2b6637ddf355a476918940289c0be016a4fe99e3b69c83d',
'Content-Type': 'application/json'
}
})
Now in your component, You can have a function which will fetch data from the api like following:
methods: {
getProducts () {
myApi.get('products?id=' + prodId).then(response => this.product = response.data)
}
}
Similarly you can use this to get data for your vuex store as well.
Edited
If you are maintaining product related data in a dedicate vuex module,
you can dispatch an action from the method in component, which will internally call the backend API and populate data in the store, code will look something like following:
Code in component:
methods: {
getProducts (prodId) {
this.$store.dispatch('FETCH_PRODUCTS', prodId)
}
}
Code in vuex store:
import myApi from '../../gateways/my-api'
const state = {
products: []
}
const actions = {
FETCH_PRODUCTS: (state, prodId) => {
myApi.get('products?id=' + prodId).then(response => state.commit('SET_PRODUCTS', response))
}
}
// mutations
const mutations = {
SET_PRODUCTS: (state, data) => {
state.products = Object.assign({}, response.data)
}
}
const getters = {
}
export default {
state,
mutations,
actions,
getters
}
Note: vue-resource is retired ! Use something else, such as Axios.
I'm using mostly Vue Resource.I create services directory, and there put all connections to endpoints, for e.g PostService.js
import Vue from 'vue'
export default {
get(id) {
return Vue.http.get(`/api/post/${id}`)
},
create() {
return Vue.http.post('/api/posts')
}
// etc
}
Then in my file I'm importing that service and create method that would call method from service file
SomeView.vue
import PostService from '../services/PostService'
export default {
data() {
item: []
},
created() {
this.fetchItem()
},
methods: {
fetchItem() {
return PostService.get(to.params.id)
.then(result => {
this.item = result.json()
})
}
}
}
Based on concept of Belmin Bedak`s answer, i have wrapped it all into a simple library:
https://github.com/robsontenorio/vue-api-query
You can request your API like this:
All results
// GET /posts?filter[status]=ACTIVE
let post = await Post
.where('status', 'ACTIVE')
.get()
Specific result
// GET /posts/1
let post = await Post.find(1)
Editing
// PUT /posts/1
post.title = 'Awsome!'
post.save()
Relationships
// GET /users/1
let user = await User.find(1)
// GET users/1/posts
let posts = await user
.posts()
.get()