首页 > 解决方案 > Next.js + Apollo + Mongodb:在 SSR 模式下无法访问 Apollo 上下文

问题描述

我正在尝试将 MongoDB 与 NextJS API 路由 Apollo 示例连接:

https://github.com/vercel/next.js/blob/canary/examples/api-routes-apollo-server-and-client

我找不到任何官方文档。所以我关注这个话题: Connect Apollo with mongodb 这个想法是从 ApolloServer 的上下文中连接 Mongodb

页面/api/graphql.js

import { ApolloServer } from 'apollo-server-micro';
import { schema } from '../../apollo/schema';
import { connectToDatabase } from '../../utils/mongodb';

const apolloServer = new ApolloServer({
  schema,
  context: async () => {
    const { db } = await connectToDatabase();
    return { db };
  },
});

export const config = {
  api: {
    bodyParser: false,
  },
};

export default apolloServer.createHandler({ path: '/api/graphql' });

“connectToDatabase”来自示例“with-mongo” https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/util/mongodb.js

NextJS 中有 3 种渲染页面的方式。当我像这样连接我的数据库时(在 ApolloServer 上下文中),它在客户端渲染中工作得很好,但在静态和服务器端渲染中完全不行。我的解析器函数中的上下文是未定义的。

阿波罗/resolvers.js

export const resolvers = {
  Query: {

    async getAllCards(_parent, _args, _context, _info) {
      console.log('_context resolver :>> ', _context);

      const res = await _context.db.db
        .collection('cards')
        .find({})
        .limit(20)
        .toArray();

      console.log('res :>> ', res);

      return res;
    },
  },
};

那时我尝试关注这个主题 Next.js graphql context is empty {} on SSR getServerSideProps

按照这些步骤,我将修改 2 个文件:

页面/api/graphql

import { ApolloServer } from 'apollo-server-micro';
import { schema } from '../../apollo/schema';
import { connectToDatabase } from '../../utils/mongodb';

async function contextResolver(ctx) {
  ctx.db = await connectToDatabase();

  return ctx;
}

const apolloServer = new ApolloServer({
  schema,
  context: contextResolver,
});

export const config = {
  api: {
    bodyParser: false,
  },
};

export default apolloServer.createHandler({ path: '/api/graphql' });

页面/explore_SSR.js

import gql from 'graphql-tag';
import Link from 'next/link';
import { initializeApollo } from '../apollo/client';
import { connectToDatabase } from '../utils/mongodb';


const Explore = () => {
 
return (
 // UI Stuff
 )
}

export async function getServerSideProps(ctx) {
  console.log('ctx :>> ', ctx);
  async function contextResolver(ctx) {
    ctx.db = await connectToDatabase();
    return ctx;
  }
  await contextResolver(ctx);
  console.log('ctx after :>> ', ctx);

  const apolloClient = initializeApollo(null, ctx);

  await apolloClient.query({
    query: gql`
      query GetAllMementoQuery {
        getAllMemento {
          title
        }
      }
    `,
  });

  return {
    props: {
      props: { initialApolloState: apolloClient.cache.extract() },
    },
  };
}

export default Explore;

console.log 的结果:console.log('ctx after :>> ', ctx);

ctx after :>>  {
  req: IncomingMessage {
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: [],
      flowing: null,
      ended: true,
      endEmitted: false,
      reading: false,
      sync: true,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: false,
      destroyed: false,
      errored: null,
      closed: false,
      closeEmitted: false,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: true,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: null
    },
    _events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
    _eventsCount: 1,
    _maxListeners: undefined,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      _paused: false,
      _httpMessage: [ServerResponse],
      timeout: 0,
      [Symbol(async_id_symbol)]: 285916,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 24100466,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: false,
        [Symbol(kHasPrimitive)]: false,
        [Symbol(asyncId)]: 290206,
        [Symbol(triggerId)]: 290203
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    httpVersionMajor: 1,
    httpVersionMinor: 1,
    httpVersion: '1.1',
    complete: true,
    headers: {
      host: 'localhost:3000',
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
      'accept-language': 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
      'accept-encoding': 'gzip, deflate',
      referer: 'http://localhost:3000/explore_clientSide',
      dnt: '1',
      connection: 'keep-alive',
      'upgrade-insecure-requests': '1',
      'sec-gpc': '1'
    },
    rawHeaders: [
      'Host',
      'localhost:3000',
      'User-Agent',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0',
      'Accept',
      'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
      'Accept-Language',
      'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
      'Accept-Encoding',
      'gzip, deflate',
      'Referer',
      'http://localhost:3000/explore_clientSide',
      'DNT',
      '1',
      'Connection',
      'keep-alive',
      'Upgrade-Insecure-Requests',
      '1',
      'Sec-GPC',
      '1'
    ],
    trailers: {},
    rawTrailers: [],
    aborted: false,
    upgrade: false,
    url: '/explore_SSR',
    method: 'GET',
    statusCode: null,
    statusMessage: null,
    client: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      _paused: false,
      _httpMessage: [ServerResponse],
      timeout: 0,
      [Symbol(async_id_symbol)]: 285916,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 24100466,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: false,
        [Symbol(kHasPrimitive)]: false,
        [Symbol(asyncId)]: 290206,
        [Symbol(triggerId)]: 290203
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _consuming: false,
    _dumped: false,
    cookies: [Getter/Setter],
    __NEXT_INIT_QUERY: {},
    [Symbol(kCapture)]: false,
    [Symbol(RequestTimeout)]: undefined
  },
  res: <ref *1> ServerResponse {
    _events: [Object: null prototype] { finish: [Function: bound resOnFinish] },
    _eventsCount: 1,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: true,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: true,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: false,
    _headerSent: false,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      _paused: false,
      _httpMessage: [Circular *1],
      timeout: 0,
      [Symbol(async_id_symbol)]: 285916,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 24100466,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: false,
        [Symbol(kHasPrimitive)]: false,
        [Symbol(asyncId)]: 290206,
        [Symbol(triggerId)]: 290203
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _header: null,
    _keepAliveTimeout: 5000,
    _onPendingData: [Function: bound updateOutgoingData],
    _sent100: false,
    _expect_continue: false,
    statusCode: 200,
    flush: [Function: flush],
    write: [Function: write],
    end: [Function: end],
    on: [Function: on],
    writeHead: [Function: writeHead],
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: null
  },
  query: {},
  resolvedUrl: '/explore_SSR',
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined,
  db: {
    client: MongoClient {
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      s: [Object],
      topology: [NativeTopology],
      [Symbol(kCapture)]: false
    },
    db: Db {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      s: [Object],
      serverConfig: [Getter],
      bufferMaxEntries: [Getter],
      databaseName: [Getter],
      [Symbol(kCapture)]: false
    }
  }
}

但这对我不起作用。我的解析器的 _context 仍未定义。(apollo/resolvers.js 中的console.log:_context :>> 未定义)

也许我们不能将 Apollo 和数据库连接与 API 路由和服务器端渲染模式一起使用,或者我只是错过了一些大事。

知道如何实现 Mongodb 数据库连接以在任何渲染模式下从我的解析器访问它吗?

标签: mongodbgraphqlnext.jsapollo-clientapollo-server

解决方案


推荐阅读