Search code examples
node.jsaws-lambdashopifyshopify-apishopify-api-node

Lambda Node JS Shopify API Call


I have been trying to resolve this for the past couple of days and no luck, i am fairly new to Node Js and Lambda. I am trying to create an API with lambda and when i call this new API, i want to create a new redirect object in Shopify. I have the following code running in my function

const http = require('https')

exports.handler = async (event) => {
    return httprequest().then((data) => {
        const response = {
            statusCode: 200,
            body: JSON.stringify(data),
        };
    return response;
    });
};
function httprequest() {
     var username = "shopify-api-private-username";
     var password = "shopify-api-private-password";
     var auth = 'Basic ' + Buffer.from(username + ':' + password).toString('base64');
    
     return new Promise((resolve, reject) => {
        const options = {
            host: 'store-name.myshopify.com',
            path: '/admin/api/2020-04/redirects.json',
            port: 443,
            method: 'POST',
            headers: {
              'Authorization': auth,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({"redirect":{"path":"http://www.apple.com/forum","target":"http://forums.apple.com"}})
        };
        
        const req = http.request(options, (res) => {
            console.log(res);
            if (res.statusCode < 200 || res.statusCode >= 300) {
                return reject(new Error('statusCode=' + res.statusCode));
            }
            var body = [];
            res.on('data', function(chunk) {
                body.push(chunk);
            });
            res.on('end', function() {
                try {
                    body = JSON.parse(Buffer.concat(body).toString());
                } catch(e) {
                    reject(e);
                }
                resolve(body);
            });
        });
        req.on('error', (e) => {
          reject(e.message);
        });
        // send the request
       req.end();
    });
}

As much i keep changing the code I keep getting the following error. After adding a console log on the response i see the following, not sure if this is a Lambda/Node Response or Shopify:

enter image description here

Function logs:
  servername: 'store-name.myshopify.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'store-name.myshopify.com',
      _readableState: [ReadableState],
      readable: true,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: true,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: [TLSWrap],
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: [HTTPParser],
      _httpMessage: [Circular],
      [Symbol(res)]: [TLSWrap],
      [Symbol(asyncId)]: 4,
      [Symbol(kHandle)]: [TLSWrap],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(connect-options)]: [Object]
    },
    _header: 'POST /admin/api/2020-04/redirects.json HTTP/1.1\r\n' +
      'Authorization: Basic base64code_replace_for_security_reasons=\r\n' +
      'Content-Type: application/json\r\n' +
      'Host: store-name.myshopify.com\r\n' +
      'Connection: close\r\n' +
      'Content-Length: 0\r\n' +
      '\r\n',
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'POST',
    insecureHTTPParser: undefined,
    path: '/admin/api/2020-04/redirects.json',
    _ended: false,
    res: [Circular],
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: HTTPParser {
      '0': [Function: parserOnHeaders],
      '1': [Function: parserOnHeadersComplete],
      '2': [Function: parserOnBody],
      '3': [Function: parserOnMessageComplete],
      '4': null,
      _headers: [],
      _url: '',
      socket: [TLSSocket],
      incoming: [Circular],
      outgoing: [Circular],
      maxHeaderPairs: 2000,
      _consumed: false,
      onIncoming: [Function: parserOnIncomingClient]
    },
    maxHeadersCount: null,
    reusedSocket: false,
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      authorization: [Array],
      'content-type': [Array],
      host: [Array]
    }
  },
  [Symbol(kCapture)]: false

Any help would be appreciated. Thank you in advance.

By the way i am doing this directly on a new Lambda function. The only file i have is index.js.

Node Version Node.js 12.x


Solution

  • A short explanation of why your code is not working is because of Bad Request as rightly pointed out in comments. It is a bad request because you are not actually sending the payload with your request. The code that you have got from Stack Overflow, may have been for GET request and simply adding body property to options object will not work. Because as per Node.js docs for HTTPS, options object does not has any property named body.

    As per Node.js docs for HTTP request, you need to send POST data using write function.

    // Write data to request body
    req.write(postData);
    

    To fix your code, add Content length property and write post data. Check code comments.

        const http = require('https')
        
        exports.handler = async (event) => {
            return httprequest().then((data) => {
                const response = {
                    statusCode: 200,
                    body: JSON.stringify(data),
                };
            return response;
            });
        };
        function httprequest() {
             var username = "shopify-api-private-username";
             var password = "shopify-api-private-password";
             var auth = 'Basic ' + Buffer.from(username + ':' + password).toString('base64');
            
             return new Promise((resolve, reject) => {
                const data = JSON.stringify({"redirect":{"path":"http://www.apple.com/forum","target":"http://forums.apple.com"}});
                const options = {
                    host: 'store-name.myshopify.com',
                    path: '/admin/api/2020-04/redirects.json',
                    port: 443,
                    method: 'POST',
                    headers: {
                      'Authorization': auth,
                      'Content-Type': 'application/json',
                      'Content-Length': data.length // Added content length
                    }
                };
                
                const req = http.request(options, (res) => {
                console.log(res);
                if (res.statusCode < 200 || res.statusCode >= 300) {
                    return reject(new Error('statusCode=' + res.statusCode));
                }
                var body = [];
                res.on('data', function(chunk) {
                    body.push(chunk);
                });
                res.on('end', function() {
                    try {
                        body = JSON.parse(Buffer.concat(body).toString());
                    } catch(e) {
                        reject(e);
                    }
                    resolve(body);
                });
            });
            req.on('error', (e) => {
              reject(e.message);
            });
            // send the request
            // write POST data
            req.write(data)
           req.end();
        });
    }
    

    I have verified this code and it works fine both on local and in AWS Lambda functions. If you receive 422 now, it means that redirect has already been added.

    Besides this consider using the Shopify API Node.js that provides better error handling, rate limiting and other required features. You can also have a look at Shopify's Official Node library.