CORS Issue in Remix JS while trying to get data from extensions

Solved

CORS Issue in Remix JS while trying to get data from extensions

Kit_
Shopify Partner
22 1 5

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:

 

Kit__0-1731037554620.png

 

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.

 

Kit__1-1731037852986.png

 

Many thanks,

Accepted Solution (1)
Deluxe-Foladun
Shopify Partner
56 9 11

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.

View solution in original post

Replies 8 (8)

iffikhan30
Shopify Partner
288 37 53

You can use shopify public proxy resolve this issue

Custom theme and app [remix] expert.

Email: irfan.sarwar.khan30@gmail.com
Chat on WhatsApp

Deluxe-Foladun
Shopify Partner
56 9 11

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.

Kit_
Shopify Partner
22 1 5

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:

Kit__1-1731375490566.jpeg

 

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.';
  }
});

 



Deluxe-Foladun
Shopify Partner
56 9 11

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.

Kit_
Shopify Partner
22 1 5

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:

 

Kit__0-1731451790465.jpeg

 

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: 

 

Kit__1-1731452388312.png

 

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:

Kit__2-1731452827795.jpeg

 

In the "Apps and sales channels" the App Proxy Url looks like below screenshot:

Kit__3-1731453084819.jpeg

 

Foladun, I would really appreciate your any input and help 🙏


Many thanks

Kit_
Shopify Partner
22 1 5

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

Deluxe-Foladun
Shopify Partner
56 9 11

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.

Kit_
Shopify Partner
22 1 5

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