134

So up until now, i have created classes and modules in node.js the following way:

    var fs = require('fs');

var animalModule = (function () {
    /**
     * Constructor initialize object
     * @constructor
     */
    var Animal = function (name) {
        this.name = name;
    };

    Animal.prototype.print = function () {
        console.log('Name is :'+ this.name);
    };

    return {
        Animal: Animal
    }
}());

module.exports = animalModule;

Now with ES6, you are able to make "actual" classes just like this:

class Animal{

 constructor(name){
    this.name = name ;
 }

 print(){
    console.log('Name is :'+ this.name);
 }
}

Now, first of all, i love this :) but it raises a question. How do you use this combined with node.js's module structure?

Say you have a class where you wish to use a module for the sake of demonstration say you wish to use fs

so you create your file:


Animal.js

var fs = require('fs');
class Animal{

 constructor(name){
    this.name = name ;
 }

 print(){
    console.log('Name is :'+ this.name);
 }
}

Would this be the right way?

Also, how do you expose this class to other files within my node project? And would you still be able to extend this class if you're using it in a separate file?

I hope some of you will be able to answer these questions :)

mscdex
  • 104,356
  • 15
  • 192
  • 153
Marc Rasmussen
  • 19,771
  • 79
  • 203
  • 364
  • 3
    Just treat the ES6 class name the same as you would have treated the constructor name in the ES5 way. They are one and the same. The ES6 syntax is just syntactic sugar and creates exactly the same underlying prototype, constructor function and objects. – jfriend00 Mar 08 '17 at 23:58
  • That IIFE that creates your `animalModule` is pretty pointless in a node module that has its own module scope anyway. – Bergi Mar 09 '17 at 00:53

5 Answers5

204

Yes, your example would work fine.

As for exposing your classes, you can export a class just like anything else:

class Animal {...}
module.exports = Animal;

Or the shorter:

module.exports = class Animal {

};

Once imported into another module, then you can treat it as if it were defined in that file:

var Animal = require('./Animal');

class Cat extends Animal {
    ...
}
rossipedia
  • 56,800
  • 10
  • 90
  • 93
  • 8
    You can also do something like module.exports = class Animal {} – Paul Mar 08 '17 at 23:59
  • that is true, I keep forgetting you can name things during assignment. – rossipedia Mar 09 '17 at 01:28
  • It comes down to code style and clarity. `module.exports` is typically used for an anonymous export, while `export` is used for a named export. This is a basic coding courtesy (you might say), that can assist others for knowing how to import your class, module et al et cetera. – greg.arnott Sep 05 '17 at 04:11
  • 8
    `module.exports = Animal;` would be the answer or most direct equivalent to the question and is valid along with `const Animal = require('./animal');` in calling code. Can you update your answer to include it? – sun Sep 20 '17 at 20:25
  • 1
    Note that `class Animal {...} module.exports = Animal` and `module.exports = class Animal {...}` are not the same: In the latter variation you cannot use `new Animal()` in the required file because the class name is accessible only from within the class. – wortwart May 05 '19 at 11:38
  • And it seems that I can assign an instance like this (parens around require are needed): `const myAnimal = new (require('./Animal'))();` – user1944491 Oct 06 '20 at 22:51
20

Just treat the ES6 class name the same as you would have treated the constructor name in the ES5 way. They are one and the same.

The ES6 syntax is just syntactic sugar and creates exactly the same underlying prototype, constructor function and objects.

So, in your ES6 example with:

// animal.js
class Animal {
    ...
}

var a = new Animal();

module.exports = {Animal: Animal};

Or the export declaration can be shortened to:

module.exports = { Animal };

You can just treat Animal like the constructor of your object (the same as you would have done in ES5). You can export the constructor. You can call the constructor with new Animal(). Everything is the same for using it. Only the declaration syntax is different. There's even still an Animal.prototype that has all your methods on it. The ES6 way really does create the same coding result, just with fancier/nicer syntax.


On the import side, this would then be used like this:

const Animal = require('./animal.js').Animal;

let a = new Animal();

Or, it can just be:

const { Animal } = require('./animal.js');

let a = new Animal();

This scheme exports the Animal constructor as the .Animal property which allows you to export more than one thing from that module.

If you don't need to export more than one thing, you can do this:

// animal.js
class Animal {
    ...
}

module.exports = Animal;

And, then import it with:

const Animal = require('./animal.js');

let a = new Animal();
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I don't know why but this just did not work for me. `module.exports = Animal` is the only solution that works. – Sam Jul 11 '18 at 22:09
  • 1
    @Sam - What my export shows needs a different `require()` than what your export shows, so that would be why one would work and the other wouldn't. You have to match up how the import works with how the export is defined. More details to explain this added to my answer. – jfriend00 Jul 11 '18 at 22:30
  • @jfriend00 - how would you declare private methods and data? – milesmeow Jan 15 '22 at 01:24
  • @milesmeow - See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields. – jfriend00 Jan 15 '22 at 01:38
7

The ES6 way of require is import. You can export your class and import it somewhere else using import { ClassName } from 'path/to/ClassName'syntax.

import fs from 'fs';
export default class Animal {

  constructor(name){
    this.name = name ;
  }

  print(){
    console.log('Name is :'+ this.name);
  }
}

import Animal from 'path/to/Animal.js';
Fan Jin
  • 2,412
  • 17
  • 25
  • 4
    It would be good to clarify that this is an option, but not a requirement. This is ES6 module syntax, but you can still use an ES6 class with Node's normal CommonJS exports. There is no requirement that you use ES6 export syntax with classes. Calling it `The ES6 way` is somewhat misleading. – loganfsmyth Mar 09 '17 at 03:16
  • 2
    That's true, it's a personal preference. Personally, I would use `import` over `require` just for the sake of syntax consistency. – Fan Jin Mar 09 '17 at 05:21
  • 2
    Yeah it's a solid approach and I do it too, just keep in mind that the way Babel's `import` interoperates with CommonJS modules is not likely what will end up working in Node, so it could require code changes in the future to be compatible with Node without Babel. – loganfsmyth Mar 09 '17 at 06:07
  • 4
    ES6 modules (import and export) are still experimental in Node 10 and need to be turned on when launching node – dcorking Sep 20 '18 at 10:51
  • To add to @dorking's point. Node 10.15.3 is the LTS (long term support) version and will be until April 2020. Additional details here: https://nodejs.org/en/about/releases/ – gtzilla May 25 '19 at 01:13
  • @gtzilla that is no more true, still in 2019 and the LTS is already 12.13.1 – tam.teixeira Dec 02 '19 at 20:01
3

Using Classes in Node -

Here we are requiring the ReadWrite module and calling a makeObject(), which returns the object of the ReadWrite class. Which we are using to call the methods. index.js

const ReadWrite = require('./ReadWrite').makeObject();
const express = require('express');
const app = express();

class Start {
  constructor() {
    const server = app.listen(8081),
     host = server.address().address,
     port = server.address().port
    console.log("Example app listening at http://%s:%s", host, port);
    console.log('Running');

  }

  async route(req, res, next) {
    const result = await ReadWrite.readWrite();
    res.send(result);
  }
}

const obj1 = new Start();
app.get('/', obj1.route);
module.exports = Start;

ReadWrite.js

Here we making a makeObject method, which makes sure that a object is returned, only if a object is not available.

class ReadWrite {
    constructor() {
        console.log('Read Write'); 
        this.x;   
    }
    static makeObject() {        
        if (!this.x) {
            this.x = new ReadWrite();
        }
        return this.x;
    }
    read(){
    return "read"
    }

    write(){
        return "write"
    }


    async readWrite() {
        try {
            const obj = ReadWrite.makeObject();
            const result = await Promise.all([ obj.read(), obj.write()])
            console.log(result);
            check();
            return result
        }
        catch(err) {
            console.log(err);

        }
    }
}
module.exports = ReadWrite;

For more explanation go to https://medium.com/@nynptel/node-js-boiler-plate-code-using-singleton-classes-5b479e513f74

Nayan Patel
  • 1,683
  • 25
  • 27
1

In class file you can either use:

module.exports = class ClassNameHere {
 print() {
  console.log('In print function');
 }
}

or you can use this syntax

class ClassNameHere{
 print(){
  console.log('In print function');
 }
}

module.exports = ClassNameHere;

On the other hand to use this class in any other file you need to do these steps. First require that file using this syntax: const anyVariableNameHere = require('filePathHere');

Then create an object const classObject = new anyVariableNameHere();

After this you can use classObject to access the actual class variables