23

Could anyone explain what is the best approach for node express app: CommonJS or ES Modules as of today? In the official documentation it shows that CommonJS module is used, however there are many discussions that point to usage of ES Modules for backend development. So I'd like to get some opinions.

Thanks in advance!

1
  • 2
    There is no "best". CJS is the legacy Node-only version that they had to invent because JS didn't have a module system when Node was invented, and ESM is JS's own module system that was added quite a while back now, but well after Node had become popular. So it's really up to you. There's very little reason to still write CJS today, but it's not "worse" than writing ESM, you're just targeting a different crowd. (And your code won't work in more modern JS solutions like Deno or Bun, for example) Commented Sep 17, 2023 at 14:51

4 Answers 4

46

Node.js official docs are excellent at explaining the two different types of module systems. CommonJS and ESM.

There is a very good chapter in Node.js Design Patterns, Chapter 2: The Module System (2020) that describes where CommonJS came from. It emerges from the need to provide a module system for JavaScript in browserless environments simply because one did not exist, other than the AMD and UMD initiatives, and to not rely on urls and <script> tags for resources. It was successful so became popular.

We now have ECMAScript Modules thanks to the ES2015 official proposal and it attempts to align the management of modules in both server-side and browser environments.

The main points to help you decide are:

  1. CommonJS uses the require function to load modules and is synchronous. As a result, when you assign a module to module.exports for example, it too must be synchronous. If you have asynchronous stages in your module then you can export an uninitialized module but using require will mean that there is a chance it won't be ready to use in the code that requires it before it's initialized. An example of exporting and importing might look like this:
// titleCase.cjs

function titleCase(str) {
   if (!str){
      return '';
   }
   return str.toLowerCase().split(' ').map(word => {
      if (!word){
         return word;  
      }else{
         return word.charAt(0).toUpperCase() + word.slice(1);
      }
   }).join(' ');
}

module.exports = titleCase;
// app.js

const titleCase = require('./titleCase');
console.log(titleCase('the good, the bad and the ugly'));

// "The Good, The Bad And The Ugly"
  1. ES modules use the import keyword to load modules and the export keyword to export them. They are static, so they need to be described at the top level of every module and support loading modules asynchronously. ES modules facilitate static analysis of the dependency tree, making dead code elimination (tree shaking) more efficient. The same titleCase function above using ESM would look like:
// titleCase.js

export default function titleCase(str) {
   if (!str){
      return '';
   }
   return str.toLowerCase().split(' ').map(word => {
      if (!word){
         return word;  
      }else{
         return word.charAt(0).toUpperCase() + word.slice(1);
      }
   }).join(' ');
}
// app.js

import titleCase from './titleCase.js'; //< File extension required on import
console.log(titleCase('the good, the bad and the ugly'));

// "The Good, The Bad And The Ugly"
  1. ES modules run implicitly in strict mode (can't be disabled). This is a good thing in many opinions as it enforces good coding practices.
  2. In CommonJS you can use the helpful __filename and __dirname. In ES modules you need to do a workaround to get the same functionality like so:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
  1. You can dynamically load modules in CommonJS with require:
let usefulModule;
if (process.env.DEV === true) {
   usefulModule = require('./utils/devmodule')
} else {
   usefulModule = require('./utils/prodmodule')
}
  1. And in ESM by using import as function:
let lib;
if (process.env.DEV === true) {
   lib  = await import('./utils/devmodule.js');
} else {
   lib  = await import('./utils/prodmodule.js');
}
const usefulModule = lib.usefulModule;

One thing I have noticed over the years of programming is that languages and libraries evolve. I have observed a growing shift in Node package docs giving more and more of their examples in ESM format. CommonJS is still the dominant option on npm but it tells me the ecosystem is slowly shifting away from CommonJS and towards ESM. Maybe they will run in tandem but such is the nature of evolution, one becomes dominant over the others. All of the projects I work on use the ESM approach. Good luck deciding.

Sign up to request clarification or add additional context in comments.

4 Comments

Number 6 I think you can using the dynamic import stackoverflow.com/questions/58858782/…
Yes, using the import() function but not the keyword. Still a useful link. Thank you.
Thank you very much for such helpful explanation! I very much appreciate it!
@jQueeny Very good write up. Very appreciated.
1

ES modules are designed to be loaded statically, while CommonJS modules are mainly loaded dynamically and synchronously. This can lead to slower performance and a blocking of the main thread. Static analysis refers to the process of analyzing code without executing it, and dynamic imports introduce runtime behavior into a module system. This means that the exact module that will be imported cannot be determined until the code is executed, making it difficult to analyze the module dependencies and relationships ahead of time (AOT).

Read this article please. https://dev.to/costamatheus97/es-modules-and-commonjs-an-overview-1i4b

Comments

-1

CommonJS is still more widely used so it can myabe be easier to work with since many modules can be written for CommonJS. I'd still suggest ESM for a new project, it's cleaner and surprisingly or no, more optimised. There's also a r performance related thing, ESM imports are asynchronous whereas CommonJS imports are synchronous. Also it has robust type checking and is statically analysable which are not available with CommonJS.

New contributor
test is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

2 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - From Review
-5

CommonJS is still more widely used so it can myabe be easier to work with since many modules can be written for CommonJS. I'd still suggest ESM for a new project, it's cleaner and surprisingly or no, more optimised. There's also a minor performance related thing, ESM imports are asynchronous whereas CommonJS imports are synchronous. Also it has robust type checking and is statically analysable which are not available with CommonJS.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.