apollo-datasource-mongodb icon indicating copy to clipboard operation
apollo-datasource-mongodb copied to clipboard

this.findOneById is not a function (Apollo Server Express v4 GraphQL)

Open mishanianod opened this issue 2 years ago • 9 comments

I have the following ApolloServer (v4)

import { MongoDataSource } from 'apollo-datasource-mongodb'

export default class LoaderAsset extends MongoDataSource {
 async getAsset(assetId) {
    return this.findOneById(assetId) // ERROR IS HERE
  }
}

async function startApolloServer(app) {
  const httpServer = http.createServer(app);
  
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
    
  });

  await server.start();
  app.use(
    '/graphql',
    cors(),
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
       return {
        dataSources: {
          loaderAsset: new LoaderAsset(modelAsset),
        }
      }
      },
    }),
  );


  const port = config.get('Port') || 8081;
  await new Promise(resolve => httpServer.listen({ port }, resolve));
}

when I run graphql and send one assetId, everything is working till I get following error:

this.findOneById is not a function

By the way (this.) has collection and model objects but not any methods.

is it because apollo-datasource-mongodb is not compatible with the new version of apollo server v4?

mishanianod avatar Oct 15 '22 01:10 mishanianod

dataSources in v3 were as follows:

 dataSources: () => ({
    users: new Users(client.db().collection('users'))
    // OR
    // users: new Users(UserModel)
  })

but in the new version dataSources is inside the context Maybe the issue is because of this change.

mishanianod avatar Oct 15 '22 01:10 mishanianod

what i did is i override my datasource class so I can call the initialize method inside in the MongoDatasource class. Now it works for me.

import { MongoDataSource } from 'apollo-datasource-mongodb';

class ReceptionDataSource extends MongoDataSource {
  constructor({ collection, cache }) {
    super(collection);
    super.initialize({ context: this.context, cache });
  }

  async getReception(receptionId) {
    return await this.findOneById(receptionId);
  }
}

export default ReceptionDataSource;

then in my context

async function startApolloServer(app) {
  const httpServer = http.createServer(app);
  
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
    
  });

  await server.start();
  app.use(
    '/graphql',
    cors(),
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
      const { cache } = server
       return {
        dataSources: {
          loaderAsset: new ReceptionDataSource({collection: ReceptionModel, cache}),
        }
      }
      },
    }),
  );


  const port = config.get('Port') || 8081;
  await new Promise(resolve => httpServer.listen({ port }, resolve));
}

systemkrash avatar Oct 20 '22 10:10 systemkrash

what i did is i override my datasource class so I can call the initialize method inside in the MongoDatasource class. Now it works for me.

import { MongoDataSource } from 'apollo-datasource-mongodb';

class ReceptionDataSource extends MongoDataSource {
  constructor({ collection, cache }) {
    super(collection);
    super.initialize({ context: this.context, cache });
  }

  async getReception(receptionId) {
    return await this.findOneById(receptionId);
  }
}

export default ReceptionDataSource;

then in my context

async function startApolloServer(app) {
  const httpServer = http.createServer(app);
  
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
    
  });

  await server.start();
  app.use(
    '/graphql',
    cors(),
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
      const { cache } = server
       return {
        dataSources: {
          loaderAsset: new ReceptionDataSource({collection: ReceptionModel, cache}),
        }
      }
      },
    }),
  );


  const port = config.get('Port') || 8081;
  await new Promise(resolve => httpServer.listen({ port }, resolve));
}

Thanks, @systemkrash , it is working now. hopefully, they update apollo-datasource-mongodb soon :)

mishanianod avatar Oct 20 '22 20:10 mishanianod

i can modify the code but i don't have time to do so because of my tight deadlines. but I think this is the fix. the cache attribute of the class needs to be expose in the constructor to work on apollo-server v4 :D

glad it works for you. :)

systemkrash avatar Oct 21 '22 01:10 systemkrash

@systemkrash Thank you for your code snippet, it's of great help! I'm still struggling with a point: How do you correctly set the context in the DataSource (with additional fields)?

super.initialize({ context: this.context, cache });

this.context is always undefined, isn't it?


Also I'll have a problem, even with a defined context. I was exploiting the fact that the dataSources context was containing the other dataSources and himself:

// inside any of my DataSource extended class

this.context.dataSources.users.findOneById(this.context.token)

EDIT: I got help from a member of ApolloGraphQL and found a way to set correctly the context and dataSources: https://github.com/apollographql/apollo-server/discussions/7096

RemyMachado avatar Oct 28 '22 22:10 RemyMachado

is it because apollo-datasource-mongodb is not compatible with the new version of apollo server v4?

Sounds like it's not! In which case I think we should:

  1. release a 0.5.5 with peer dep of apollo-server < 4
  2. release a 0.6.0 with a dep of @apollo/server instead of apollo-server-errors, and a fix to this issue.

I'd welcome a PR for # 2.

https://www.apollographql.com/docs/apollo-server/migration/#datasources

lorensr avatar Oct 28 '22 23:10 lorensr

@systemkrash Thank you for your code snippet, it's of great help! I'm still struggling with a point: How do you correctly set the context in the DataSource (with additional fields)?

super.initialize({ context: this.context, cache });

this.context is always undefined, isn't it?

Also I'll have a problem, even with a defined context. I was exploiting the fact that the dataSources context was containing the other dataSources and himself:

// inside any of my DataSource extended class

this.context.dataSources.users.findOneById(this.context.token)

EDIT: I got help from a member of ApolloGraphQL and found a way to set correctly the context and dataSources: apollographql/apollo-server#7096

I believed its not undefined because that attribute belongs to MongoDataSource class which we use to extend our own Datasource class. sorry for the late response.

systemkrash avatar Nov 09 '22 01:11 systemkrash

Hi @lorensr , I have been working on upgrading Apollo Server 3 to 4 in my own project where I using this package. I created a fork to update MongoDataSource to work with the changes made to data sources in Apollo Server 4. I believe I have come up with a general implementation that would solve the problem described in this issue as well as resolving issues #115 and #108.

The major change was removing DataSource class and apollo-datasource package, which have been deprecated as Apollo Server v4. In v4, data source classes can either be custom made or extend a community maintained package like this one. That package constructor should have a typed object which contains only what that data source implementation requires.

In this case, I defined a MongoDataSourceConfig interface for the constructor which requires a modelOrCollection and optionally a cache. If you need access to any data in your subclass, you can add them to the argument of the constructor in the subclass and call super passing in the options argument. This way, the user has total control over what data or context is accessible in each data source.

Also, since everything is initialized in the data source constructors and the abstract DataSource class was removed, the initialize method on MongoDataSource is no longer needed. I also removed the Context type argument on MongoDataSource because context, if it's needed, is stored on the subclasses, not MongoDataSource itself.

I also removed some deprecated packages like apollo-server-errors, as in v4, ApolloError was replaced by GraphQLError from the graphql package.

I created a pull request for my fork that you can review. If you have any further questions about the changes I made, feel free to message me. I also updated the README to reflect the changes and some links to relevant documentation on Apollo Server.

nnoce14 avatar May 09 '23 18:05 nnoce14

This issue report is going on a year now, any hope on a resolution?

NickDuBois avatar Sep 28 '23 15:09 NickDuBois