apollo-server-integrations / apollo-server-integration-next

An Apollo Server integration for use with Next.js
MIT License
196 stars 21 forks source link

Handler in App router TypeError 'setup' #113

Open luchillo17 opened 1 year ago

luchillo17 commented 1 year ago

Handler in App router TypeError 'setup'

While using Next 13.4.1, tried to use the app router to expose graphql server under app/graphql/route.ts, it gives errors, it does work if I move it to the deprecated pages router under pages/api/graphql.ts, did try the experimental flag on the next.config.js file to no avail.

Code

import { startServerAndCreateNextHandler } from '@as-integrations/next';
import { getApolloServer } from '@graph-meister/neo4j-apollo';
import { NextRequest } from 'next/server';

const typeDefs = `#graphql
   type Movie {
    title: String
    tagline: String
    released: Int
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN)
    directors: [Person!]! @relationship(type: "DIRECTED", direction: IN)
  }

  type Person {
    name: String
    born: Int
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
  }
`;

const server = await getApolloServer(typeDefs);

const handler = startServerAndCreateNextHandler(server);

export async function GET(request: NextRequest) {
  return handler(request);
}

export async function POST(request: NextRequest) {
  return handler(request);
}

Environment

{
  "name": "",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {},
  "private": true,
  "dependencies": {
    "@apollo/client": "^3.7.15",
    "@apollo/server": "^4.7.1",
    "@as-integrations/next": "^2.0.0",
    "@neo4j/graphql": "^3.20.1",
    "@nx/next": "16.3.0",
    "graphql": "^16.6.0",
    "neo-forgery": "^2.0.1",
    "neo4j-driver": "^5.9.0",
    "next": "13.4.1",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tslib": "^2.3.0"
  },
  "devDependencies": {
    "@nx/cypress": "16.3.0",
    "@nx/eslint-plugin": "16.3.0",
    "@nx/jest": "16.3.0",
    "@nx/js": "16.3.0",
    "@nx/linter": "16.3.0",
    "@nx/react": "16.3.0",
    "@nx/workspace": "16.3.0",
    "@testing-library/react": "14.0.0",
    "@types/jest": "^29.4.0",
    "@types/node": "18.14.2",
    "@types/react": "18.0.28",
    "@types/react-dom": "18.0.11",
    "@typescript-eslint/eslint-plugin": "^5.58.0",
    "@typescript-eslint/parser": "^5.58.0",
    "babel-jest": "^29.4.1",
    "cypress": "^12.11.0",
    "encoding": "^0.1.13",
    "eslint": "~8.15.0",
    "eslint-config-next": "13.4.1",
    "eslint-config-prettier": "8.1.0",
    "eslint-plugin-cypress": "^2.10.3",
    "eslint-plugin-import": "2.27.5",
    "eslint-plugin-jsx-a11y": "6.7.1",
    "eslint-plugin-react": "7.32.2",
    "eslint-plugin-react-hooks": "4.6.0",
    "jest": "^29.4.1",
    "jest-environment-jsdom": "^29.4.1",
    "jest-environment-node": "^29.4.1",
    "nx": "16.3.0",
    "nx-cloud": "latest",
    "prettier": "^2.6.2",
    "ts-jest": "^29.1.0",
    "ts-node": "10.9.1",
    "typescript": "~5.0.2"
  }
}

Terminal output


