The Road to MERN #1

Kaelin Alexander
5 min readJan 3, 2021
A mongoose, proud and on the alert.
A mongoose, proudly on the alert for database queries.

In preparing for the dive into a full MERN (MongoDB, Express, React, Node) app, I started out with a tutorial for the full stack. Now, though, I want to dive in a little deeper with each layer of the stack so that I’m ensuring I’m expanding my knowledge base, rather than just figuring out how to perform a new task (or, well, an old task in new ways).

With the turn to MongoDB, the database game changes quite a bit. As a NoSQL database system, MongoDB eschews our beloved tables of rows that formed the backbone of Rails’ data persistence and opts instead for collections of documents.

For the moment, that distinction remains pretty theoretical, but some point soon I’ll be taking a deep dive in order to understand how and why the distinction between NoSQL and SQL databases really matters.

Off the cuff, the major distinction seems to be that the objects stored in MongoDB are indeed always objects, which means they must object the rules for objects in JavaScript more generally. As with Nodes’ more general lean into JS for everyone and everything, this provides a lower barrier to entry: if you know what to do with an object, you know what to do with a document in MongoDB.

Moreover, while I love tables, they’re a level of abstraction that simply doesn’t exist as such within Ruby. In that regard, I can at least conceptually understand why NoSQL databases are — according to Ye Olde Documentation — faster and easier to scale.

Back in Bootcamp, all the Rails apps that I made relied on SQLite databases, which meant turning to ActiveRecord as the ORM (Object Relations Mapping) system that allowed users to perform full CRUD. Mongoose is the analogous ODM (Object Document Modeling) library for MongoDB, and while the underlying databases are quite a bit different, the CRUD actions remain more-or-less analogous, if a bit more straightforward and elegant — at least for now.

For folks making the leap over to MongoDB/Mongoose from SQLite/ActiveRecord, here’s how CRUD methods compare between the two, assuming you’ve already set up your Schema/models and migrations/models, and imported them correctly.

For all examples, then, let’s assume we have a model called “Pokemon” (it doesn’t have the accent when represented in code, naturally) that includes attributes for name (a string), dex (a number/integer), type1 (a string), and type2 (also a string).

Keep in mind, too, that the Mongoose methods can almost all except multiple parameters; this is the most basic way to set things up with a single parameter (except, of course, where more than one is explicitly required).

Create

Pokemon.create / Pokemon.new & Pokemon.save > new Pokemon & newPoke.save

In ActiveRecord, creating a new instance of a Pokémon is as easy as creating an instance variable, let’s call it “new_poke,” and calling create on the Pokemon class with all the necessary (or optional) attributes:

new_poke = Pokemon.create(name: “Clefairy”, dex: 35, type1: “Fairy”, type2: null)

Alternately, you can always call “new” and “save” separately — the create method simply does both in one go.

In Mongoose, making the same deeply wonderful, retyped, evolves-into-a-total-beast-with-Magic-Guard Pokémon absolutely requires calling save() on the instance of the object — here called “newPoke” because camelCase in JS — which you first inaugurate by assigning a local variable to “new Pokemon”:

https://codepen.io/kaelinalexander/pen/RwGMBgX

Like all mongoose-based methods that reach out the database, save() is asynchronous and returns a promise; as such, it needs handling in that regard.

Read (Single Instance)

Pokemon.find_by(attr: value) > Pokemon.findOne({attr: value})

There are many, many ways to query and filter data in both ActiveRecord and Mongoose. Both the methods shown here will return the first matching record, rather than the entire collection of matching records.

In ActiveRecord, this is pretty straight forward:

clef = Pokemon.find_by(name: “Clefairy”)

In Mongoose, this isn’t all that different — just mind the curly braces here, and in all other references to the document, and remember that this is asynchronous:

https://codepen.io/kaelinalexander/pen/YzGajJz

The most down-to-earth focused search is, of course, finding by ID. In ActiveRecord, the ID attribute is implied in the super-vanilla-sounding find method:

https://codepen.io/kaelinalexander/pen/YzGajJz

In Mongoose, you can find by id either using findOne(), remembering that the ID attribute is inaugurated as “_id” with an underscore.

… Or you can use the more expressive findById():

https://codepen.io/kaelinalexander/pen/YzGajJz

Keep in mind that the _id attribute in MongoDB is actually itself an object. So, if you need to compare a string-version of an id to a version of an ID pulled from the database, you’ll need to call .toString() on the ID as well.

Update

Pokemon.find(id) && clef.attr = new_value && clef.save // Pokemon.findOneAndUpdate()

This is one arena in which Mongoose has clearly streamlined things. Where updating in ActiveRecord is a three-step process (finding the record, changing it, and saving it) Mongoose’s approach requires only one step.

So, in ActiveRecord, we need to retrieve the record and assign it to a local variable, modify the attributes on that local variable as necessary, and call the save method. Assuming our beloved Clefairy has evolved into the beast we know and love:

clef = Pokemon.find_by(name: “Clefairy”)clef.name = “Clefable”clef.dex = 36clef.save

We can accomplish the exact same evolution in Mongoose with findOneAndUpdate(), which requires at least two parameters: some query information and the updated attribute values for the first matching record that is returned:

https://codepen.io/kaelinalexander/pen/xxEWJmB

As always, this is asynchronous, so handle it as you need to.

There are several variations on this of course — findByIdAndUpdate() is, of course, the least error-prone one, since you might well live the dream of having an entire team of Clef-prefixed Pokémon, and findOne() will only return the first one called “Clefairy.”

You could also very well find the record with findOne(), or what-have-you, and then call save() on the record after making your changes; that would do the same thing, but would be less DRY.

Delete

Pokemon.find_by(attr: value) && clef.destroy > findOneAndRemove()

While I cannot fathom a universe in which one would ever part with a Clefairy, my hypothetical has backed me into a corner here. So, as before this is a two step process in ActiveRecord: find the Clefairy in question and then — long sigh here — destroy it.

clef = Pokemon.find_by(name: “Clefairy”)clef.destroy

In Mongoose, this is yet again a single method (or one of many single methods, anyway), findOneAndRemove():

https://codepen.io/kaelinalexander/pen/ZEpxjPR

The more precise variant is, of course, findByIdAndRemove(). For the more longwinded version, first query with the method of your choice, then call remove(). Both remove() and its query-concise variations are, of course, asynchronous.

(Photo by Ilyas Aliev on Unsplash)

--

--

Kaelin Alexander

Full-Stack Web Developer, Researcher, Editor, LGBTQ+ Media Nerd