CORS issue when requesting data from shopify app (remix template) during local theme development

Solved

CORS issue when requesting data from shopify app (remix template) during local theme development

Danh11
Shopify Partner
84 2 38

I am developing ontop of the Remix app template. I have a route setup to return user data from the database.

 

I am making a request via the shopify app proxy to a route setup to fetch data from the app.

I understand the shopify app proxy won't work while running the theme locally (I think?), so I have a domain pointing to a tunnel which points to the app running on localhost.

 

I can successfully make requests by hitting the endpoint via the tunnel in my browser.

 

Although the request is rejected when being made from the theme locally. I am returning headers to the preflight request, which I think are right, but it's still not working.

 

I'm wondering if some middlewear when running the theme locally is interfering with something.

 

api.get-user.jsx

export async function loader({ request }) {
  const corsHeaders = {
    "Access-Control-Allow-Origin": "http://127.0.0.1:9292",
    "Access-Control-Allow-Methods": "GET, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type",
  };

  if (request.method === 'OPTIONS') {
    return json({
      status: 200,
      headers: corsHeaders,
      body: ''
    });
  }
  
  return json({ body: 'data' });
}

JS in theme

async getUser() {    
    const url = `${this.appUrl}/api/get-user`;
  
    try {
      const response = await fetch(url, {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
      });
  
      if (!response.ok) throw new Error('Network response error:', response.statusText);
  
      const user = await response.json();
  
      return user;
  
    } catch (error) {
      console.error('Error:', error);
    }
  }


I am aware that I should be checking the digital signature from the request, and the preflight response headers probably aren't the greatest, but I'm just trying to get this working first.

Accepted Solution (1)

Danh11
Shopify Partner
84 2 38

This is an accepted solution.

The way to get this working is to use the CORS function from the remix-utils package.

 

If installing this on the Remix template of the shopify app, you will need to modify the remix.config.js file by including serverDependenciesToBundle: [ /^remix-utils.*/ ] in the module.exports as documented here.  This is because remix-utils is published as ESM only and the remix.config.js file has the serverModuleFormat set to cjs. My editor still yells at me about the incorrect import method, but it works nonetheless. This is my updated remix.config.js file:

 

remix.config.js

 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
  serverDependenciesToBundle: [
    /^remix-utils.*/,
  ],
};

 

Then, import cors from the remix-utils package and update the return in your loader function of your API route with the cors function, as per their docs linked above. It should look like the following:

 

api.get-user.jsx

 

import { json } from '@remix-run/node';
import { cors } from 'remix-utils/cors';

export async function loader({ request }) {

  const response = json({ body: 'data' });

  return await cors(request, response);
}

 

I am now successfully making requests from the theme which is running locally.

 

View solution in original post

Replies 18 (18)

Liam
Shopify Staff
2873 312 821

Hi Danh11,

 

From your code, it seems like you're handling the CORSflight request. However, you might want to add the "Access-Control-Allow-Credentials" header and set it to "true" if you're using cookies for authentication. Also, consider allowing all headers in theAccess-Control-Allow-Headers" in your CORS configuration for testing purposes:

const cors = {
  "Access-Control-Allow-Origin": "http://1270.0.1:9292",
  "Access-Control-Allow-Methods": "GET, OPTIONS",
  "Access-Control-Headers": "*",
  "Access-Control-Allow-Credentials": "true"
};

Moreover, ensure that your server is set up to handle OPTIONS requests. The preflight request is an OPTIONS request and server needs to respond with the appropriate CORS headers.

 

Hope this helps!

Liam | Developer Advocate @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit Shopify.dev or the Shopify Web Design and Development Blog

Danh11
Shopify Partner
84 2 38

Unfortunately the request is still being blocked. There were a couple of typos which I think were a mistake? By `Access-Control-Headers` I assume you meant `

Access-Control-Allow-Headers`? Fixing them didn't resolve the issue though.
 
As I'm using the Shopify app template, a lot of the server config is hidden and I can't find a way to access or modify it. All that I can see is `shopify.server.js`. I'm not able to find any info on how to modify the express instance which I think is running.
 
 

Danh11
Shopify Partner
84 2 38

This is an accepted solution.

The way to get this working is to use the CORS function from the remix-utils package.

 

