I have a new appreciation for Object Relational Mapping (ORMs). I can write a SQL query but I still worry about getting it wrong so I spend a lot of time searching. I'm focusing on writing more database queries with personal projects since at work we use a Java ORM to manage database access. I've been using MongoDB while reading MongoDB The Definitive Guide. +1 for print media, I already learned about $elemMatch
which I did not learn online skimming documentation at random.
I created a hit counter for this site! I used the native MongoDB Node.js database driver since it's easy to write insert and update queries. I drafted the document schema for a Like button and thought "let's expand on the database driver functions," and I started writing an abstraction around the driver to create new document collections. I thought this would be a good way to make extract-transform-load scripts to insert data into the database, create new collections, etc. My version looked something like this:
async function createCollection(
dbClient: MongoClient,
dbName: string,
collectionName: string,
schema: { [key: string]: any },
) {
try {
const newCollection = await dbClient
.db(dbName)
.createCollection(collectionName, {
validator: {
$jsonSchema: {
...schema,
},
},
});
return newCollection;
} catch (error) {
console.error(error);
}
}
My function accepts a MongoDB database instance like this, then the user specifies the database name and a new collection name. The last argument is the document schema which is any MongoDB JSON validation schema. The schema
argument accepts any set of key-value pairs then passes those to the actual MongoDB driver function. This wrapper would execute on startup to connect to the database and create the collection if it didn't exist (not shown). It got complicated to define even a single collection.
This function would probably work but now I'm creating a lot of overhead. You'll notice I am logging the errors but I'm not accounting for any real error handling with this function. Should I retry creating the collection if this function fails? How do I gracefully exit if the collection already exists? What if I want to add query validation in addition to $jsonSchema
configuration? So many questions...that already have an answer, an ORM.
Node.js has an ORM called mongoose
designed for MongoDB. It solves this whole "create collection" problem I mentioned. You can also programatically create new documents to insert and update as needed alongside everything else you'd want to do with MongoDB. Mongoose supports Typescript, so no need to type the custom arguments for my createCollection
function. Also, since a team of people work on developing the ORM (or Object Document Mapping in this case) they can be responsible for managing edge cases and all sorts of different error states. Obviously then it falls on me to understand the best way to use the library error handling included, but I don't have to reinvent the wheel.
However, all this advanced technology isn't free. Node.js' MongoDB driver is 2.66MB in size (v4.11.0
). That's ok, too big for the browser but it's the cost of doing business, you want to use the database you need a way to connect. I write my functions on top of that, business as usual, no big deal except all the problems I just mentioned above. Add mongoose v6.7.1
to this and you get another 2.75MB, doubling the install size. For this project, this may still not be a tradeoff I'm willing to make, but for someone who needs to manage a lot of different collections it's probably worthwhile to go the ORM route.
This was a big learning for me and I will have to think more about the level of abstraction I want to put on top of the driver, so I will probably revisit the database setup scripts in more detail.
References
[1] J. W. Comeau, "Creating a Modern Day Hit Counter," joshwcomeau.com, Sept. 28 2020. Accessed on: Nov. 6 2020. Available: https://joshwcomeau.com/react/serverless-hit-counter