Netlify-lambda functions work in development but return 500 errors in deploy

I’ve been successfully using standard Netlify functions for a while now and am attempting to move away from CommonJS to ES6 import/export syntax, so I’m trying to get netlify-lambda to build my functions from src/lambda into ./functions.

I’ve been following the modern netlify-lambda guides I could find and am pretty stuck at this point. My deploy url can be found here, and when inspecting xhr requests, you can see on each load that both functions are returning a 500 error of

"FetchError: invalid json response body at https://clean-gs.myshopify.com/api/2021-04/graphql.json reason: Unexpected end of JSON input"

In dev, using netlify dev all functions work perfectly.

However when running build and deploy, the same functions fail.

My netlify.toml file looks like this:

[dev]
  command = "yarn dev"
  publish = "public"
  functions = "functions"
  port = 8888

[build]
  command = "netlify-lambda build src/lambda && yarn build"
  publish = "public"
  functions = "functions"

I’m not using a .babelrc file since the docs say that if one doesn’t exist it will make some assumptions and a few basic settings. I’m not doing anything fancy like TypeScript, just some very simple ES6 syntax.

Using one of the functions as an example, I call the it within Gatsby like this:

const createNewCheckout = () => {
  return fetch(`/.netlify/functions/createCheckout`, {
    method: 'POST',
    body: JSON.stringify({}),
  })
    .then((res) => res.json())
    .then(function (response) {
      return response
    })
    .catch(function (error) {
      console.log(error)
    })
}

Here is the called function (note: data isn’t used for this particular API call, as it just takes an empty object) and the helpers it imports from.

import { validateIncomingData, statusReturn } from './utilities'

import { queryShopify, preparePayload } from '../queries/shopifyStorefrontAPI/requestConfig'

import { CREATE_CHECKOUT } from '../queries/shopifyStorefrontAPI/checkout'

let data
let checkout

export const handler = async (event) => {
  data = validateIncomingData(event)

  const payload = preparePayload(CREATE_CHECKOUT, {
    input: {},
  })

  try {
    checkout = await queryShopify(payload)
    const newCheckout = checkout.data.checkoutCreate.checkout

    return statusReturn(200, newCheckout)
  } catch (err) {
    console.log(err.message)
    return statusReturn(500, { error: err.message })
  }
}

Which is using some helpers from:

// requestConfig.js

import fetch from 'node-fetch'
require('dotenv').config()

const {
  GATSBY_SHOP_NAME,
  GATSBY_SHOPIFY_STOREFRONT_TOKEN,
  GATSBY_SHOPIFY_STOREFRONT_API_VERSION,
} = process.env

const SHOPIFY_GRAPHQL_URL = `https://${GATSBY_SHOP_NAME}.myshopify.com/api/${GATSBY_SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`

const shopifyConfig = {
  'Content-Type': 'application/json',
  'X-Shopify-Storefront-Access-Token': GATSBY_SHOPIFY_STOREFRONT_TOKEN,
}

export const preparePayload = (query, v) => {
  return {
    query,
    variables: v,
  }
}

export const queryShopify = (payload) => {
  return fetch(SHOPIFY_GRAPHQL_URL, {
    method: 'post',
    body: JSON.stringify(payload),
    headers: shopifyConfig,
  })
    .then((res) => res.json())
    .catch((error) => {
      throw new Error(error)
    })
}

and

// utilities.js

const headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Content-Type',
  'Content-Type': 'application/json',
}

export const validateIncomingData = (event) => {
  if (event.httpMethod !== 'POST' || !event.body) return statusReturn(400, '')

  try {
    return JSON.parse(event.body)
  } catch (error) {
    return statusReturn(400, { error: 'Bad request body' })
  }
}

export const statusReturn = (code, body) => {
  return {
    statusCode: code,
    headers,
    body: JSON.stringify(body),
  }
}

and the query itself:

export const CREATE_CHECKOUT = `mutation checkoutCreate($input: CheckoutCreateInput!) {
  checkoutCreate(input: $input) {
    checkout {
      id
    }
    checkoutUserErrors {
      code
      field
      message
    }
  }
}`

And lastly, here is the functions part of the build command output, which to me indicates that the functions where compiled successfully (at least, I think it does, as I’m not entirely sure how to read it). Worth noting that all functions listed work correctly with netlify dev.

$ netlify-lambda build src/lambda && yarn netlify-build
netlify-lambda: Building functions
Hash: 1c5326d16d65b7173d1a
Version: webpack 4.46.0
Time: 608ms
Built at: 06/21/2021 4:36:06 PM
            Asset      Size  Chunks             Chunk Names
     addresses.js  60.5 KiB    0, 7  [emitted]  addresses
createCheckout.js  60.4 KiB    1, 7  [emitted]  createCheckout
      customer.js  60.5 KiB    2, 7  [emitted]  customer
   getCheckout.js  60.4 KiB    3, 7  [emitted]  getCheckout
         login.js  60.9 KiB    4, 7  [emitted]  login
        logout.js  60.4 KiB    5, 7  [emitted]  logout
        orders.js  60.5 KiB    6, 7  [emitted]  orders
     utilities.js  1.52 KiB       7  [emitted]  utilities
Entrypoint addresses = addresses.js
Entrypoint createCheckout = createCheckout.js
Entrypoint customer = customer.js
Entrypoint getCheckout = getCheckout.js
Entrypoint login = login.js
Entrypoint logout = logout.js
Entrypoint orders = orders.js
Entrypoint utilities = utilities.js
[19] ./utilities.js 1010 bytes {0} {1} {2} {3} {4} {5} {6} {7} [built]
[20] ../queries/shopifyStorefrontAPI/requestConfig.js 1.26 KiB {0} {1} {2} {3} {4} {5} {6} [built]
[21] /Users/kevin/Sites/clean-gs/node_modules/@babel/runtime/helpers/interopRequireDefault.js 225 bytes {0} {1} {2} {3} {4} {5} {6} [built]
[22] /Users/kevin/Sites/clean-gs/node_modules/axios/index.js 40 bytes {0} {1} {2} {3} {4} {5} {6} [built]
[50] /Users/kevin/Sites/clean-gs/node_modules/node-fetch/lib/index.mjs 40.1 KiB {0} {1} {2} {3} {4} {5} {6} [built]
[51] /Users/kevin/Sites/clean-gs/node_modules/dotenv/lib/main.js 3.08 KiB {0} {1} {2} {3} {4} {5} {6} [built]
[54] ../queries/shopifyStorefrontAPI/customers.js 5.25 KiB {0} {2} {4} {5} {6} [built]
[56] ../queries/shopifyStorefrontAPI/checkout.js 3.3 KiB {1} {3} [built]
[59] ./addresses.js 1020 bytes {0} [built]
[60] ./createCheckout.js 1.02 KiB {1} [built]
[61] ./customer.js 891 bytes {2} [built]
[62] ./getCheckout.js 910 bytes {3} [built]
[63] ./login.js 1.74 KiB {4} [built]
[64] ./logout.js 750 bytes {5} [built]
[65] ./orders.js 944 bytes {6} [built]
    + 51 hidden modules

What might I be missing here?

Thanks in advance! :slight_smile:

Which version of Netlify CLI are you using? In the past, there was a problem when the functions worked locally but won’t work when deployed using the CLI. The solution was to deploy with Git. Could you try that and let us know if it helps?

Right now I’m using version 3.30.0, but will try updating and see what results I get after that.

According to this: https://answers.netlify.com/t/function-works-locally-but-500-errors-when-i-deploy-on-netlify/37664/16?u=hrishikesh, it should have been fixed in 3.29.15. However, if it’s still not working, are you getting a different result with using a Git provider?

I’ve just tried updating to the latest version of Netlify CLI but the same problem still persists. I’ve also tried to deploy via a Git branch deploy, but with the same results.

