6

I'm a front-end dev trying to expand my horizons on a new Next project, learning Node, Mongo, and the server side of GraphQL for the first time. Apollo strikes me as the easiest way to jump in, as I've already used client-side Apollo on previous projects.

I've been following the official docs, where I learned of apollo-datasource-mongodb (seemingly the best way to plug my Apollo Server straight into a local Mongo database. Unfortunately there don't seem to be any example repos of this package in action for me to cheat off of so I've been left to muddle through.

I have mongo running locally via mongod and I can perform successful find() queries through the mongo shell, so I know the database itself is in good shape and contains nearly 600,000 records (I'm working with a rather large dataset).

I also have access to the Apollo Playground at localhost:4000 so I know the server is starting properly and connecting to the database (with appropriate Schema tips/errors I've since managed to resolve).

Here's the query I'm using in the Playground:

{
  item(id: 22298006) {
    title
  }
}

and here's what I'm getting back in response:

{
  "errors": [
    {
      "message": "Topology is closed, please connect",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "item"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "name": "MongoError",
          "stacktrace": [
            "MongoError: Topology is closed, please connect",

            ...

          ]
        }
      }
    }
  ],
  "data": {
    "item": null
  }
}

I've attached my server file below. I have a suspicion that this might be some kind of timeout error, like it takes to long to comb through all 600k records to find the one with the ID I provided? When I remove useUnifiedTopology: true from the MongoClient definition I get a different error:

UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

but I'm not using async or promises myself. Which has me thinking — should I be? Could I somehow hold up the process while I wait for the return from findOneById() (if that is, indeed, the problem)?

As an aside, I've seen at least one example codebase where MongoClient included within itself a Server declaration (also from the 'mongodb' npm package). Would implementing something like that save me from having to block up a terminal window with mongod every time I want to work on my project?

Thank you so much for your time! If I can get this working I'll definitely do a full writeup on Medium or something to pave the way for others looking to pair MongoClient with ApolloServer for a quick and easy API.

index.js

const { MongoClient } = require('mongodb');
const assert = require('assert');
const { ApolloServer, gql } = require('apollo-server');
const { MongoDataSource } = require('apollo-datasource-mongodb');


const client = new MongoClient('mongodb://localhost:27017/projectdb', { useNewUrlParser: true, useUnifiedTopology: true }, (err) => {
  err && console.log(err);
});

client.connect((err) => {
  assert.equal(null, err);
  client.close();
});

const db = client.db();

class Items extends MongoDataSource {
  getItem(id) {
    return this.findOneById(id);
  }
}

const typeDefs = gql`
  type Item {
    id: Int!
    title: String!
  }
  type Query {
    item(id: Int!): Item
  }
`;

const resolvers = {
  Query: {
    item: (_, { id }, { dataSources }) => dataSources.items.getItem(id),
  }
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources: () => ({
    items: new Items(db.collection('items')),
  }),
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${ url }`);
});
Teagan Atwater
  • 178
  • 1
  • 3
  • 10
  • you need to have this `client.close();` after your DB operation. remove it over there – whoami - fakeFaceTrueSoul Jan 09 '20 at 05:04
  • Hi @srinivasy, thanks for the reply! Unfortunately moving `client.close();` to the space between `const server = ... });` and `server.listen()...` still results in the same error as before. Same with moving it to the last line of the file. – Teagan Atwater Jan 09 '20 at 05:12
  • I also tried taking everything from `const db = client.db();` through to the end of the file and pasting it between `assert.equal(null, err);` and `client.close();` but no luck — same error. – Teagan Atwater Jan 09 '20 at 05:15
  • what would this `return this.findOneById(id);` return & what is `id` ? – whoami - fakeFaceTrueSoul Jan 09 '20 at 05:19
  • `id` is a custom, unique 8-digit int variable belonging to each `item` that came from the dataset I'm using (not to be confused with Mongo's `_id`), and yep I'm pretty sure that's where the magic happens — the `apollo-datasource-mongodb` package I linked above takes over after that I think – Teagan Atwater Jan 09 '20 at 05:23
  • 1
    After removing `client.close();` from `client.connect()` , Can you do this :: `async getItem(id) { let result = await this.findOneById(id); client.close(); return result; }` , Also this line `const client = new MongoClient()` doesn't need a callback.. – whoami - fakeFaceTrueSoul Jan 09 '20 at 05:24
  • ok not sure about it usually if a db connection is closed early to db operation you would get topology destroyed kind of error so check if this is some kind related to that, If it didn't work sorry for that !! – whoami - fakeFaceTrueSoul Jan 09 '20 at 05:31
  • Progress (maybe)! The first time I ran it it returned `{ "data": { "item": null } }` but clicking run a second time yields that same old error – Teagan Atwater Jan 09 '20 at 05:36
  • I couldn't find anything on that error, is that any kind of connectivity issue ? – whoami - fakeFaceTrueSoul Jan 09 '20 at 05:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205638/discussion-between-teagan-atwater-and-srinivasy). – Teagan Atwater Jan 09 '20 at 05:37
  • this is a major issue. this is a problem – Christian Matthew May 28 '20 at 16:16

2 Answers2

2

Actually there is one more thing that may be causing this as I also had the same exact error "Topology Closed, please connect".

The thing is if you have a dynamic IP address then in MongoDB atlas you should try allowing all IP addresses.

Add the IP address: 0.0.0.0/0 All my problems were solved after whitelisting this IP address which allows all.

Image of dynamic IP 0.0.0.0/0:

enter image description here

Tomislav Stankovic
  • 3,080
  • 17
  • 35
  • 42
Baraja Swargiary
  • 381
  • 2
  • 10
  • 1
    That's not solving my issue. I have had whitelisted 'from anywhere' even before having this issue in the first place. – Hans May 09 '20 at 14:39
1

Thanks to GitHub's "Used By" dropdown on the apollo-datasource-mongodb I was able to cheat off a few other repositories, and here's what I ended up with (with changes marked in comments):

const { MongoClient } = require('mongodb');
const assert = require('assert');
const { ApolloServer, gql } = require('apollo-server');
const { MongoDataSource } = require('apollo-datasource-mongodb');


// Isolated these for prominence and reuse
const dbURL = 'mongodb://localhost:27017';
const dbName = 'projectdb';

// Made each function async/await
// Swapped the datasource's findOneById() for the collection itself & standard Mongo functions
class Items extends MongoDataSource {
  async getItem(id) {
    return await this.collection.findOne({id: id});
  }
}

const typeDefs = gql`
  type Item {
    id: Int!
    title: String!
  }
  type Query {
    item(id: Int!): Item
  }
`;

// Made each query async/await
const resolvers = {
  Query: {
    item: async (_, { id }, { dataSources }) => {
      return await dataSources.items.getItem(id);
    },
  }
}

// Move the ApolloServer constructor to its own function that takes the db
const init = (db) = {
  return new ApolloServer({
    typeDefs,
    resolvers,
    dataSources: () => ({
      items: new Items(db.collection('items')),
    }),
  });
}

// Use .connect() instead of new MongoClient
// Pass the new db to the init function defined above once it's been defined
// Call server.listen() from within MongoClient
MongoClient.connect(dbURL, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
  assert.equal(null, err);
  const db = client.db(dbName);
  console.log(`Mongo database ${ dbName } at ${ dbURL }`);

  const server = init(db);

  server.listen().then(({ url }) => {
    console.log(`Server ready at ${ url }`);
  });
});

With these changes, the Apollo Playground at localhost:4000 works great! Now to solve the 400 error I'm getting in my client app when I query...

Teagan Atwater
  • 178
  • 1
  • 3
  • 10
  • you don't have to await in your getItem function, and you dont have to declare it async, just return the promesse, and await when calling getItem – Martijn Scheffer Feb 16 '20 at 15:59