FROM CACHE - en_header

Problem Querying REST API from React + Node

Solved
Thresher
Shopify Partner
20 2 24

Hi all,

 

I've been following the Node and React Shopify app tutorial, and have managed to get basically everything working. I can access the GraphQL API no problem, and have written a few of my own queries and mutations. However, some of the data I need is only available through the Shopify REST API.

 

I've read through the Shopify OAuth guide and I'm able to retrieve a valid access_token. I'm using that to send a GET request to retrieve a list of collections, but I get a 401 Unauthorized response. I've tried using Postman to test the exact request, and it works fine. 

 

Is there anything special I need to do? Is it possible that some of the GraphQL code from the demo is interfering? I've posted my code below, in case it's helpful.

 

 

    fetch("https://my-fake-demo-store.myshopify.com/admin/api/2019-04/custom_collections.json", {
      mode: 'no-cors',
      method: 'GET',
      headers: {
        "X-Shopify-Access-Token": accessToken,
	"Content-type": "application/json",
      },
    }).then(response => {
      console.log(response)
      return response;
    }).then(json => {
      console.log(json)
    });

 

 

Accepted Solution (1)

Accepted Solutions
Thresher
Shopify Partner
20 2 24

This is an accepted solution.

I managed to figure this out, and wanted to share an update in case anyone runs into the same issue. My problem was that my API calls were coming from the frontend instead of the backend and they were being rejected. My solution was to create my own backend routes and call them from my frontend.

 

1. Add koa-router to server.js

const Router = require('koa-router');
const router = new Router();

2. Add custom backend routes to make API calls to Shopify:

router.get('/api/:object', async (ctx) => {
    try {
      const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2019-04/" + ctx.params.object + ".json", {
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
        },
      })
      .then(response => response.json())
      .then(json => {
        return json;
      });
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

3. Call backend routes from React frontend:

getCollections = () => {
    var fetchUrl = "/api/custom_collections";
    var method = "GET";
    fetch(fetchUrl, { method: method })
    .then(response => response.json())
    .then(json => console.log(json))
  }

I'm not sure if this is the most effective solution, so feel free to chime in if you have suggestions to improve this.

View solution in original post

Replies 28 (28)
Alex
Shopify Staff
Shopify Staff
1561 81 324

Would you be able to provide an X-Request-Id response header after replicating? That would give me a look at the logs to see what might be happening.

 

Cheers.

Alex | 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 the Shopify Help Center or the Shopify Blog

Thresher
Shopify Partner
20 2 24

@Alex sure thing, I just added the X-Request-Id with a value of "thresher_collections_rest_api_test" to my header. Thanks for your response!

Alex
Shopify Staff
Shopify Staff
1561 81 324

Sorry for the confusion! I meant could you capture a response header which we provide called x-request-id. This will be a unique value I can use to track down logs.

 

Cheers!

Alex | 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 the Shopify Help Center or the Shopify Blog

Thresher
Shopify Partner
20 2 24

@Alex apologies, wasn't 100% sure what you were asking. I attached an image showing the response I get back from the above fetch request. The response.headers seem to be empty. Does my code above look correct, or am I missing something?

 

Screen Shot 2019-05-01 at 1.16.56 PM.png

 

Thresher
Shopify Partner
20 2 24

So after doing some more digging, it looks like I can't query the REST API because the requests are coming directly from my app frontend instead of backend. Does that make sense?

 

Are there examples of how to connect to the REST API via Node JS server + React app?

 

Edit: it appears that is the case. I'm able to access the REST API from my server.js file with the following code:

fetch("https://" + shop + "/admin/api/2019-04/custom_collections.json", {
  headers: {
    "X-Shopify-Access-Token": accessToken,
  },
})
.then(response => response.json())
.then(json => console.log(json))

I'm not sure how to set this up so I can call the API from my React front end though.

Thresher
Shopify Partner
20 2 24

This is an accepted solution.

I managed to figure this out, and wanted to share an update in case anyone runs into the same issue. My problem was that my API calls were coming from the frontend instead of the backend and they were being rejected. My solution was to create my own backend routes and call them from my frontend.

 

1. Add koa-router to server.js

const Router = require('koa-router');
const router = new Router();

2. Add custom backend routes to make API calls to Shopify:

router.get('/api/:object', async (ctx) => {
    try {
      const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2019-04/" + ctx.params.object + ".json", {
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
        },
      })
      .then(response => response.json())
      .then(json => {
        return json;
      });
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

3. Call backend routes from React frontend:

getCollections = () => {
    var fetchUrl = "/api/custom_collections";
    var method = "GET";
    fetch(fetchUrl, { method: method })
    .then(response => response.json())
    .then(json => console.log(json))
  }

I'm not sure if this is the most effective solution, so feel free to chime in if you have suggestions to improve this.

Ireneludi
Tourist
9 0 1

Hi Thresher, tried your solution, still getting 401. Where did you add the custom backend route in server.js? I have no backend experience, and I'm really confused...

app.prepare().then(()=>{
   const server = new Koa();
   server.use(session(server));
   server.keys = [SHOPIFY_API_SECRET_KEY];

   server.use(
     createShopifyAuth({
       apiKey: SHOPIFY_API_KEY,
       secret: SHOPIFY_API_SECRET_KEY,
       scopes: ['read_products', 'read_orders', 'write_products'],
       afterAuth(ctx) {
         const { shop, accessToken } = ctx.session;
         
            ctx.cookies.set('shopOrigin', shop, { httpOnly: false });
         ctx.redirect('/');
       },
     }),
   );

   server.use(graphQLProxy({version: ApiVersion.April19}));
   server.use(verifyRequest());
   server.use(async (ctx) => {
       await handle(ctx.req, ctx.res);
       ctx.respond = false;
       ctx.res.statusCode = 200;
       return
   });

   router.get('/api/:object', async (ctx) => {
    try {
      const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2019-04/" + ctx.params.object + ".json", {
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
        },
      })
      .then(response => response.json())
      .then(json => {
        return json;
      });
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

   server.listen(port, () => {
     console.log(`> Ready on http://localhost:${port}`);
   });

 

Thresher
Shopify Partner
20 2 24

@Ireneludi make sure that you include the code from step 1 at the top of server.js. You're missing a line below where you set the shopOrigin cookie:

ctx.cookies.set('accessToken', accessToken);

Let me know if that works!

Ireneludi
Tourist
9 0 1

@Thresher got this error  GET https://bd73afd9.ngrok.io/api/orders 404 (Not Found)

and my frontend fetch function is like

const fetchUrl = '/api/orders';
		const method = "GET";
		fetch(fetchUrl, {method: method,})
		.then(response=>response.json()).then(json => console.log(json))

then I tried to change the fetchUrl

const fetchUrl = 'https://getordertest.myshopify.com/api/orders.json';
		const method = "GET";
		fetch(fetchUrl, {method: method,})
		.then(response=>response.json()).then(json => console.log(json))

and I got : Access to fetch at 'https://getordertest.myshopify.com/api/orders.json' from origin 'https://bd73afd9.ngrok.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

 

And I add {mode: 'no-cors'}

and I just got an opaque response...

Thresher
Shopify Partner
20 2 24

@Ireneludi  ahh, did you install the koa router npm package?

 

npm install --save koa-router

If not, stop your server, run the above command, and start your server again.

Ireneludi
Tourist
9 0 1

@Thresher Yes, I installed at very first.

QAQ I have been stuck with this API problem for the whole week....

Ireneludi
Tourist
9 0 1

Finally figure it out. The issue was related with chrome security, after I disable chrome cors, everything works fine.

RHM
Shopify Partner
28 0 13

Hello @Thresher, isn't a risky approach to store the accessToken into a cookie ? 

Wouldn't be better to: 
1) verify request is coming from your domain and avoid extra jobs if not 

2) get the stored access token from your DB using the shop name 

3) make the Shopify request 

4) return data 

 

cheers

 

Robots Hate Monkeys
Thresher
Shopify Partner
20 2 24

Yes @RHM , that's what I'm doing now!

copleykj
Excursionist
21 0 3

I don't think disabling cross origin security in the browser is the solution.

zoultrex
Tourist
18 0 2

Sorry for digging an old thread but it has part of the solution I need, and it's in the exact same context, query the API from the backend.

const fetchUrl = 'https://getordertest.myshopify.com/api/orders.json';
		const method = "GET";
		fetch(fetchUrl, {method: method,})
		.then(response=>response.json()).then(json => console.log(json))

The code above, is calling the orders api, which is what I need. But I have no idea where to insert this snippet.
Should I wrap it in a server.use? or maybe in a router.get? How do I make it trigger?
Even if its just for a test as soon as the server launches would do for me to get moving. I'm stuck trying to understand how to call the APIs for a long time.

Ireneludi
Tourist
9 0 1

I used in router.get(`insert the code snippet here`)
Basically what I do is, from client, call my backend, and in backend router, call Shopify API

copleykj
Excursionist
21 0 3

Your best bet is to use the shopify-api-node package so that proper headers are sent for auth without having to manually configure them. All of this should happen on your server and not on the client so as not to expose your accessToken.

fruitstand_josh
Excursionist
21 1 4

@Thresher thanks for this post.

 

I implemented it almost exactly as you all have outlined but when I call `/api/products` or anything else I get a 404 error.  Any idea what's up?  Here is the relevant server code.  TBH I dont see where the server is using the routes based on this code.

 

app.prepare().then(() => {
  const server = new Koa();
  server.use(session(server));
  server.keys = [SHOPIFY_API_SECRET_KEY];

  server.use(
    createShopifyAuth({
      apiKey: SHOPIFY_API_KEY,
      secret: SHOPIFY_API_SECRET_KEY,
      scopes: ['read_products'],
      afterAuth(ctx) {
        const { shop, accessToken } = ctx.session;

        console.log('cookie', shop)
        ctx.cookies.set('shopOrigin', shop, { httpOnly: false });
        ctx.cookies.set('accessToken', accessToken, { httpOnly: false });

        ctx.redirect('/');
      },
    }),
  );

  server.use(verifyRequest());
  server.use(async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
    return
  });

  router.get('/api/:object', async (ctx) => {
    try {
      const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2019-10/" + ctx.params.object + ".json", {
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
        },
      })
        .then(response => response.json())
        .then(json => {
          console.log('json', json)
          return json;
        });
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});