App reviews, troubleshooting, and recommendations
Hi Developers,
I am developing the simple private Shopify app via Remix JS.
I have created backend API endpoint for shipping calculation in app.caluclateshipping.tsx as per my below code:
import { json, LoaderFunction } from "@remix-run/node";
import { authenticate } from "../shopify.server"; // Adjust path as needed
import { cors } from "remix-utils/cors";
export const loader: LoaderFunction = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const urlParams = new URL(request.url).searchParams;
const zipCode = urlParams.get("zip");
if (!zipCode) {
// Respond with CORS headers when the zip code is not provided
return new Response(JSON.stringify({ error: "Zip code is required" }), {
status: 400,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // Set to specific domain in production
}
});
}
// Fetch all relevant metafields
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
const parsedResponse = await response.json();
const metafields = parsedResponse.data?.product?.metafields?.edges || [];
// Look for the metafield that contains the requested zip code
for (const { node } of metafields) {
const data = JSON.parse(node.value);
if (data.some(entry => entry.range.includes(zipCode))) {
const matchingEntry = data.find(entry => entry.range.includes(zipCode));
if (matchingEntry) {
// Respond with CORS headers and the shipping cost
return new Response(JSON.stringify({ shipping_cost: matchingEntry.price }), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // Set to specific domain in production
}
});
}
}
}
// Respond with CORS headers when no shipping cost is found
return new Response(JSON.stringify({ error: "No shipping cost found for this zip code" }), {
status: 404,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // Set to specific domain in production
}
});
};
and in the Shopify admin when i try to load the below link it displays as "{"shipping_cost": "8"}" which is right
https://admin.shopify.com/store/taktwoapp/apps/zip-code-3/app/calculateshipping?zip=8848. Below is the screenshot:
Now below is my liquid code in extensions to display the price based on the postcode:
<div>
<!-- Zip code input for shipping cost calculation -->
<label class="zipcode-label" for="zip-code-input">Estimated Shipping</label>
<div class="input-field-btn">
<input type="text" id="zip-code-input" name="zip-code" placeholder="Enter zipcode or postcode">
<button id="calculate-shipping-btn" type="button">Find</button>
</div>
<p id="shipping-cost-output"></p>
</div>
<script>
document.getElementById('calculate-shipping-btn').addEventListener('click', async function() {
const zipCode = document.getElementById('zip-code-input').value.trim();
// Check if the user entered a valid zip code
if (!zipCode) {
alert('Please enter a valid zip code.');
return;
}
// Provide feedback to the user that the calculation is in progress
document.getElementById('shipping-cost-output').innerText = 'Calculating...';
try {
// Make the API request to fetch the shipping cost
const response = await fetch(`https://admin.shopify.com/store/taktwoapp/apps/zip-code-3/app/calculateshipping?zip=${zipCode}`, {
method: 'GET',
mode: 'cors', // Allows cross-origin requests with CORS headers
credentials: 'include', // Include credentials (e.g., cookies) with the request
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest' // Custom header to indicate an AJAX request
}
});
// Check if the response was successful
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
// Parse the JSON response
const data = await response.json();
// Display the shipping cost or error message based on the response
if (data.shipping_cost) {
document.getElementById('shipping-cost-output').innerText = `Shipping Cost: $${data.shipping_cost}`;
} else if (data.error) {
document.getElementById('shipping-cost-output').innerText = data.error;
}
} catch (error) {
// Handle any errors that occurred during the fetch
console.error('Error fetching shipping cost:', error);
document.getElementById('shipping-cost-output').innerText = 'Error calculating shipping cost.';
}
});
</script>
Now, the issue is that in the front end it is throwing an CORS issue, I would really appreciate it if I could get help regarding this issue.
Many thanks,
Solved! Go to the solution
This is an accepted solution.
Hi @Kit_,
We're very close now! - Just a bit more to go.
The issue is that, when using a proxy endpoint, authentication is still needed to verify the request. In Remix, you can use the following approach:
await authenticate.public.appProxy(request);
Add this line before running your GraphQL query, typically as the first line in your loader function. This ensures that your requests are properly authenticated when using your proxy.
Here's how you might implement it:
const { admin } = await authenticate.public.appProxy(request);
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
This should help you retrieve the metafields without running into further issues related to authenticate.admin(request).
Feel free to reach out if you hit any other bumps!
Best regards,
Foladun | Customer Account Deluxe
• Boost engagement and sales with Customer Account Deluxe: a Modern Customer Page with Loyalty, Wishlist, and Social Login (Free plan available✨)
• Drive more revenue, increases user retention and repeat purchases, with simple one-click installation.
You can use shopify public proxy resolve this issue
Hi @Kit_ ,
Foladun here from the Customer Account Deluxe app team. 👋
To avoid CORS issues, you should create and use a dedicated app proxy link.
This guide explains how to create proxy links:
Shopify Dev: Display dynamic store data with app proxies
Here are examples of setting up the Remix server to authenticate and handle received requests:
Shopify Dev: Remix App Proxy
Feel free to let me know if you need more help with this.
Best regards,
Foladun | Customer Account Deluxe
• Boost engagement and sales with Customer Account Deluxe: a Modern Customer Page with Loyalty, Wishlist, and Social Login (Free plan available✨)
• Drive more revenue, increases user retention and repeat purchases, with simple one-click installation.
Hi Foladun,
Thank you so much for your feedback.
Since I'm new to the Remix JS world, I did go through the documentation and based on the URLs I setup the App proxy as per below:
Since I am trying to access the shipping_cost value in the admin backend that I created in app.calculateshipping.tsx as per below. All I am trying to do is to get the shipping cost from the extensions in zipcode.liquid
import { json, LoaderFunction } from "@remix-run/node";
import { authenticate } from "../shopify.server"; // Adjust path as needed
export const loader: LoaderFunction = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const urlParams = new URL(request.url).searchParams;
const zipCode = urlParams.get("zip");
if (!zipCode) {
return json({ error: "Zip code is required" }, { status: 400 });
}
// Fetch all relevant metafields
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
const parsedResponse = await response.json();
const metafields = parsedResponse.data?.product?.metafields?.edges || [];
// Look for the metafield that contains the requested zip code
for (const { node } of metafields) {
const data = JSON.parse(node.value);
if (data.some(entry => entry.range.includes(zipCode))) {
const matchingEntry = data.find(entry => entry.range.includes(zipCode));
if (matchingEntry) {
return json({ shipping_cost: matchingEntry.price }, { status: 200 });
}
}
}
return json({ error: "No shipping cost found for this zip code" }, { status: 404 });
};
So my question would be what would be the fetch url for below in extensions liquid code?
try {
const response = await fetch(`/apps/shipping/calculate-shipping?zip=${zipCode}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error('Network response was not ok.');
}
const data = await response.json();
if (data.shipping_cost) {
document.getElementById('shipping-cost-output').innerText = `Shipping Cost: $${data.shipping_cost}`;
} else if (data.error) {
document.getElementById('shipping-cost-output').innerText = data.error;
}
} catch (error) {
console.error('Error fetching shipping cost:', error);
document.getElementById('shipping-cost-output').innerText = 'Error calculating shipping cost.';
}
});
Hi @Kit_,
I'm glad to hear you're making progress! 🙂
Your proxy URL on the frontend should be:
const response = await fetch(`/apps/shipping-calc?zip=${zipCode}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
Given how your proxy configuration is set up, '/apps/shipping-calc' corresponds to your proxy URL, which in this example translates to 'https://get-syndrome-lands-obtained.trycloudflare.com/app/proxy'.
Therefore, the equivalent proxy URL would be:
const response = await fetch(`https://get-syndrome-lands-obtained.trycloudflare.com/app/proxy?zip=${zipCode}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
Hope this helps! Feel free to let me know how it goes.
Best regards,
Foladun | Customer Account Deluxe
• Boost engagement and sales with Customer Account Deluxe: a Modern Customer Page with Loyalty, Wishlist, and Social Login (Free plan available✨)
• Drive more revenue, increases user retention and repeat purchases, with simple one-click installation.
Hi Foladun,
Thank you so much for your help, I think you are the only one that could help me to resolve this issue.
I have set up the fetch url as per below:
<div>
<!-- Zip code input for shipping cost calculation -->
<label class="zipcode-label" for="zip-code-input">Estimated Shipping</label>
<div class="input-field-btn">
<input type="text" id="zip-code-input" name="zip-code" placeholder="Enter zipcode or postcode">
<button id="calculate-shipping-btn" type="button">Find</button>
</div>
<p id="shipping-cost-output"></p>
</div>
<script>
document.getElementById('calculate-shipping-btn').addEventListener('click', async function() {
const zipCode = document.getElementById('zip-code-input').value.trim();
if (!zipCode) {
alert('Please enter a valid zip code.');
return;
}
document.getElementById('shipping-cost-output').innerText = 'Calculating...';
try {
const response = await fetch(`/apps/proxy?zip=${zipCode}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error('Network response was not ok.');
}
const data = await response.json();
// Display the result based on the response
if (data.shipping_cost) {
document.getElementById('shipping-cost-output').innerText = `Shipping Cost: $${data.shipping_cost}`;
} else if (data.ok && data.data) {
// Example: If the response is a general data object as shown in the forum example
document.getElementById('shipping-cost-output').innerText = `Store Info: ${JSON.stringify(data.data)}`;
} else if (data.error) {
document.getElementById('shipping-cost-output').innerText = data.error;
}
} catch (error) {
console.error('Error fetching shipping cost:', error);
document.getElementById('shipping-cost-output').innerText = 'Error calculating shipping cost.';
}
});
</script>
Below is the screenshot of overall project structure:
Below is my updated app.calculateshipping.tsx:
import { json, LoaderFunction } from "@remix-run/node";
import { authenticate } from "../shopify.server"; // Adjust path as needed
export const loader: LoaderFunction = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const urlParams = new URL(request.url).searchParams;
const zipCode = urlParams.get("zip");
if (!zipCode) {
return json({ error: "Zip code is required" }, { status: 400 });
}
// Fetch all relevant metafields
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
const parsedResponse = await response.json();
const metafields = parsedResponse.data?.product?.metafields?.edges || [];
// Look for the metafield that contains the requested zip code
for (const { node } of metafields) {
const data = JSON.parse(node.value);
if (data.some(entry => entry.range.includes(zipCode))) {
const matchingEntry = data.find(entry => entry.range.includes(zipCode));
if (matchingEntry) {
return json({ shipping_cost: matchingEntry.price }, { status: 200 });
}
}
}
return json({ error: "No shipping cost found for this zip code" }, { status: 404 });
};
and in the Shopify backend, i get the output as below which is correct, the url is => https://admin.shopify.com/store/<store-name>/apps/zip-code-3/app/proxy?zip=8848:
And in the front store, when i try to search shipping price for the zipcode via extensions, its got below error message in the console log:
"GET https://<store-name>.myshopify.com/auth/login 404 (Not Found)"
"Error fetching shipping cost: Error: Network response was not ok." as per below:
In the "Apps and sales channels" the App Proxy Url looks like below screenshot:
Foladun, I would really appreciate your any input and help 🙏
Many thanks
Hi Foladun,
Finally I found the issue, the issue is in the app.proxy.tsx, now right now my Remix app works for the below code:
import { json } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const urlParams = new URL(request.url).searchParams;
const zipCode = urlParams.get("zip");
// Handle CORS preflight request
if (request.method === "OPTIONS") {
const response = json({ status: 200 });
return await cors(request, response);
}
if (!zipCode) {
return json({ error: "Zip code is required" }, { status: 400 });
}
// Dummy data setup or you can fetch actual data from Shopify
const shippingCostMap: Record<string, number> = {
"1000": 10,
"2000": 15,
"3000": 20,
"8848": 8,
};
const shippingCost = shippingCostMap[zipCode] || null;
if (shippingCost !== null) {
const response = new Response(JSON.stringify({ shipping_cost: shippingCost }), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
return await cors(request, response); // Apply CORS headers
}
return json({ error: "No shipping cost found for this zip code" }, { status: 404 });
};
However, if i change this app.proxy.tsx to below code as i am trying to get the zipcodes and associated price from the product metafield that i have stored. Now while running the graphql query it runs the 'authenticate.admin(request)' hence it was having issue.
Now, i am trying to run the below query without having to run authenticate.admin(request):
// Fetch all relevant metafields
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
I would really appreciate your help.
Many thanks
This is an accepted solution.
Hi @Kit_,
We're very close now! - Just a bit more to go.
The issue is that, when using a proxy endpoint, authentication is still needed to verify the request. In Remix, you can use the following approach:
await authenticate.public.appProxy(request);
Add this line before running your GraphQL query, typically as the first line in your loader function. This ensures that your requests are properly authenticated when using your proxy.
Here's how you might implement it:
const { admin } = await authenticate.public.appProxy(request);
const response = await admin.graphql(`
query {
product(id: "gid://shopify/Product/8304181903529") {
metafields(namespace: "custom_app", first: 100) {
edges {
node {
key
value
}
}
}
}
}
`);
This should help you retrieve the metafields without running into further issues related to authenticate.admin(request).
Feel free to reach out if you hit any other bumps!
Best regards,
Foladun | Customer Account Deluxe
• Boost engagement and sales with Customer Account Deluxe: a Modern Customer Page with Loyalty, Wishlist, and Social Login (Free plan available✨)
• Drive more revenue, increases user retention and repeat purchases, with simple one-click installation.
Hi Foladun,
Thank you so so much 🙏
It's been two days since i was working to resolve this issue.
Thank you so much again 😊
Many thanks
As 2024 wraps up, the dropshipping landscape is already shifting towards 2025's trends....
By JasonH Nov 27, 2024Hey Community! It’s time to share some appreciation and celebrate what we have accomplis...
By JasonH Nov 14, 2024In today’s interview, we sat down with @BSS-Commerce to discuss practical strategies f...
By JasonH Nov 13, 2024