If installing this on the Remix template of the shopify app, you will need to modify the remix.config.js file by including serverDependenciesToBundle: [ /^remix-utils.*/ ] in the module.exports as documented here.  This is because remix-utils is published as ESM only and the remix.config.js file has the serverModuleFormat set to cjs. My editor still yells at me about the incorrect import method, but it works nonetheless. This is my updated remix.config.js file:

 

remix.config.js

 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
  serverDependenciesToBundle: [
    /^remix-utils.*/,
  ],
};

 

Then, import cors from the remix-utils package and update the return in your loader function of your API route with the cors function, as per their docs linked above. It should look like the following:

 

api.get-user.jsx

 

import { json } from '@remix-run/node';
import { cors } from 'remix-utils/cors';

export async function loader({ request }) {

  const response = json({ body: 'data' });

  return await cors(request, response);
}

 

I am now successfully making requests from the theme which is running locally.

 

Danh11
Shopify Partner
84 2 38

Just to note - This actually still does not work when making the request via the Shopify app proxy while on local development. It does work ok when the theme is deployed and running from the Shopify server.

 

Although making requests to the app URL (with the tunnel running feeding traffic to the app running locally) does work in local development. So at least this makes it possible to develop locally. The app URL can be swapped out for the Shopify app proxy when it comes to deployment. This won't be a great dev experience if/when the app proxy signature is checked on the endpoint provided by the app, but that's a problem for another time.

myvirtualteams
Shopify Partner
28 0 9

Hey @Danh11 We're also stuck at cors error just i'm not creating a theme .
I have created an app using shopify remix.

 

Created an route in the app which will open in browser rather than on the store admin app dashboard.

 

Updated the remix config files like this 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  publicPath: process.env.SHOPIFY_APP_URL + "/build/",
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  includeRoutes: [
    require.resolve("./app/routes/api.jsx"), // Include your API route
  ],
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
};

As you can see I've give the public path with url where our app will be hosting I've named it SHOPIFY_APP_URL but it'll be heroku url where our app is hosted.

 

We've to do it as when we use as proxy with default publivPath it try to access build files using shopify domain and there is error not found.

We've add that route as app proxy so we can access through the store.

 

Then we've hosted our app on the heroku and added that url like this heroku_url/app_route in app proxy.

Now we're geting files but getting cors error while running that url as app aproxy.

 

css file is able to load but the build js files are giving cors errors.

 

I dont able to get where i will add cors headers so my store can access the shopify app build files hosted on heroku can remove cors errors

 

 

My Virtual Teams
Danh11
Shopify Partner
84 2 38

I'm not really familiar with what you're trying to do there, sorry!

 

If you're trying to define a new route, that would be done with a jsx or tsx file named appropriately. For example, api.endpoint.jsx for the path of /api/endpoint. You can then use the cors package that I mentioned in my previous comment to add the headers to the response.

 

Unless I have misinterpreted what you're trying to achieve?

seunoyebode
Shopify Partner
11 0 0

 

Hello @Danh11 

Thanks, i've been trying to make a fetch request from my extension to my server. Even tried what worked for you. My loader function is similar to yours. Any idea what I'm doing wrong?



fetch('https://insight-fog-gmc-implies.trycloudflare.com/app/make', {
  method: "POST",
  body: "Hello, World!",
  headers: {
    "Content-Type": "text/plain"
  }
})

 

Danh11
Shopify Partner
84 2 38

Hey! I need a little more information 🙂 What error are you getting? What does the function look like where you're making this request? What does your route look like? Is the route being hit by the request? Is it failing to return anything at all? 

seunoyebode
Shopify Partner
11 0 0

Thank you so much Danh11 for your response

this is the error I get from my browser when I trigger the fetch request

seunoyebode_0-1703941802838.png

I also believe the route is being hit as I get this error in my terminal 

seunoyebode_1-1703941901217.png

this is how my js in the liquid file looks like

seunoyebode_2-1703942020298.png

 

my remix route looks like this (code block), My primary aim here is to be able to send data from my theme app extension to my server 

import {
  Card,
  Page,
  Text,
  Layout,
  Divider,
  PageActions,
  BlockStack,
  RadioButton,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { useState, useCallback} from 'react';
import { useSubmit, useNavigation } from "@remix-run/react";
import { redirect, json } from "@remix-run/node";
import { cors } from 'remix-utils/cors';

export const loader = async ({ request }) => {
  const { admin, session } =  await authenticate.admin(request);

   console.log(cors(request),"cors log");
   console.log(request,"request log");

   return null;


 

Danh11
Shopify Partner
84 2 38

It seems like the error is occuring during the preflight request that the client is making. The server isn't handling the OPTIONS request type, which is what the preflight is, so it's causing the error and in turn the client also fails on the preflight request.

 

It's a bit hard for me to tell you exactly how to fix it, but look into what a preflight request is and how to handle it. Maybe ChatGPT could point you in the right direction as well.

seunoyebode
Shopify Partner
11 0 0

Thanks for your response Dan.

I'm still on it 😅.

This time around. I'm hitting the proxy URL directly (image attached). However, it redirects to "https://store-url.com/auth/login" . I have the below in my loader function as seen on the Shopify Remix Template App Proxy Page

import { json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import { getMyAppModelData } from "~/db/model.server";
export const loader = async ({ request }) => {
  // Get the session for the shop that initiated the request to the app proxy.
  const { session } = await authenticate.public.appProxy(request);

  // Use the session data to make to queries to your database or additional requests.
  return json(await getMyAppModelData({shop: session.shop));
};



Do you know what the issue could be?

Thank you.


seunoyebode_0-1705891339390.png

 

techaccounts
Shopify Partner
2 0 0

Were you able to solve it, i am stuck at same point

seunoyebode
Shopify Partner
11 0 0

This YouTube video was helpful in properly setting up Shopify App Proxy. 

However, it didn’t help with properly getting back the data sent from the BE. 

I had to settle for the approach in this YouTube video

if you ever figure out a way to use App Proxy and also get the data sent from the BE in your liquid file, please share 

techaccounts
Shopify Partner
2 0 0

i can create product using app proxy but remix server doesn't respond with proper 200 status and not able to send json as well

attaboiaj
Shopify Partner
22 4 5

Did you try it by running by hosting urls somewhere
I hosted my app on a VPS and I am getting the cors error 

mehmettekn
Shopify Partner
4 0 0

Does anyone know how to implement this solution with the current version of the Shopify Remix app template now with Vite? There isn't a remix.config.js file anymore.

attaboiaj
Shopify Partner
22 4 5

You don't need to setup any Vite or remix configs


see my below code and just fixes your code similar to mine

 

Issue is: 
When you call Remix APIs Shopify send the OPTIONS requests so we need to CORS safe them and that's exactly what I did with my code 

https://community.shopify.com/c/online-store-and-theme/cors-issue-when-requesting-data-from-shopify-...

attaboiaj
Shopify Partner
22 4 5

Ok if you are new to this I wasted a day to fix this error 

this error comes when you do POST/GET request to your remix API from your DEV store via embeds 

Reason is you are not managing your OPTIONS REQUEST correctly 

NOTE: you never gets this error when you using this "Cloudflare tunnel Urls" You gets these Cors errors when you host you host your Remix somewhere like a VPS

and with remix this is how you should you mange it

remix-utils is the main package here
check it's doc for how to restrict your site more.

 

import { json } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";


export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const shop = url.searchParams.get("shop");
  
  // This is where you need to cors safe your request
  if (request.method === "OPTIONS") {
    const response = json({
      status: 200,
    });
    return await cors(request, response);
  }

  if (!shop) {
    return json({
      message: "Missing data. Required data: shop",
      method: "GET",
    });
  }

    const response = json({
    ok: true,
    message: "Success",
    data: "analyticDbResponse",
  });

  return cors(request, response);
}

const addAnalytic = async (data) => {
 return "some db stuff"
};

export async function action({ request }: ActionFunctionArgs) {

  // for double check I added this here too
  if (request.method === "OPTIONS") {
    const response = json({
      status: 200,
    });
    return await cors(request, response);
  }

  if (request.method == "POST") {
    let data = await request.json();
    try {
      if (data.shop !== undefined) {
        addAnalytic(data); // me doing some db updates
        const response = json({
          message: "success",
        });

        await cors(request, response);
        return response;
      } else {
        return json({
          message: "Missing data. Required data: shop",
          method: "POST",
        });
      }
    } catch (error) {
      console.log("error", error);
      return json(
        {
          message: "error with endpoint",
          method: "POST",
        },
        400,
      );
    }
  }
}