I’m wondering if maybe it’s something Babel-related or maybe the env variables aren’t making it into the final functions being shipped?

I’d like to take a look into that further, but using Gatsby, I can’t figure out how to get netlify-lambda to compile unminified since I’m not too experience with modifying Webpack through Gatsby. Do you have any experience with that?

Thanks

That’s possible. Could you check that once by adding console.log(env) statements?

If you wish, you could also skip netlify-lamda entirely. Netlify automatically bundles the functions as per the configuration: Functions overview | Netlify Docs

Update: Still haven’t solved this yet (not great at debugging), but here are some steps I’ve taken so far. I’ve also confirmed that env variables to make it in.

From the netlify-lambda docs I learned you can build your functions unminified with these two steps.

  1. First make the file…
// webpack.functions.js

module.exports = {
  optimization: { minimize: false }
};
  1. Then add and run the following script in your package.json
"build:lambda": "netlify-lambda build src/lambda --config ./webpack.functions.js"

This will give you a /functions folder with mostly-readable compiled functions.

From there, I removed all traces of netlify-lambda from my project so I could just test those compiled functions and play with them a little.

  1. They still work as expected using netlify dev
  2. They are still having the same 500 error issue with netlify build && netlify deploy

Which leads me to believe that they’ve compiled correctly.

It looks like the error is thrown in the function, queryShopify (which is used in all of my functions) according to the Netlify function logs.

The payload passed into the function is

{
  "query": "mutation checkoutCreate($input: CheckoutCreateInput!) {\n  checkoutCreate(input: $input) {\n    checkout {\n      id\n    }\n    checkoutUserErrors {\n      code\n      field\n      message\n    }\n  }\n}",
  "variables": {
    "input": {}
  }
}

Which matches Shopify’s API docs.

{
  "message": "invalid json response body at https://clean-gs.myshopify.com/api/2021-04/graphql.json reason: Unexpected end of JSON input",
  "type": "invalid-json"
}

So I’m either not understanding something correctly about how to fetch properly, or there’s something different about Netlify’s deploy environment that’s causing it. Not sure.

I could skip it altogether now as you suggested, but I’m so curious now and would really like to use ES6 syntax.

Thoughts?

@hrishikesh,

I think I figured it out after switching over to Axios and inspecting Netlify’s function logs. When process.env.VARIABLE_NAME is called, it doesn’t just pull out the variable’s value, but the entire line

It should be outputting:

"X-Shopify-Storefront-Access-Token": "YourApiTokenHere"

But instead is outputting the entire env variable name and its value. It seems to be only doing so in the headers, as I also use env variables to build the URL itself, which prints the env variable values as it should.

When I use my API token directly instead of process.env, it works in both a git deploy and netlify build, deploy.

Cheers

Gatsby prefixed environment variables work in client-side Gatsby build, not when using serverless functions. I missed that line. :grimacing:

@hrishikesh,

The odd part is that some of them are working correctly. I’m using them like this to build the URL

const {
  GATSBY_SHOP_NAME,
  GATSBY_SHOPIFY_STOREFRONT_TOKEN,
  GATSBY_SHOPIFY_STOREFRONT_API_VERSION,
} = process.env

const SHOPIFY_GRAPHQL_URL = `https://${GATSBY_SHOP_NAME}.myshopify.com/api/${GATSBY_SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`

The URL is being being built correctly because it’s in the error and it’s only being built in this one place. It’s only the GATSBY_SHOPIFY_STOREFRONT_TOKEN variable that’t not working and they’re all prefixed with GATSBY_

I’ll try removing the Gatsby prefix for these to see if they work :slight_smile:

According to Gatsby:

It still doesn’t explain why some would work while others won’t but I thought it’s worth a share.

Thanks for the help! I think we got to the bottom of it. I’ll just make two version of each variable which sounds like it should work :crossed_fingers:

Awesome. If you have any more questions, do let us know.