DEV Community

codestruct.io
codestruct.io

Posted on • Edited on

Writing reusable modules in JavaScript

As developers we often use a lot of libraries and packages and know very well how to use them. We're used to it, because packages and libraries are a huge part of the ecosystem and a great way to speed up development and free us from rather complicated, repetitive tasks.

When it comes to writing libraries or reusable modules from scratch however, it's not always so clear where to start and how to make sure our libraries are not breaking anything else in an application. Making sure we're using our own scope, function names and such are only part of what makes a good module. Let's take a close look at how to write our own libraries that can be plugged into different projects without worrying about breaking any existing code.

When we're the developer who's using a library, we care about simplicity and ideally want to understand what's going on by the name of the function we're calling alone. We're also very used to a certain kind of syntax, which probably looks something like this:

Library.init({
    option: "option"
})

Library.doThing(arg1, arg2);

Enter fullscreen mode Exit fullscreen mode

We can initialize our library with some options and then just call pre-defined and hopefully well documented functions on the library, possibly passing in some arguments. Sometimes we'll get a return value or promise out of that function call, other times there might be a change in the DOM as a result of calling the function. Either way, we're very used to that kind of syntax when using libraries and so when writing our own we should try to imitate that.

The object literal notation in JavaScript lets us do just that. It also has the added benefit of keeping everything inside our object and therefore we're not putting anything related to our module on the global scope, except the object itself.

var Module = {
    // Our Object literal can hold simple variables
  someProperty: "someValue",

    // We can also declare further objects inside our object
  config: {
    reload: true,
    language: "en"
  },

    // And of course declare regular, named functions
  saySomething: function (word) {
    console.log(word);
  }
};

Module.say("hi");
Enter fullscreen mode Exit fullscreen mode

The module pattern

We can take the object literal approach one step further with the module pattern, which allows us to declare public and private functions and variables, using closures. We're basically defining a public API that our library/module exposes, while keeping other internal states, variables and functions private and thereby inaccessible from the outside.

var Module = (function() {

  let secret = 0;
    let publicNumber = 2;

  function privateFunc(a) {
    console.log(a);
  }

    function addOne(b) {
        return b + 1;
    }

  return {
        addOne: addOne,
        publicNumber: publicNumber
  }

})()

Module.addOne(Module.publicNumber);
Enter fullscreen mode Exit fullscreen mode

As shown above we can now explicitly define what functions and variables should be accessible from the outside by returning an object with only those things what we want the outside to know about. This is great, because we can hide away a lot of the heavy lifting or things we don't want to expose because calling them directly would break things. We're basically exposing a little API that is consistent and well structured.

While this pattern is great for frontend libraries and modules, it is also super convenient when writing modules in node. In fact most modules naturally follow this pattern in a way, because they have a (default) export statement. Here's how we can utilize the same structure in node

import db from '../models/index.js'

const MessageController = {
    sendMessage: async function(message) {
                // ...
        }

    receiveMessage: async function(message) {
        // ...
    },
}

export default MessageController
Enter fullscreen mode Exit fullscreen mode

That way we get to keep the clean structure of our object literal / module pattern, while being able to just import our module like we would with other modules. We also get to chosse what to export explicitly while we can keep other functions private. Here's how we'd import the above module in node:

import MessageController from './controllers/message-controller.js';
Enter fullscreen mode Exit fullscreen mode

Stay tuned for a look at other useful JS design patterns like singletons or the factories 👉 https://allround.io

Top comments (0)