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:
Incorrect uri scheme. Fails at step 1. This is handled by the current implementation, and a warning is printed to console.
Correct uri scheme but it includes a port, which in some cases cannot actually be included (mongodb+srv cannot include a port).
Correct uri scheme but incorrect MongoDB credentials.
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:
Indiekit would take ~30 seconds to start
I could not create notes, nor query them
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. :man_facepalming:
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 instantiateMongoClient
. 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:
mongodb+srv
cannot include a port).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:
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 forserverSelectionTimeoutMS
). Indiekit printed a warning but kept running. This means thatclient
was nowundefined
, and I encountered runtime exceptions when executingposts.insertOne()
ormedia.findOne()
, becauseposts
andmedia
wereundefined
.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. :man_facepalming: