Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immediately notify the user about connection issues with MongoDB #690

Closed
jackdbd opened this issue Feb 14, 2024 · 1 comment · Fixed by #695
Closed

Immediately notify the user about connection issues with MongoDB #690

jackdbd opened this issue Feb 14, 2024 · 1 comment · Fixed by #695
Labels
enhancement New feature or request

Comments

@jackdbd
Copy link
Contributor

jackdbd commented Feb 14, 2024

Is your feature request related to a problem?

At the moment the getMongodbClient function doesn't implement any error handling, apart from printing a warning when it cannot instantiate MongoClient. This catch captures only a few failure modes, for instance when the uri scheme is incorrect, like "incorrect-mongodb-uri".

The correct implementation leaves out many failures modes which manifest as runtime issues when using Indiekit. See the snippet below for details.

Describe the solution you’d like

A UI element like a toast, a dialog, or something else could notify the user about database connection issues as soon as Indiekit starts.

I can think of the following scenarios which Indiekit should handle:

  1. Incorrect uri scheme. Fails at step 1. This is handled by the current implementation, and a warning is printed to console.
  2. Correct uri scheme but it includes a port, which in some cases cannot actually be included (mongodb+srv cannot include a port).
  3. Correct uri scheme but incorrect MongoDB credentials.
  4. Correct uri scheme and correct MongoDB credentials, but nonexistent database.
import { MongoClient, ServerApiVersion } from "mongodb";
import { uri } from "./config.js";

const username = "john";
const password = "wrong-password";
const host = "cluster123.abc.mongodb.net";
const port = 27017;

// 1. incorrect uri scheme. Fails at step 1
// const uri = "incorrect-mongodb-uri";

// 2. correct uri scheme but it fails because mongodb+srv cannot include a port. Fails at step 1
// const uri = `mongodb+srv://${username}:${password}@${host}:${port}`;

// 3. correct uri scheme but nonexistent database. Fails at step 2
// const uri = `mongodb+srv://${username}:${password}@${host}`;

// 4. correct uri (which I have in config.js)

// 5. correct uri and credentials, but nonexistent database
// const db_name = "nonexistent-database";
const db_name = "indiekit";

const run = async () => {
  // step 1: instantiate MongoClient
  let client;
  try {
    client = new MongoClient(uri, {
      serverApi: {
        deprecationErrors: true,
        strict: true,
        version: ServerApiVersion.v1,
      },
      //   serverSelectionTimeoutMS: 1, // this will let us instantiate MongoClient, but it will fail at step 2
      serverSelectionTimeoutMS: 10000,
    });
  } catch (ex) {
    // Example: Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"
    // Example: mongodb+srv URI cannot have port number

    // There is no db connection to close because there is no client in the first place
    return {
      error: new Error(ex.message || "could not create MondoDB client"),
    };
  }

  // step 2: connect to MongoDB
  let error;
  try {
    await client.connect();
  } catch (ex) {
    // Example: querySrv ENOTFOUND _mongodb._tcp.cluster123.abc.mongodb.net
    // Example: MongoServerSelectionError: Server selection timed out after 1 ms
    error = new Error(ex.message || "could not connect to MongoDB");
  }

  if (error) {
    await client.close();
    return { error };
  }

  let db_names = [];
  try {
    db_names = (await client.db(db_name).admin().listDatabases()).databases.map(
      (db) => db.name
    );
    if (!db_names.includes(db_name)) {
      error = new Error(`database ${db_name} does not exist`);
    }
  } catch (ex) {
    error = new Error(
      ex.message ||
        `could connect to MongDB but could not connect to database ${db_name}`
    );
  }

  if (error) {
    await client.close();
    return { error };
  }

  // Database exists, so we return the MongoDB client. The caller will have to
  // call `await client.close()` when appropriate.
  return { value: client };
};

run().then((result) => {
  const { error, value: client } = result;

  if (error) {
    console.trace(error);
  }

  if (client) {
    client.close().then(() => {
      console.info(`connection closed`);
    });
  }
});

Describe alternatives you’ve considered

No response

Additional context

I'm writing here how I found out about the need for better error handling in regards to the MongoDB connection.

I was using MongoDB Atlas as my database, and I was connecting to it from my laptop and from a Google Cloud Compute Engine VM.

The connection from my laptop was fine, and I could use Indiekit without any issue. However, when launching Indiekit from the VM, I had these issues:

  1. Indiekit would take ~30 seconds to start
  2. I could not create notes, nor query them
  3. I could not upload media, nor query them

Long story short, the reason for those ~30 seconds was that MongoClient could not establish a connection to Atlas, and would throw after 30000ms (the default value for serverSelectionTimeoutMS). Indiekit printed a warning but kept running. This means that client was now undefined, and I encountered runtime exceptions when executing posts.insertOne() or media.findOne(), because posts and media were undefined.

And why wasn't I able to connect to Atlas from my VM?
Simple. I had forgotten about whitelisting the IP of my VM in Atlas. 🤦‍♂️

@jackdbd jackdbd added the enhancement New feature or request label Feb 14, 2024
@jackdbd
Copy link
Contributor Author

jackdbd commented Feb 16, 2024

If Indiekit cannot establish a connection to MongoDB at startup, I get an error like this as soon as I try to perform some operation on the database.

upload-file-error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant