<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Nienke</title>
    <description>The latest articles on Forem by Nienke (@helenasometimes).</description>
    <link>https://forem.com/helenasometimes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F46058%2F5ecedab3-2a62-4884-9100-3ecf3f6b73ef.jpg</url>
      <title>Forem: Nienke</title>
      <link>https://forem.com/helenasometimes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/helenasometimes"/>
    <language>en</language>
    <item>
      <title>Getting started with Mongoose discriminators in Express.js</title>
      <dc:creator>Nienke</dc:creator>
      <pubDate>Mon, 12 Feb 2018 09:00:14 +0000</pubDate>
      <link>https://forem.com/helenasometimes/getting-started-with-mongoose-discriminators-in-expressjs--22m9</link>
      <guid>https://forem.com/helenasometimes/getting-started-with-mongoose-discriminators-in-expressjs--22m9</guid>
      <description>&lt;p&gt;I recently started rewriting my Rails side project, &lt;a href="https://what.pm/"&gt;what.pm&lt;/a&gt;, to Express. One reason is that I want to get better at JavaScript, the other is that Rails feels very magical and I don't like the fact that I don't really know what I'm doing when I use it ("it works, but I'm not sure why"). That's not necessarily a bad thing and it's something that can be solved by diving under Rails' hood, but I'm not interested in doing that, to be honest.&lt;/p&gt;

&lt;p&gt;So for this rewrite, I wanted to dig a little deeper in storing data and stop relying on behind-the-scenes magic. This means coming up with a proper data model. I wanted a NoSQL database for flexibility (I might need to add different collection types later!). I opted for MongoDB because it meshes well with Node, and because I wanted to try MongooseJS (looking at the docs, it seemed to provide an easy to understand abstraction layer and spoiler alert: it is pretty neat).&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;I'm writing this post as I'm learning, and my understanding of any concepts mentioned might be wrong. If you think that's the case, do let me know 😃 &lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Imagine you're tracking which movies, books and tv shows you consume in a given year. These three things have a few things in common: they all have a title and a date of release. They also differ from eachother, however: a Book has an &lt;em&gt;author&lt;/em&gt;, whereas a Movie has a &lt;em&gt;director&lt;/em&gt;. A TV show has neither of these things, but it does have a &lt;em&gt;season&lt;/em&gt;. So, how would you set up your Mongoose schemas? You could easily create three different schemas for each (Book, Movie and TVshow). However, you'd be repeating yourself - in every schema, you'd have the same title field and date of release field. And if you wanted to add another field that all three schemas have in common - such as whether it's a rewatch/reread ('redo') - you'd have to add that new field to three different files. &lt;/p&gt;

&lt;p&gt;What if you could extend some kind of "Base" schema, and have Movies, Books and TV Shows inherit from that one schema? I didn't know how, but luckily, a &lt;a href="https://peeke.nl/"&gt;colleague&lt;/a&gt; suggested I look into Mongoose discriminators. Unfortunately, the documentation is a little sparse, and I couldn't find any Express.js specific tutorials/blog posts, so here's my attempt at fixing that. Hopefully, this post will help those looking to integrate Mongoose discriminators in their Express app :)&lt;/p&gt;

&lt;h2&gt;
  
  
  The non-DRY way
&lt;/h2&gt;

&lt;p&gt;Just for clarity, this is what our schemas could look like without discriminators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/book.js

// Define our Book schema
const BookSchema = new mongoose.Schema(
  {
    title: { type: String, required: true },
    author: { type: String, required: true },
    release_date: { type: Date, required: true },
  }
);

// Create a model from our schema
module.exports = mongoose.model('Book', BookSchema);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/movie.js

const MovieSchema = new mongoose.Schema(
  {
    title: { type: String, required: true },
    director: { type: String, required: true },
    release_date: { type: Date, required: true },
  }
);

module.exports = mongoose.model('Movie', MovieSchema);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/tvshow.js

const Tvshow = new mongoose.Schema(
  {
    title: { type: String, required: true },
    season: { type: Number, required: true },
    release_date: { type: Date, required: true },
  }
);

module.exports = mongoose.model('Tvshow', TvshowSchema);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing wrong with that! However, like I mentioned before, if we wanted to add a new property, say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// signals whether I've already seen or read the item in question
redo: { type: Boolean, required: false } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'd have to add it three times in three separate files 😖. So let's try something different. &lt;/p&gt;

&lt;p&gt;We're going to create one 'master' schema called &lt;code&gt;Base&lt;/code&gt;, and we're going to make &lt;code&gt;Book&lt;/code&gt;, &lt;code&gt;Movie&lt;/code&gt; and &lt;code&gt;Tvshow&lt;/code&gt; inherit from it. This is what we want to achieve in pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base:
    title: { type: String, required: true },
    date_released: { type: Date, required: true },
    redo: { type: Boolean, required: false },

Book:
    Inherit everything from Base, and add the following just for this schema:
    author: { type: String, required: true }

Movie:
    Inherit everything from Base, and add the following just for this schema:
    director: { type: String, required: true }

TV Show:
    Inherit everything from Base, and add the following just for this schema:
    season: { type: Number, required: true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how are we going to give our child schemas (Book, Movie, Tvshow) the &lt;code&gt;Base&lt;/code&gt; options? In other words, how will we extend our &lt;code&gt;Base&lt;/code&gt;? Enter &lt;a href="http://mongoosejs.com/docs/discriminators.html"&gt;discriminators&lt;/a&gt;. A discriminator is a function for &lt;code&gt;model&lt;/code&gt; that &lt;em&gt;returns a model whose schema is the union of the base schema and the discriminator schema.&lt;/em&gt; So basically, a discriminator will allow us to specify a key, like &lt;code&gt;kind&lt;/code&gt; or &lt;code&gt;itemtype&lt;/code&gt;. With this key, we can store different entities (books, movies, tv shows..) in one collection, and we'll still be able to discriminate (&lt;em&gt;badum tsss&lt;/em&gt;) between these entities.&lt;/p&gt;

&lt;p&gt;So let's set up our Base schema. Again, that's the structure that our other schemas will extend from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const baseOptions = {
  discriminatorKey: 'itemtype', // our discriminator key, could be anything
  collection: 'items', // the name of our collection
};

// Our Base schema: these properties will be shared with our "real" schemas
const Base = mongoose.model('Base', new mongoose.Schema({
      title: { type: String, required: true },
      date_added: { type: Date, required: true },
      redo: { type: Boolean, required: false },
    }, baseOptions,
  ),
);

module.exports = mongoose.model('Base');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we could edit &lt;code&gt;book.js&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/book.js

const Base = require('./base'); // we have to make sure our Book schema is aware of the Base schema

const Book = Base.discriminator('Book', new mongoose.Schema({
    author: { type: String, required: true },
  }),
);

module.exports = mongoose.model('Book');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Base.discriminator()&lt;/code&gt;, we're telling Mongoose that we want to get the properties of &lt;code&gt;Base&lt;/code&gt;, and add another &lt;code&gt;author&lt;/code&gt; property, solely for our Book schema. Let's do the same thing with &lt;code&gt;models/movie.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/movie.js

const Base = require('./base');

const Movie = Base.discriminator('Movie', new mongoose.Schema({
    director: { type: String, required: true },
  }),
);

module.exports = mongoose.model('Movie');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;tvshow.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; models/tvshow.js

const Base = require('./base');

const Tvshow = Base.discriminator('Tvshow', new mongoose.Schema({
    season: { type: Number, required: true },
  }),
);

module.exports = mongoose.model('Tvshow');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we create a new book for our collection, the new Book instance will show up in our MongoDB collection like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "_id": {
        "$oid": "unique object ID"
    },
    "itemtype": "Book", 
    "author": "Book Author 1",
    "title": "Book Title 1",
    "date_added": {
        "$date": "2018-02-01T00:00:00.000Z"
    },
    "redo": false,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, right? Now let's fetch some data. The example below will return the amount of books in our collection, and all tv shows with their titles and seasons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; controllers/someController.js

const Book = require('../models/book');
const Tvshow = require('../models/tvshow');
const async = require('async');

exports.a_bunch_of_stuff = function(req, res) {
    async.parallel({
        book_count: function (callback) {
            Book.count(callback);
        },
        tvshow_all: function(callback) {
            Tvshow.find({}, 'title season', callback)
        },
    }, function(err, results) {
        res.render('index', { error: err, data: results });
    });
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;By using a discriminator we have four small files with DRY code, instead of three larger model files with lots of the same code 😎 now anytime I want to add a new property that is shared across schemas, I'll only have to edit &lt;code&gt;Base&lt;/code&gt;. And if I want to add new models (maybe I should start keeping track of concerts I go to!), I can easily extend existing properties when needed.&lt;/p&gt;

</description>
      <category>express</category>
      <category>mongodb</category>
      <category>mongoose</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
