Basic Node Functionality

A rundown of the concepts that all Node.js applications are built on.

Scroll down...

Content

Resources

Comments

Node.js is much more than just the REPL that you get by entering the node command. It can run scripts, create command-line tools and fire up robust web applications. Of course, to make this possible you need to be able to create organized code. Let's see how we can accomplish this with Node.js.

Node.js Modules

Node.js uses files as modules. Think of a module as just a reusable piece of code, any code, of any size. You can make code public from a module by setting module.exports equal to the value you want your module to have when required.

First, you have a file called my_module.js.

// my_module.js
module.exports = {
  name: 'My Module'
};

NOTE: a common pitfall is using exports instead of module.exports. We suggest that you ignore the existence of exports and only use module.exports. Why? Have a look at this StackOverflow question answer to learn more. The key takeaway is to use module.exports.

Then you have a file that requires your module.

Using require

Let's require and use my_module.js.

// index.js
var myModule = require('./my_module');

console.log(myModule.name);
//=> My Module

NOTE that we use the preceding dot and slash here because we're requiring a file we made ourselves. When requiring modules relatively from the file system you reference them with relative paths. If we were requiring a native Node.js library we would omit those. We can also omit the .js extension.

Creating a Module

Let's create a module that does something useful. How about a simple logger?

var logger = {
  log: function(message) {
    console.log(message);
  }
};

module.exports = logger;

Notice we created an object to wrap the log function. We could easily put more functions in this object and build out more functionality. This bare minimum will work fine for now. Let's require and use the module!

var logger = require('./lib/logger');

logger.log("Hello world!");

We use relative path syntax to require the logger. This returns the object we created. Calling it logger in this file is just to keep things simple, at this point we could call it anything! The functions available on it will be those defined in the object of the module.

Exporting Objects

The module.exports object defaults to an empty object. If you console.log it, you'll see {}. You may attach properties and functions to it like so:

module.exports.foo = 'bar';
module.exports.fiz = function() {
  console.log('baz');
};

You can reassign it:

module.exports = {
  foo: 'bar',
  fiz: function() {
    console.log('baz');
  }
};

Or you can create the object before exporting, then assign that object to module.exports:

var biz = {
  foo: 'bar',
  fiz: function() {
    console.log('baz');
  }
};

module.exports = biz;

All of these accomplish the same thing. But what about exporting functions and constructors?

Exporting Functions and Constructors

The same options that exist for exporting objects are available when exporting functions. Here is the recommended way to export functions (which can also be constructors):

var Foo = function() {
  this.foo = 'bar';
  this.baz = function() {
    console.log('fiz');
  };
};

module.exports = Foo;

Notice we assign module.exports to the reference to the constructor! This allows us to create instances of this object after we require the module like so:

var Foo = require('./lib/foo');

var foo = new Foo();

This is fine, but why mention this at all? Well... consider the following example:

var Foo = function() {
  this.foo = 'bar';
  this.baz = function() {
    console.log('fiz');
  };
};

module.exports = new Foo();

Now take it into account that require caches its result after the first time you require a file. So what's going to happen? The file gets required in another file A:

var foo = require('./lib/foo');
foo.foo = 'Different!';

Now we've changed a property of the cached instance. So the next time it is required in file B:

var foo = require('./lib/foo');
console.log(foo.foo);
//=> 'Different!'

We can see that the same instance is required and the property has the value set in file A. This is a problem because we most likely want to be able to create separate instances of the Foo constructor. We don't want side effects from one file effecting another.

It is much more flexible and less prone to errors if we export the reference to the constructor and instantiate separate instances in each file that requires the module:

// lib/foo.js

module.exports = Foo;
// file A
var Foo = require('./lib/foo');
var foo = new Foo();
foo.foo = 'Different!';
// file B
var Foo = require('./lib/foo');
var foo = new Foo();
console.log(foo.foo);
//=> 'bar'

The key takeaway: export the reference to a constructor, not an instance of a constructor.

Code Review

Here are the important snippets of code from this lesson.

// Create a module file

// my_module.js
module.exports = {
  name: 'My Module'
};


// Require a local module file

// index.js
var myModule = require('./my_module');

console.log(myModule.name);
//=> My Module
// Various ways to export objects

// As properties
module.exports.foo = 'bar';
module.exports.fiz = function() {
  console.log('baz');
};

// Reassign to object literal
module.exports = {
  foo: 'bar',
  fiz: function() {
    console.log('baz');
  }
};

// Assign to object reference
var biz = {
  foo: 'bar',
  fiz: function() {
    console.log('baz');
  }
};

module.exports = biz;
// Exporting a constructor function

// lib/foo.js
var Foo = function() {
  this.foo = 'bar';
  this.baz = function() {
    console.log('fiz');
  };
};

module.exports = Foo;

// file A
var Foo = require('./lib/foo');
var foo = new Foo();
foo.foo = 'Different!';

// file B
var Foo = require('./lib/foo');
var foo = new Foo();
console.log(foo.foo);
//=> 'bar'

Wrapping Up

As you can see, the basics of using Node.js modules is very straightforward. You simply export the interface you want to make available from your module, require it, and then use it! But what about extending the functionality of Node.js and creating reusable code across various projects? What about incorporating other developer's code into your projects so you can benefit from the huge Node.js community's contributions? That is the purpose of NPM and brings us to our next section.



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!