GraphQLGuide / apollo-datasource-mongodb

Apollo data source for MongoDB
MIT License
285 stars 64 forks source link

this.findOneById is not a function #41

Closed ptvandi closed 3 years ago

ptvandi commented 3 years ago

I've been trying to get this working all day. My code is pretty straight-forward but I'm greeted with the error this.findOneById is not a function.

My setup is almost identical to the basic example, except my dataSources function is async because the MongoClient connect method is a promise.

// data-sources.js
import { MongoDataSource } from 'apollo-datasource-mongodb';

export default class Users extends MongoDataSource {
  async getUser(userId) {
    return await this.findOneById(userId);
  }
}
// type-defs.js
import { MongoClient } from 'mongodb';

import Users from './users';

export async function dataSources() {
  const client = await MongoClient.connect(process.env.MONGODB_URI);

  return {
    users: new Users(client.db.collection('users'))
  };
}
// resolvers.js
export const resolvers = {
  Query: {
    async me(parent, args, context, info) {
      return (await context.dataSources).users.getUser(args.userId);
    }
  },
};
// /api/graphql.js
import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from './type-defs';
import { resolvers } from './resolvers';
import { dataSources } from './data-sources';

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources
});

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

Any thoughts on what I might be doing incorrectly?

9at8 commented 3 years ago

I believe dataSources has to be a sync function. At least looking at the type annotations in the apollo server source indicate that you can't use an async function.

9at8 commented 3 years ago

This might work:

// type-defs.js
import { MongoClient } from 'mongodb';

import Users from './users';

export function dataSources(client) {
  return {
    users: new Users(client.db.collection('users'))
  };
}
// /api/graphql.js
import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from './type-defs';
import { resolvers } from './resolvers';
import { dataSources } from './data-sources';

export async function createApolloServer() {
  const client = await MongoClient.connect(process.env.MONGODB_URI);

  const apolloServer = new ApolloServer({
    typeDefs,
    resolvers,
    dataSources: () => dataSources(client),
  });
}

I'm not sure what the apolloServer.createHandler function does, but you might need to move it to a different place.

ptvandi commented 3 years ago

Thank you all for the suggestions. I finally got it working.

One mistake I was making was including the dataSources property in the makeExecutableSchema function (not included in the code above). I also realized the pages export could be async in Next.js, so I moved the asynchronous logic into that block and cached the apollo server handler, and all is well.

// apollo/type-defs.js
import { MongoClient } from 'mongodb';
import Users from 'apollo/data-sources/users';

export async function makeDataSourcesFn() {
  const client = await MongoClient.connect(process.env.MONGODB_URI);

  return function dataSources() {
    return {
      users: new Users(client.db().collection('users'))
    };
  };
}
// /pages/api/graphql.js
import { ApolloServer } from 'apollo-server-micro';
import { makeDataSourcesFn } from 'apollo/data-sources';
import { schema } from 'apollo/schema';

let apolloServerHandler;

async function getApolloServerHandler() {
  if (!apolloServerHandler) {
    const dataSources = await makeDataSourcesFn();
    const apolloServer = new ApolloServer({ schema, dataSources });
    apolloServerHandler = apolloServer.createHandler({ path: '/api/graphql' });
  }

  return apolloServerHandler;
}

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

export default async (req, res) => (await getApolloServerHandler())(req, res);
ash0080 commented 3 years ago

same issue when imported for test propose

my solution is, call objInstance.initialize({context: {}}) manually

But I would still like to see a more elegant solution