- event compiled successfully in 4.9s (1478 modules)
- error TypeError: Cannot read properties of undefined (reading 'setup')
    at RouteHandlerManager.handle (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/future/route-handler-managers/route-handler-manager.js:24:16)
    at doRender (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:935:58)
    at cacheEntry.responseCache.get.incrementalCache.incrementalCache (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:1161:34)
    at /home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/response-cache/index.js:99:42
    at ResponseCache.get (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/response-cache/index.js:149:11)
    at DevServer.renderToResponseWithComponentsImpl (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:1080:53)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async DevServer.renderPageComponent (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:1312:24)
    at async DevServer.renderToResponseImpl (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:1343:32)
    at async DevServer.pipeImpl (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:627:25)
    at async Object.fn (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/next-server.js:1136:21)
    at async Router.execute (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/router.js:315:32)
    at async DevServer.runImpl (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:601:29)
    at async DevServer.run (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/next-dev-server.js:922:20)
    at async DevServer.handleRequestImpl (/home/carlos/Projects/graph-meister/node_modules/.pnpm/next@13.4.1_@babel+core@7.22.1_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:533:20)```
martinnabhan commented 1 year ago

Are you sure the server variable's promise is being fulfilled before passing server to startServerAndCreateNextHandler? It looks like you're doing top-level await, which as far as I know isn't supported in Next.js.

luchillo17 commented 1 year ago

It is in experimental flags in the config for webpack, should work based on this comment 2 years back, basically it's webpack that gives the support: https://github.com/vercel/next.js/discussions/11185#discussioncomment-128562

luchillo17 commented 1 year ago

If it were to be the await, why does it work when I move this code to the pages/api/graphql.ts? (of course I have to remove the GET & SET functions).

luchillo17 commented 1 year ago

The biggest difference here would be the export default in the Pages router vs the export of the 2 handlers in the App router, I do believe I followed the steps, could topLevelAwait be having unintended consequences here? image

skn437 commented 1 year ago

luchillo17, I have the same problem as you. Page Router works fine when using top level "await". But when I use app router, I get almost the same terminal output as you.

luchillo17 commented 1 year ago

@skn437 Have you tried without the top level await part? I can't remove it because Neo4j generates the schema with a promise so...

skn437 commented 1 year ago

@skn437 Have you tried without the top level await part? I can't remove it because Neo4j generates the schema with a promise so...

Me as well. I also use Neo4j. It needs "await" to generate Schema. So I can't remove it.

martinnabhan commented 1 year ago

According to this it seems like top-level await doesn't work in the app directory.

I haven't tried this so it might not work, but you might be able to fulfill the promise when the first request comes in like this:

import { startServerAndCreateNextHandler } from '@as-integrations/next';
import { getApolloServer } from '@graph-meister/neo4j-apollo';
import { NextRequest } from 'next/server';

const typeDefs = `#graphql
   type Movie {
    title: String
    tagline: String
    released: Int
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN)
    directors: [Person!]! @relationship(type: "DIRECTED", direction: IN)
  }

  type Person {
    name: String
    born: Int
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
  }
`;

let handler = null;
let server = null;

export async function GET(request: NextRequest) {
  if (!server) {
    server = await getApolloServer(typeDefs);
    handler = startServerAndCreateNextHandler(server);
  }

  return handler(request);
}

export async function POST(request: NextRequest) {
  // might need the same code here

  return handler(request);
}
luchillo17 commented 1 year ago

Oh, bit ugly but as long as it does work, so we store the instance on the first available request and reuse it as long as it exists in the global variable.

I wonder if the schemaLink will help as mentioned here: https://github.com/apollographql/apollo-client-nextjs/issues/31#issuecomment-1584349812

luchillo17 commented 1 year ago

It worked perfectly, though I wonder if this could be considered a production-ready setup...

let server: ApolloServer<BaseContext>;
let handler: ReturnType<typeof startServerAndCreateNextHandler>;

const getServerHandler = async () => {
  server ??= await getApolloServer(typeDefs);
  handler ??= startServerAndCreateNextHandler(server);
};

export async function GET(request: NextRequest) {
  await getServerHandler();
  return handler(request);
}

export async function POST(request: NextRequest) {
  await getServerHandler();
  return handler(request);
}
skn437 commented 1 year ago

Thanks a lot...It works...

luchillo17 commented 1 year ago

Maybe mentioning this in the README for working around top-level await?