3
votes

I've just rewritten a Node.js GraphQL API from an old version of graphql-yoga + prisma to using express, serverless-http, prisma, and apollo-server-express. I've got it working locally just fine as an Express app, but when I try to get it published on Netlify as Function, I get a "Cannot find module" error:

{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module './schema.graphql'\nRequire stack:\n- /var/task/bundle/index.js\n- /var/task/graphql.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js",
"trace": [
"Runtime.ImportModuleError: Error: Cannot find module './schema.graphql'",
"Require stack:",
"- /var/task/bundle/index.js",
"- /var/task/graphql.js",
"- /var/runtime/UserFunction.js",
"- /var/runtime/index.js",
"    at _loadUserApp (/var/runtime/UserFunction.js:100:13)",
"    at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
"    at Object.<anonymous> (/var/runtime/index.js:43:30)",
"    at Module._compile (internal/modules/cjs/loader.js:1156:30)",
"    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1176:10)",
"    at Module.load (internal/modules/cjs/loader.js:1000:32)",
"    at Function.Module._load (internal/modules/cjs/loader.js:899:14)",
"    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)",
"    at internal/main/run_main_module.js:18:47"
]
}

I've tried multiple ways of importing these graphql files (graphql-imports, require.resolve, fs.readDirSync, etc.), but either it doesn't work at all or it only works from the project root. require.resolve works from the project root locally, so I'm left thinking that this file just wasn't included in the build. How can I tell what gets zipped?

My netlify.toml is:

[build]
  command = "npm run bundle"
  functions = "functions"

npm run bundle is basically cpx 'src/**/*' 'functions/bundle'and the files are visible locally in functions/bundle/schema.graphql and functions/bundle/generated/prisma.graphql.

My functions/graphql.js just takes the Express server and creates a lambda wrapper:

const serverlessHttp = require("serverless-http");

const { app } = require("./bundle/index.js");

exports.handler = serverlessHttp(app, {
  request(req, event, context) {
    req.event = event;
    req.context = context;
  }
});

"graphql-import" is super annoying because it only works from the root for the project, so copying the src/ files into functions/bundle/ changes the directory structure and those imports no longer work Inside the functions/bundle/index.js, I try to require.resolve some .grapqhl type definition files:

const { importSchema } = require("graphql-import");

const typeDefs = importSchema(require.resolve("./schema.graphql"));
const prismaTypeDefs = importSchema(
  require.resolve("./generated/prisma.graphql")
);

const db = new Prisma({
  typeDefs: prismaTypeDefs,
  endpoint: process.env.PRISMA_ENDPOINT,
  secret: process.env.PRISMA_SECRET,
  debug: process.env.NODE_ENV === "development"
});

const schema = makeExecutableSchema({
  typeDefs: gql`
    ${typeDefs}
  `,
  resolvers: {
    Mutation,
    Query,
  },
  resolverValidationOptions: { requireResolversForResolveType: false }
});

Again, this works locally so I assume the file is just outright missing from the zipped files.

I'm at a loss. Any ideas?

1

1 Answers

1
votes

Have you tried testing locally but with the netlify environment via netlify functions:invoke? It can help to debug such issues. (See https://github.com/netlify/cli/blob/master/docs/netlify-dev.md#locally-testing-functions-with-netlify-functionsinvoke)

Regarding your issue it seems certain files are ignored as you already suggested:

Netlify will access the functions directory during every deploy, zipping and deploying each supported code file as a serverless Lambda function on AWS. (Note that, with one exception, any files in subdirectories will be ignored.)

To use multiple files you need to define a folder with the function name and the js in it within your functions folder, otherwise it will only consider the .js file:

You can use either a standalone .js file that exports a handler (e.g. functions/{function_name}.js) or a folder with the same name as a .js file in it that exports a handler (e.g. functions/{function_name}/{function_name}.js).

Make sure to use relative imports or consider the full path.

Also you could try deploying unbundled JavaScript starting with CLI version 2.7.0. See: https://docs.netlify.com/cli/get-started/#unbundled-javascript-function-deploys